10
build.gradle
@ -43,6 +43,8 @@ dependencies {
|
||||
compile 'com.github.chrisbanes.photoview:library:1.2.3'
|
||||
compile 'com.github.bumptech.glide:glide:3.6.0'
|
||||
compile 'com.makeramen:roundedimageview:2.1.0'
|
||||
compile 'com.pnikosis:materialish-progress:1.5'
|
||||
compile 'de.greenrobot:eventbus:2.4.0'
|
||||
compile ('com.afollestad:material-dialogs:0.7.3.1') {
|
||||
exclude module: 'appcompat-v7'
|
||||
exclude module: 'recyclerview-v7'
|
||||
@ -72,7 +74,7 @@ dependencies {
|
||||
compile 'org.whispersystems:jobmanager:0.11.0'
|
||||
compile 'org.whispersystems:libpastelog:1.0.6'
|
||||
compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||
compile 'org.whispersystems:textsecure-android:1.6.0'
|
||||
compile 'org.whispersystems:textsecure-android:1.6.1'
|
||||
|
||||
androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
|
||||
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
|
||||
@ -104,6 +106,8 @@ dependencyVerification {
|
||||
'com.github.chrisbanes.photoview:library:8b5344e206f125e7ba9d684008f36c4992d03853c57e5814125f88496126e3cc',
|
||||
'com.github.bumptech.glide:glide:adf657e6bddccb168a29e18ab0954043af46a9b5c736d8c3193c9783fd83d69e',
|
||||
'com.makeramen:roundedimageview:1f5a1865796b308c6cdd114acc6e78408b110f0a62fc63553278fbeacd489cd1',
|
||||
'com.pnikosis:materialish-progress:d71d80e00717a096784482aee21001a9d299fec3833e4ebd87739ed36cf77c54',
|
||||
'de.greenrobot:eventbus:61d743a748156a372024d083de763b9e91ac2dcb3f6a1cbc74995c7ddab6e968',
|
||||
'com.afollestad:material-dialogs:c17205f0d300baa307599c428a5473a6659684c94a5f68ae3c2b84b5e4741172',
|
||||
'pl.tajchert:waitingdots:2835d49e0787dbcb606c5a60021ced66578503b1e9fddcd7a5ef0cd5f095ba2c',
|
||||
'com.soundcloud.android:android-crop:ffd4b973cf6e97f7d64118a0dc088df50e9066fd5634fe6911dd0c0c5d346177',
|
||||
@ -119,11 +123,11 @@ dependencyVerification {
|
||||
'org.whispersystems:jobmanager:ea9cb943c4892fb90c1eea1be30efeb85cefca213d52c788419553b58d0ed70d',
|
||||
'org.whispersystems:libpastelog:550d33c565380d90f4c671e7b8ed5f3a6da55a9fda468373177106b2eb5220b2',
|
||||
'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb',
|
||||
'org.whispersystems:textsecure-android:b5786690a2603ca78eed8a4f829737c41e2b5099695ce02bd44d0a9af3392318',
|
||||
'org.whispersystems:textsecure-android:843d4483e9c3b3414373ddd70df19895b3ee7ef559eeb15e60926e1b07fcecf3',
|
||||
'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',
|
||||
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||
'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f',
|
||||
'org.whispersystems:textsecure-java:dd32ab5fbb232116e7e533a78dce7b8be168bf561c5774772406aea54a677c0a',
|
||||
'org.whispersystems:textsecure-java:f161c5d5be5a0ba52ede273692ef17982b2af270c6af5c3666bc2adb289a3f61',
|
||||
'org.whispersystems:axolotl-android:40d3db5004a84749a73f68d2f0d01b2ae35a73c54df96d8c6c6723b96efb6fc0',
|
||||
'com.googlecode.libphonenumber:libphonenumber:eba17eae81dd622ea89a00a3a8c025b2f25d342e0d9644c5b62e16f15687c3ab',
|
||||
'com.google.protobuf:protobuf-java:e0c1c64575c005601725e7c6a02cebf9e1285e888f756b2a1d73ffa8d725cc74',
|
||||
|
Before Width: | Height: | Size: 991 B After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 369 B |
Before Width: | Height: | Size: 329 B |
Before Width: | Height: | Size: 348 B |
Before Width: | Height: | Size: 348 B |
Before Width: | Height: | Size: 319 B |
Before Width: | Height: | Size: 354 B |
Before Width: | Height: | Size: 684 B After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 286 B |
Before Width: | Height: | Size: 263 B |
Before Width: | Height: | Size: 281 B |
Before Width: | Height: | Size: 275 B |
Before Width: | Height: | Size: 253 B |
Before Width: | Height: | Size: 276 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 464 B |
Before Width: | Height: | Size: 412 B |
Before Width: | Height: | Size: 433 B |
Before Width: | Height: | Size: 419 B |
Before Width: | Height: | Size: 397 B |
Before Width: | Height: | Size: 457 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.2 KiB |
5
res/drawable/progress_background.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="#99000000"/>
|
||||
</shape>
|
@ -1,30 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/* //device/apps/common/res/drawable/status_icon_background.xml
|
||||
**
|
||||
** Copyright 2008, The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
-->
|
||||
<animation-list
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:oneshot="false">
|
||||
<item android:drawable="@drawable/stat_sys_download_anim0" android:duration="200" />
|
||||
<item android:drawable="@drawable/stat_sys_download_anim1" android:duration="200" />
|
||||
<item android:drawable="@drawable/stat_sys_download_anim2" android:duration="200" />
|
||||
<item android:drawable="@drawable/stat_sys_download_anim3" android:duration="200" />
|
||||
<item android:drawable="@drawable/stat_sys_download_anim4" android:duration="200" />
|
||||
<item android:drawable="@drawable/stat_sys_download_anim5" android:duration="200" />
|
||||
</animation-list>
|
||||
|
@ -46,8 +46,8 @@
|
||||
|
||||
<org.thoughtcrime.securesms.components.ThumbnailView
|
||||
android:id="@+id/attachment_thumbnail"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="150dip"
|
||||
android:layout_width="230dp"
|
||||
android:layout_height="150dp"
|
||||
app:riv_corner_radius="3dp"
|
||||
android:contentDescription="@string/conversation_activity__attachment_thumbnail"/>
|
||||
</FrameLayout>
|
||||
|
@ -1,8 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.thoughtcrime.securesms.components.SquareLinearLayout
|
||||
<org.thoughtcrime.securesms.components.SquareFrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
@ -10,8 +8,6 @@
|
||||
android:id="@+id/image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
android:background="#11ffffff"
|
||||
android:contentDescription="@string/media_preview_activity__image_content_description" />
|
||||
|
||||
</org.thoughtcrime.securesms.components.SquareLinearLayout>
|
||||
</org.thoughtcrime.securesms.components.SquareFrameLayout>
|
||||
|
@ -1,22 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="210dp">
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<org.thoughtcrime.securesms.components.ForegroundImageView
|
||||
android:id="@+id/image_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"
|
||||
android:scaleType="centerCrop"
|
||||
android:adjustViewBounds="true"
|
||||
android:contentDescription="@string/conversation_item__mms_image_description"
|
||||
app:riv_corner_radius="@dimen/message_bubble_corner_radius"
|
||||
app:riv_border_width="@dimen/media_bubble_border_width"
|
||||
tools:src="@drawable/ic_video_light"
|
||||
tools:visibility="visible" />
|
||||
<com.makeramen.roundedimageview.RoundedImageView
|
||||
android:id="@+id/thumbnail_image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:adjustViewBounds="true"
|
||||
android:contentDescription="@string/conversation_item__mms_image_description"
|
||||
android:layout_margin="@dimen/media_bubble_border_width"
|
||||
app:riv_corner_radius="@dimen/message_bubble_corner_radius" />
|
||||
|
||||
</LinearLayout>
|
||||
<com.pnikosis.materialishprogress.ProgressWheel
|
||||
android:id="@+id/progress_wheel"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/progress_background"
|
||||
android:visibility="gone"
|
||||
app:matProg_barColor="@color/white"
|
||||
app:matProg_linearProgress="true"
|
||||
app:matProg_spinSpeed="0.333" />
|
||||
|
||||
</merge>
|
||||
|
@ -100,12 +100,6 @@
|
||||
<attr name="menu_forward_icon" format="reference" />
|
||||
<attr name="menu_save_icon" format="reference" />
|
||||
|
||||
<declare-styleable name="ForegroundImageView">
|
||||
<attr name="android:foreground" />
|
||||
<attr name="android:foregroundInsidePadding" />
|
||||
<attr name="android:foregroundGravity" />
|
||||
</declare-styleable>
|
||||
|
||||
<attr name="pref_ic_sms_mms" format="reference" />
|
||||
<attr name="pref_ic_notifications" format="reference" />
|
||||
<attr name="pref_ic_app_protection" format="reference" />
|
||||
|
@ -5,11 +5,14 @@ import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.animation.AnimationUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
@ -58,10 +61,8 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
protected void startActivitySceneTransition(Intent intent, View sharedView, String transitionName) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this, sharedView, transitionName).toBundle());
|
||||
} else {
|
||||
startActivity(intent);
|
||||
}
|
||||
Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(this, sharedView, transitionName)
|
||||
.toBundle();
|
||||
ActivityCompat.startActivity(this, intent, bundle);
|
||||
}
|
||||
}
|
||||
|
@ -263,6 +263,7 @@ public class ConversationItem extends LinearLayout {
|
||||
mediaThumbnail.setImageResource(masterSecret, messageRecord.getId(),
|
||||
messageRecord.getDateReceived(),
|
||||
((MediaMmsMessageRecord)messageRecord).getSlideDeckFuture());
|
||||
mediaThumbnail.setShowProgress(!messageRecord.isFailed());
|
||||
bodyText.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
|
||||
} else {
|
||||
mediaThumbnail.setVisibility(View.GONE);
|
||||
@ -374,7 +375,7 @@ public class ConversationItem extends LinearLayout {
|
||||
contactPhoto.setAvatar(recipient, true);
|
||||
contactPhoto.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
|
||||
/// Event handlers
|
||||
|
||||
private void handleApproveIdentity() {
|
||||
|
@ -1,232 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2006 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActivityOptions;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
|
||||
import com.makeramen.roundedimageview.RoundedImageView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
/**
|
||||
* https://gist.github.com/chrisbanes/9091754
|
||||
*/
|
||||
public class ForegroundImageView extends RoundedImageView {
|
||||
|
||||
private Drawable mForeground;
|
||||
|
||||
private final Rect mSelfBounds = new Rect();
|
||||
private final Rect mOverlayBounds = new Rect();
|
||||
|
||||
private int mForegroundGravity = Gravity.FILL;
|
||||
|
||||
private boolean mForegroundInPadding = true;
|
||||
|
||||
private boolean mForegroundBoundsChanged = false;
|
||||
|
||||
public ForegroundImageView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public ForegroundImageView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public ForegroundImageView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ForegroundImageView,
|
||||
defStyle, 0);
|
||||
|
||||
mForegroundGravity = a.getInt(
|
||||
R.styleable.ForegroundImageView_android_foregroundGravity, mForegroundGravity);
|
||||
|
||||
final Drawable d = a.getDrawable(R.styleable.ForegroundImageView_android_foreground);
|
||||
if (d != null) {
|
||||
setForeground(d);
|
||||
}
|
||||
|
||||
mForegroundInPadding = a.getBoolean(
|
||||
R.styleable.ForegroundImageView_android_foregroundInsidePadding, true);
|
||||
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes how the foreground is positioned.
|
||||
*
|
||||
* @return foreground gravity.
|
||||
*
|
||||
* @see #setForegroundGravity(int)
|
||||
*/
|
||||
public int getForegroundGravity() {
|
||||
return mForegroundGravity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes how the foreground is positioned. Defaults to START and TOP.
|
||||
*
|
||||
* @param foregroundGravity See {@link android.view.Gravity}
|
||||
*
|
||||
* @see #getForegroundGravity()
|
||||
*/
|
||||
public void setForegroundGravity(int foregroundGravity) {
|
||||
if (mForegroundGravity != foregroundGravity) {
|
||||
if ((foregroundGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
|
||||
foregroundGravity |= Gravity.START;
|
||||
}
|
||||
|
||||
if ((foregroundGravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
|
||||
foregroundGravity |= Gravity.TOP;
|
||||
}
|
||||
|
||||
mForegroundGravity = foregroundGravity;
|
||||
|
||||
|
||||
if (mForegroundGravity == Gravity.FILL && mForeground != null) {
|
||||
Rect padding = new Rect();
|
||||
mForeground.getPadding(padding);
|
||||
}
|
||||
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(VERSION_CODES.JELLY_BEAN)
|
||||
public ActivityOptions getThumbnailTransition() {
|
||||
return ActivityOptions.makeScaleUpAnimation(this, 0, 0, getWidth(), getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean verifyDrawable(Drawable who) {
|
||||
return super.verifyDrawable(who) || (who == mForeground);
|
||||
}
|
||||
|
||||
@Override
|
||||
@TargetApi(VERSION_CODES.HONEYCOMB)
|
||||
public void jumpDrawablesToCurrentState() {
|
||||
super.jumpDrawablesToCurrentState();
|
||||
if (mForeground != null) mForeground.jumpToCurrentState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawableStateChanged() {
|
||||
super.drawableStateChanged();
|
||||
if (mForeground != null && mForeground.isStateful()) {
|
||||
mForeground.setState(getDrawableState());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Supply a Drawable that is to be rendered on top of all of the child
|
||||
* views in the frame layout. Any padding in the Drawable will be taken
|
||||
* into account by ensuring that the children are inset to be placed
|
||||
* inside of the padding area.
|
||||
*
|
||||
* @param drawable The Drawable to be drawn on top of the children.
|
||||
*/
|
||||
public void setForeground(Drawable drawable) {
|
||||
if (mForeground != drawable) {
|
||||
if (mForeground != null) {
|
||||
mForeground.setCallback(null);
|
||||
unscheduleDrawable(mForeground);
|
||||
}
|
||||
|
||||
mForeground = drawable;
|
||||
|
||||
if (drawable != null) {
|
||||
setWillNotDraw(false);
|
||||
drawable.setCallback(this);
|
||||
if (drawable.isStateful()) {
|
||||
drawable.setState(getDrawableState());
|
||||
}
|
||||
if (mForegroundGravity == Gravity.FILL) {
|
||||
Rect padding = new Rect();
|
||||
drawable.getPadding(padding);
|
||||
}
|
||||
} else {
|
||||
setWillNotDraw(true);
|
||||
}
|
||||
requestLayout();
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the drawable used as the foreground of this FrameLayout. The
|
||||
* foreground drawable, if non-null, is always drawn on top of the children.
|
||||
*
|
||||
* @return A Drawable or null if no foreground was set.
|
||||
*/
|
||||
public Drawable getForeground() {
|
||||
return mForeground;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
mForegroundBoundsChanged = changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
mForegroundBoundsChanged = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
super.draw(canvas);
|
||||
|
||||
if (mForeground != null) {
|
||||
final Drawable foreground = mForeground;
|
||||
|
||||
if (mForegroundBoundsChanged) {
|
||||
mForegroundBoundsChanged = false;
|
||||
final Rect selfBounds = mSelfBounds;
|
||||
final Rect overlayBounds = mOverlayBounds;
|
||||
|
||||
final int w = getRight() - getLeft();
|
||||
final int h = getBottom() - getTop();
|
||||
|
||||
if (mForegroundInPadding) {
|
||||
selfBounds.set(0, 0, w, h);
|
||||
} else {
|
||||
selfBounds.set(getPaddingLeft(), getPaddingTop(),
|
||||
w - getPaddingRight(), h - getPaddingBottom());
|
||||
}
|
||||
|
||||
Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(),
|
||||
foreground.getIntrinsicHeight(), selfBounds, overlayBounds);
|
||||
foreground.setBounds(overlayBounds);
|
||||
}
|
||||
|
||||
foreground.draw(canvas);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
public class SquareFrameLayout extends FrameLayout {
|
||||
@SuppressWarnings("unused")
|
||||
public SquareFrameLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public SquareFrameLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@TargetApi(VERSION_CODES.HONEYCOMB) @SuppressWarnings("unused")
|
||||
public SquareFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@TargetApi(VERSION_CODES.LOLLIPOP) @SuppressWarnings("unused")
|
||||
public SquareFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
//noinspection SuspiciousNameCombination
|
||||
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
|
||||
}
|
||||
}
|
@ -4,56 +4,87 @@ import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.Animation.AnimationListener;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.bumptech.glide.GenericRequestBuilder;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.RequestListener;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
import com.makeramen.roundedimageview.RoundedImageView;
|
||||
import com.pnikosis.materialishprogress.ProgressWheel;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.jobs.PartProgressEvent;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
import org.thoughtcrime.securesms.mms.ThumbnailTransform;
|
||||
import org.thoughtcrime.securesms.util.FutureTaskListener;
|
||||
import org.thoughtcrime.securesms.util.ListenableFutureTask;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import de.greenrobot.event.EventBus;
|
||||
import ws.com.google.android.mms.pdu.PduPart;
|
||||
|
||||
public class ThumbnailView extends RoundedImageView {
|
||||
public class ThumbnailView extends FrameLayout {
|
||||
private static final String TAG = ThumbnailView.class.getSimpleName();
|
||||
|
||||
private boolean showProgress = true;
|
||||
private RoundedImageView image;
|
||||
private ProgressWheel progress;
|
||||
|
||||
private ListenableFutureTask<SlideDeck> slideDeckFuture = null;
|
||||
private SlideDeckListener slideDeckListener = null;
|
||||
private ThumbnailClickListener thumbnailClickListener = null;
|
||||
private String slideId = null;
|
||||
private Slide slide = null;
|
||||
private Handler handler = new Handler();
|
||||
|
||||
public ThumbnailView(Context context) {
|
||||
super(context);
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public ThumbnailView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public ThumbnailView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
inflate(context, R.layout.thumbnail_view, this);
|
||||
image = (RoundedImageView) findViewById(R.id.thumbnail_image);
|
||||
progress = (ProgressWheel) findViewById(R.id.progress_wheel);
|
||||
}
|
||||
|
||||
@Override protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
EventBus.getDefault().registerSticky(this);
|
||||
}
|
||||
|
||||
@Override protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
EventBus.getDefault().unregister(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void onEventAsync(final PartProgressEvent event) {
|
||||
if (this.slide != null && event.partId.equals(this.slide.getPart().getPartId())) {
|
||||
Util.runOnMain(new Runnable() {
|
||||
@Override public void run() {
|
||||
progress.setInstantProgress(((float) event.progress) / event.total);
|
||||
if (event.progress >= event.total) animateOutProgress();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void setImageResource(@Nullable MasterSecret masterSecret,
|
||||
@ -67,7 +98,7 @@ public class ThumbnailView extends RoundedImageView {
|
||||
String slideId = id + "::" + timestamp;
|
||||
|
||||
if (!slideId.equals(this.slideId)) {
|
||||
setImageDrawable(null);
|
||||
image.setImageDrawable(null);
|
||||
this.slide = null;
|
||||
this.slideId = slideId;
|
||||
}
|
||||
@ -82,13 +113,24 @@ public class ThumbnailView extends RoundedImageView {
|
||||
}
|
||||
|
||||
public void setImageResource(@NonNull Slide slide, @Nullable MasterSecret masterSecret) {
|
||||
if (isContextValid()) {
|
||||
if (!Util.equals(slide, this.slide)) buildGlideRequest(slide, masterSecret).into(this);
|
||||
this.slide = slide;
|
||||
setOnClickListener(new ThumbnailClickDispatcher(thumbnailClickListener, slide));
|
||||
} else {
|
||||
Log.w(TAG, "Not going to load resource, context is invalid");
|
||||
if (Util.equals(slide, this.slide)) {
|
||||
Log.w(TAG, "Not loading resource, slide was identical");
|
||||
return;
|
||||
}
|
||||
if (!isContextValid()) {
|
||||
Log.w(TAG, "Not loading resource, context is invalid");
|
||||
return;
|
||||
}
|
||||
|
||||
this.slide = slide;
|
||||
if (slide.isInProgress() && showProgress) {
|
||||
progress.spin();
|
||||
progress.setVisibility(VISIBLE);
|
||||
} else {
|
||||
progress.setVisibility(GONE);
|
||||
}
|
||||
buildGlideRequest(slide, masterSecret).into(image);
|
||||
setOnClickListener(new ThumbnailClickDispatcher(thumbnailClickListener, slide));
|
||||
}
|
||||
|
||||
public void setThumbnailClickListener(ThumbnailClickListener listener) {
|
||||
@ -99,6 +141,13 @@ public class ThumbnailView extends RoundedImageView {
|
||||
if (isContextValid()) Glide.clear(this);
|
||||
}
|
||||
|
||||
public void setShowProgress(boolean showProgress) {
|
||||
this.showProgress = showProgress;
|
||||
if (progress.getVisibility() == View.VISIBLE && !showProgress) {
|
||||
animateOutProgress();
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(VERSION_CODES.JELLY_BEAN_MR1)
|
||||
private boolean isContextValid() {
|
||||
return !(getContext() instanceof Activity) ||
|
||||
@ -110,22 +159,17 @@ public class ThumbnailView extends RoundedImageView {
|
||||
@Nullable MasterSecret masterSecret)
|
||||
{
|
||||
final GenericRequestBuilder builder;
|
||||
if (slide.getPart().isPendingPush()) {
|
||||
builder = buildPendingGlideRequest(slide);
|
||||
} else if (slide.getThumbnailUri() != null) {
|
||||
if (slide.getThumbnailUri() != null) {
|
||||
builder = buildThumbnailGlideRequest(slide, masterSecret);
|
||||
} else {
|
||||
builder = buildPlaceholderGlideRequest(slide);
|
||||
}
|
||||
|
||||
return builder.error(R.drawable.ic_missing_thumbnail_picture);
|
||||
}
|
||||
|
||||
private GenericRequestBuilder buildPendingGlideRequest(Slide slide) {
|
||||
return Glide.with(getContext()).load(R.drawable.stat_sys_download_anim0)
|
||||
.dontTransform()
|
||||
.skipMemoryCache(true)
|
||||
.crossFade();
|
||||
if (slide.isInProgress() && showProgress) {
|
||||
return builder;
|
||||
} else {
|
||||
return builder.error(R.drawable.ic_missing_thumbnail_picture);
|
||||
}
|
||||
}
|
||||
|
||||
private GenericRequestBuilder buildThumbnailGlideRequest(Slide slide, MasterSecret masterSecret) {
|
||||
@ -148,7 +192,7 @@ public class ThumbnailView extends RoundedImageView {
|
||||
}
|
||||
|
||||
return Glide.with(getContext()).load(new DecryptableUri(masterSecret, slide.getThumbnailUri()))
|
||||
.transform(new ThumbnailTransform(getContext()));
|
||||
.centerCrop();
|
||||
}
|
||||
|
||||
private GenericRequestBuilder buildPlaceholderGlideRequest(Slide slide) {
|
||||
@ -157,6 +201,19 @@ public class ThumbnailView extends RoundedImageView {
|
||||
.crossFade();
|
||||
}
|
||||
|
||||
private void animateOutProgress() {
|
||||
AlphaAnimation animation = new AlphaAnimation(1f, 0f);
|
||||
animation.setDuration(200);
|
||||
animation.setAnimationListener(new AnimationListener() {
|
||||
@Override public void onAnimationStart(Animation animation) { }
|
||||
@Override public void onAnimationRepeat(Animation animation) { }
|
||||
@Override public void onAnimationEnd(Animation animation) {
|
||||
progress.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
progress.startAnimation(animation);
|
||||
}
|
||||
|
||||
private class SlideDeckListener implements FutureTaskListener<SlideDeck> {
|
||||
private final MasterSecret masterSecret;
|
||||
|
||||
@ -170,14 +227,14 @@ public class ThumbnailView extends RoundedImageView {
|
||||
|
||||
final Slide slide = slideDeck.getThumbnailSlide(getContext());
|
||||
if (slide != null) {
|
||||
handler.post(new Runnable() {
|
||||
Util.runOnMain(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setImageResource(slide, masterSecret);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
handler.post(new Runnable() {
|
||||
Util.runOnMain(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.w(TAG, "Resolved slide was null!");
|
||||
@ -190,7 +247,7 @@ public class ThumbnailView extends RoundedImageView {
|
||||
@Override
|
||||
public void onFailure(Throwable error) {
|
||||
Log.w(TAG, error);
|
||||
handler.post(new Runnable() {
|
||||
Util.runOnMain(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.w(TAG, "onFailure!");
|
||||
|
@ -721,6 +721,12 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
contentValues.put(DATE_RECEIVED, contentValues.getAsLong(DATE_SENT));
|
||||
contentValues.remove(ADDRESS);
|
||||
|
||||
if (sendRequest.getBody() != null) {
|
||||
for (int i = 0; i < sendRequest.getBody().getPartsNum(); i++) {
|
||||
sendRequest.getBody().getPart(i).setInProgress(true);
|
||||
}
|
||||
}
|
||||
|
||||
long messageId = insertMediaMessage(masterSecret, sendRequest.getPduHeaders(),
|
||||
sendRequest.getBody(), contentValues);
|
||||
jobManager.add(new TrimThreadJob(context, threadId));
|
||||
|
@ -57,26 +57,26 @@ import ws.com.google.android.mms.pdu.PduPart;
|
||||
public class PartDatabase extends Database {
|
||||
private static final String TAG = PartDatabase.class.getSimpleName();
|
||||
|
||||
private static final String TABLE_NAME = "part";
|
||||
private static final String ROW_ID = "_id";
|
||||
private static final String MMS_ID = "mid";
|
||||
private static final String SEQUENCE = "seq";
|
||||
private static final String CONTENT_TYPE = "ct";
|
||||
private static final String NAME = "name";
|
||||
private static final String CHARSET = "chset";
|
||||
private static final String CONTENT_DISPOSITION = "cd";
|
||||
private static final String FILENAME = "fn";
|
||||
private static final String CONTENT_ID = "cid";
|
||||
private static final String CONTENT_LOCATION = "cl";
|
||||
private static final String CONTENT_TYPE_START = "ctt_s";
|
||||
private static final String CONTENT_TYPE_TYPE = "ctt_t";
|
||||
private static final String ENCRYPTED = "encrypted";
|
||||
private static final String DATA = "_data";
|
||||
private static final String PENDING_PUSH_ATTACHMENT = "pending_push";
|
||||
private static final String SIZE = "data_size";
|
||||
private static final String THUMBNAIL = "thumbnail";
|
||||
private static final String ASPECT_RATIO = "aspect_ratio";
|
||||
private static final String UNIQUE_ID = "unique_id";
|
||||
private static final String TABLE_NAME = "part";
|
||||
private static final String ROW_ID = "_id";
|
||||
private static final String MMS_ID = "mid";
|
||||
private static final String SEQUENCE = "seq";
|
||||
private static final String CONTENT_TYPE = "ct";
|
||||
private static final String NAME = "name";
|
||||
private static final String CHARSET = "chset";
|
||||
private static final String CONTENT_DISPOSITION = "cd";
|
||||
private static final String FILENAME = "fn";
|
||||
private static final String CONTENT_ID = "cid";
|
||||
private static final String CONTENT_LOCATION = "cl";
|
||||
private static final String CONTENT_TYPE_START = "ctt_s";
|
||||
private static final String CONTENT_TYPE_TYPE = "ctt_t";
|
||||
private static final String ENCRYPTED = "encrypted";
|
||||
private static final String DATA = "_data";
|
||||
private static final String IN_PROGRESS = "pending_push";
|
||||
private static final String SIZE = "data_size";
|
||||
private static final String THUMBNAIL = "thumbnail";
|
||||
private static final String ASPECT_RATIO = "aspect_ratio";
|
||||
private static final String UNIQUE_ID = "unique_id";
|
||||
|
||||
private static final String PART_ID_WHERE = ROW_ID + " = ? AND " + UNIQUE_ID + " = ?";
|
||||
|
||||
@ -86,12 +86,12 @@ public class PartDatabase extends Database {
|
||||
CONTENT_DISPOSITION + " TEXT, " + FILENAME + " TEXT, " + CONTENT_ID + " TEXT, " +
|
||||
CONTENT_LOCATION + " TEXT, " + CONTENT_TYPE_START + " INTEGER, " +
|
||||
CONTENT_TYPE_TYPE + " TEXT, " + ENCRYPTED + " INTEGER, " +
|
||||
PENDING_PUSH_ATTACHMENT + " INTEGER, "+ DATA + " TEXT, " + SIZE + " INTEGER, " +
|
||||
IN_PROGRESS + " INTEGER, "+ DATA + " TEXT, " + SIZE + " INTEGER, " +
|
||||
THUMBNAIL + " TEXT, " + ASPECT_RATIO + " REAL, " + UNIQUE_ID + " INTEGER NOT NULL);";
|
||||
|
||||
public static final String[] CREATE_INDEXS = {
|
||||
"CREATE INDEX IF NOT EXISTS part_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");",
|
||||
"CREATE INDEX IF NOT EXISTS pending_push_index ON " + TABLE_NAME + " (" + PENDING_PUSH_ATTACHMENT + ");",
|
||||
"CREATE INDEX IF NOT EXISTS pending_push_index ON " + TABLE_NAME + " (" + IN_PROGRESS + ");",
|
||||
};
|
||||
|
||||
private final static String IMAGES_QUERY = "SELECT " + TABLE_NAME + "." + ROW_ID + ", "
|
||||
@ -127,7 +127,7 @@ public class PartDatabase extends Database {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
|
||||
part.setContentDisposition(new byte[0]);
|
||||
part.setPendingPush(false);
|
||||
part.setInProgress(false);
|
||||
|
||||
ContentValues values = getContentValuesForPart(part);
|
||||
|
||||
@ -275,10 +275,10 @@ public class PartDatabase extends Database {
|
||||
if (!cursor.isNull(encryptedColumn))
|
||||
part.setEncrypted(cursor.getInt(encryptedColumn) == 1);
|
||||
|
||||
int pendingPushColumn = cursor.getColumnIndexOrThrow(PENDING_PUSH_ATTACHMENT);
|
||||
int inProgressColumn = cursor.getColumnIndexOrThrow(IN_PROGRESS);
|
||||
|
||||
if (!cursor.isNull(pendingPushColumn))
|
||||
part.setPendingPush(cursor.getInt(pendingPushColumn) == 1);
|
||||
if (!cursor.isNull(inProgressColumn))
|
||||
part.setInProgress(cursor.getInt(inProgressColumn) == 1);
|
||||
|
||||
int sizeColumn = cursor.getColumnIndexOrThrow(SIZE);
|
||||
|
||||
@ -325,7 +325,7 @@ public class PartDatabase extends Database {
|
||||
}
|
||||
|
||||
contentValues.put(ENCRYPTED, part.getEncrypted() ? 1 : 0);
|
||||
contentValues.put(PENDING_PUSH_ATTACHMENT, part.isPendingPush() ? 1 : 0);
|
||||
contentValues.put(IN_PROGRESS, part.isInProgress() ? 1 : 0);
|
||||
contentValues.put(UNIQUE_ID, part.getUniqueId());
|
||||
|
||||
return contentValues;
|
||||
@ -437,7 +437,7 @@ public class PartDatabase extends Database {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
Pair<File, Long> partData = null;
|
||||
|
||||
if (!part.isPendingPush()) {
|
||||
if (part.getData() != null || part.getDataUri() != null) {
|
||||
partData = writePartData(masterSecret, part);
|
||||
Log.w(TAG, "Wrote part to file: " + partData.first.getAbsolutePath());
|
||||
}
|
||||
@ -457,7 +457,7 @@ public class PartDatabase extends Database {
|
||||
Log.w(TAG, "inserting pre-generated thumbnail");
|
||||
ThumbnailData data = new ThumbnailData(thumbnail);
|
||||
updatePartThumbnail(masterSecret, partId, part, data.toDataStream(), data.getAspectRatio());
|
||||
} else if (!part.isPendingPush()) {
|
||||
} else if (!part.isInProgress()) {
|
||||
thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret, partId));
|
||||
}
|
||||
|
||||
@ -472,7 +472,7 @@ public class PartDatabase extends Database {
|
||||
Pair<File, Long> partData = writePartData(masterSecret, part, data);
|
||||
|
||||
part.setContentDisposition(new byte[0]);
|
||||
part.setPendingPush(false);
|
||||
part.setInProgress(false);
|
||||
|
||||
ContentValues values = getContentValuesForPart(part);
|
||||
|
||||
@ -488,6 +488,17 @@ public class PartDatabase extends Database {
|
||||
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId));
|
||||
}
|
||||
|
||||
public void markPartUploaded(long messageId, PduPart part) {
|
||||
ContentValues values = new ContentValues(1);
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
|
||||
part.setInProgress(false);
|
||||
values.put(IN_PROGRESS, false);
|
||||
database.update(TABLE_NAME, values, PART_ID_WHERE, part.getPartId().toStrings());
|
||||
|
||||
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId));
|
||||
}
|
||||
|
||||
public void updatePartData(MasterSecret masterSecret, PduPart part, InputStream data)
|
||||
throws MmsException
|
||||
{
|
||||
@ -640,5 +651,20 @@ public class PartDatabase extends Database {
|
||||
public boolean isValid() {
|
||||
return rowId >= 0 && uniqueId >= 0;
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
PartId partId = (PartId)o;
|
||||
|
||||
if (rowId != partId.rowId) return false;
|
||||
return uniqueId == partId.uniqueId;
|
||||
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
return Util.hashCode(rowId, uniqueId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,12 @@ package org.thoughtcrime.securesms.jobs;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.PartDatabase;
|
||||
import org.thoughtcrime.securesms.database.PartDatabase.PartId;
|
||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
@ -15,6 +17,7 @@ import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
|
||||
import org.whispersystems.libaxolotl.InvalidMessageException;
|
||||
import org.whispersystems.textsecure.api.TextSecureMessageReceiver;
|
||||
import org.whispersystems.textsecure.api.messages.TextSecureAttachment.ProgressListener;
|
||||
import org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer;
|
||||
import org.whispersystems.textsecure.api.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
import org.whispersystems.textsecure.api.push.exceptions.PushNetworkException;
|
||||
@ -26,6 +29,7 @@ import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import de.greenrobot.event.EventBus;
|
||||
import ws.com.google.android.mms.MmsException;
|
||||
import ws.com.google.android.mms.pdu.PduPart;
|
||||
|
||||
@ -82,15 +86,19 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
|
||||
private void retrievePart(MasterSecret masterSecret, PduPart part, long messageId)
|
||||
throws IOException
|
||||
{
|
||||
PartDatabase database = DatabaseFactory.getPartDatabase(context);
|
||||
File attachmentFile = null;
|
||||
PartDatabase.PartId partId = part.getPartId();
|
||||
PartDatabase database = DatabaseFactory.getPartDatabase(context);
|
||||
File attachmentFile = null;
|
||||
|
||||
final PartId partId = part.getPartId();
|
||||
try {
|
||||
attachmentFile = createTempFile();
|
||||
|
||||
TextSecureAttachmentPointer pointer = createAttachmentPointer(masterSecret, part);
|
||||
InputStream attachment = messageReceiver.retrieveAttachment(pointer, attachmentFile);
|
||||
InputStream attachment = messageReceiver.retrieveAttachment(pointer, attachmentFile, new ProgressListener() {
|
||||
@Override public void onAttachmentProgress(long total, long progress) {
|
||||
EventBus.getDefault().postSticky(new PartProgressEvent(partId, total, progress));
|
||||
}
|
||||
});
|
||||
|
||||
database.updateDownloadedPart(masterSecret, messageId, partId, part, attachment);
|
||||
} catch (InvalidPartException | NonSuccessfulResponseCodeException | InvalidMessageException | MmsException e) {
|
||||
@ -145,4 +153,5 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
|
||||
private class InvalidPartException extends Exception {
|
||||
public InvalidPartException(Exception e) {super(e);}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ public class AvatarDownloadJob extends MasterSecretJob {
|
||||
|
||||
destination.deleteOnExit();
|
||||
|
||||
socket.retrieveAttachment(relay, contentLocation, destination);
|
||||
socket.retrieveAttachment(relay, contentLocation, destination, null);
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
@ -103,7 +103,8 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje
|
||||
FileInputStream contactsFileStream = new FileInputStream(contactsFile);
|
||||
TextSecureAttachmentStream attachmentStream = new TextSecureAttachmentStream(contactsFileStream,
|
||||
"application/octet-stream",
|
||||
contactsFile.length());
|
||||
contactsFile.length(),
|
||||
null);
|
||||
|
||||
try {
|
||||
messageSender.sendMessage(TextSecureSyncMessage.forContacts(attachmentStream));
|
||||
@ -117,7 +118,7 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje
|
||||
try {
|
||||
Uri displayPhotoUri = Uri.withAppendedPath(uri, ContactsContract.Contacts.Photo.DISPLAY_PHOTO);
|
||||
AssetFileDescriptor fd = context.getContentResolver().openAssetFileDescriptor(displayPhotoUri, "r");
|
||||
return Optional.of(new TextSecureAttachmentStream(fd.createInputStream(), "image/*", fd.getLength()));
|
||||
return Optional.of(new TextSecureAttachmentStream(fd.createInputStream(), "image/*", fd.getLength(), null));
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
@ -140,7 +141,7 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje
|
||||
byte[] data = cursor.getBlob(0);
|
||||
|
||||
if (data != null) {
|
||||
return Optional.of(new TextSecureAttachmentStream(new ByteArrayInputStream(data), "image/*", data.length));
|
||||
return Optional.of(new TextSecureAttachmentStream(new ByteArrayInputStream(data), "image/*", data.length, null));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,7 +95,8 @@ public class MultiDeviceGroupUpdateJob extends MasterSecretJob implements Inject
|
||||
FileInputStream contactsFileStream = new FileInputStream(contactsFile);
|
||||
TextSecureAttachmentStream attachmentStream = new TextSecureAttachmentStream(contactsFileStream,
|
||||
"application/octet-stream",
|
||||
contactsFile.length());
|
||||
contactsFile.length(),
|
||||
null);
|
||||
|
||||
messageSender.sendMessage(TextSecureSyncMessage.forGroups(attachmentStream));
|
||||
}
|
||||
@ -105,7 +106,7 @@ public class MultiDeviceGroupUpdateJob extends MasterSecretJob implements Inject
|
||||
if (avatar == null) return Optional.absent();
|
||||
|
||||
return Optional.of(new TextSecureAttachmentStream(new ByteArrayInputStream(avatar),
|
||||
"image/*", avatar.length));
|
||||
"image/*", avatar.length, null));
|
||||
}
|
||||
|
||||
private File createTempFile(String prefix) throws IOException {
|
||||
|
15
src/org/thoughtcrime/securesms/jobs/PartProgressEvent.java
Normal file
@ -0,0 +1,15 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import org.thoughtcrime.securesms.database.PartDatabase.PartId;
|
||||
|
||||
public class PartProgressEvent {
|
||||
public PartId partId;
|
||||
public long total;
|
||||
public long progress;
|
||||
|
||||
public PartProgressEvent(PartId partId, long total, long progress) {
|
||||
this.partId = partId;
|
||||
this.total = total;
|
||||
this.progress = progress;
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.PartDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||
import org.thoughtcrime.securesms.mms.PartParser;
|
||||
@ -30,6 +31,7 @@ import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import ws.com.google.android.mms.MmsException;
|
||||
import ws.com.google.android.mms.pdu.PduBody;
|
||||
import ws.com.google.android.mms.pdu.SendReq;
|
||||
|
||||
import static org.thoughtcrime.securesms.dependencies.TextSecureCommunicationModule.TextSecureMessageSenderFactory;
|
||||
@ -69,6 +71,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
||||
database.markAsPush(messageId);
|
||||
database.markAsSecure(messageId);
|
||||
database.markAsSent(messageId, "push".getBytes(), 0);
|
||||
updatePartsStatus(message.getBody());
|
||||
} catch (InsecureFallbackApprovalException ifae) {
|
||||
Log.w(TAG, ifae);
|
||||
database.markAsPendingInsecureSmsFallback(messageId);
|
||||
@ -97,6 +100,13 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
||||
notifyMediaMessageDeliveryFailed(context, messageId);
|
||||
}
|
||||
|
||||
private void updatePartsStatus(PduBody body) {
|
||||
if (body == null) return;
|
||||
PartDatabase database = DatabaseFactory.getPartDatabase(context);
|
||||
for (int i = 0; i < body.getPartsNum(); i++) {
|
||||
database.markPartUploaded(messageId, body.getPart(i));
|
||||
}
|
||||
}
|
||||
|
||||
private void deliver(MasterSecret masterSecret, SendReq message)
|
||||
throws RetryLaterException, InsecureFallbackApprovalException, UntrustedIdentityException,
|
||||
|
@ -10,13 +10,12 @@ import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
|
||||
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||
import org.whispersystems.textsecure.api.messages.TextSecureAttachment;
|
||||
import org.whispersystems.textsecure.api.messages.TextSecureAttachment.ProgressListener;
|
||||
import org.whispersystems.textsecure.api.messages.TextSecureAttachmentStream;
|
||||
import org.whispersystems.textsecure.api.push.TextSecureAddress;
|
||||
import org.whispersystems.textsecure.api.util.InvalidNumberException;
|
||||
@ -26,6 +25,7 @@ import java.io.InputStream;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import de.greenrobot.event.EventBus;
|
||||
import ws.com.google.android.mms.ContentType;
|
||||
import ws.com.google.android.mms.pdu.PduPart;
|
||||
import ws.com.google.android.mms.pdu.SendReq;
|
||||
@ -59,16 +59,19 @@ public abstract class PushSendJob extends SendJob {
|
||||
List<TextSecureAttachment> attachments = new LinkedList<>();
|
||||
|
||||
for (int i=0;i<message.getBody().getPartsNum();i++) {
|
||||
PduPart part = message.getBody().getPart(i);
|
||||
String contentType = Util.toIsoString(part.getContentType());
|
||||
final PduPart part = message.getBody().getPart(i);
|
||||
final String contentType = Util.toIsoString(part.getContentType());
|
||||
if (ContentType.isImageType(contentType) ||
|
||||
ContentType.isAudioType(contentType) ||
|
||||
ContentType.isVideoType(contentType))
|
||||
{
|
||||
|
||||
try {
|
||||
InputStream is = PartAuthority.getPartStream(context, masterSecret, part.getDataUri());
|
||||
attachments.add(new TextSecureAttachmentStream(is, contentType, part.getDataSize()));
|
||||
attachments.add(new TextSecureAttachmentStream(is, contentType, part.getDataSize(), new ProgressListener() {
|
||||
@Override public void onAttachmentProgress(long total, long progress) {
|
||||
EventBus.getDefault().postSticky(new PartProgressEvent(part.getPartId(), total, progress));
|
||||
}
|
||||
}));
|
||||
} catch (IOException ioe) {
|
||||
Log.w(TAG, "Couldn't open attachment", ioe);
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ public class ImageSlide extends Slide {
|
||||
|
||||
@Override
|
||||
public Uri getThumbnailUri() {
|
||||
if (!getPart().isPendingPush() && getPart().getDataUri() != null) {
|
||||
if (getPart().getDataUri() != null) {
|
||||
return isDraft()
|
||||
? getPart().getDataUri()
|
||||
: PartAuthority.getThumbnailUri(getPart().getPartId());
|
||||
|
@ -80,7 +80,7 @@ public class IncomingMediaMessage {
|
||||
media.setName(Util.toIsoBytes(relay.get()));
|
||||
}
|
||||
|
||||
media.setPendingPush(true);
|
||||
media.setInProgress(true);
|
||||
|
||||
this.body.addPart(media);
|
||||
}
|
||||
|
@ -85,7 +85,6 @@ public class OutgoingMediaMessage {
|
||||
media.setContentType(Util.toIsoBytes(attachment.getContentType()));
|
||||
media.setContentLocation(Util.toIsoBytes(String.valueOf(attachment.asPointer().getId())));
|
||||
media.setContentDisposition(Util.toIsoBytes(Base64.encodeBytes(encryptedKey)));
|
||||
media.setPendingPush(true);
|
||||
|
||||
body.addPart(media);
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ import android.content.res.Resources.Theme;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
@ -75,6 +74,10 @@ public abstract class Slide {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isInProgress() {
|
||||
return part.isInProgress();
|
||||
}
|
||||
|
||||
public @DrawableRes int getPlaceholderRes(Theme theme) {
|
||||
throw new AssertionError("getPlaceholderRes() called for non-drawable slide");
|
||||
}
|
||||
@ -108,6 +111,7 @@ public abstract class Slide {
|
||||
this.hasImage() == that.hasImage() &&
|
||||
this.hasVideo() == that.hasVideo() &&
|
||||
this.isDraft() == that.isDraft() &&
|
||||
this.isInProgress() == that.isInProgress() &&
|
||||
Util.equals(this.getUri(), that.getUri()) &&
|
||||
Util.equals(this.getThumbnailUri(), that.getThumbnailUri());
|
||||
}
|
||||
|
@ -65,7 +65,8 @@ public class SlideDeck {
|
||||
PduBody body = new PduBody();
|
||||
|
||||
for (Slide slide : slides) {
|
||||
body.addPart(slide.getPart());
|
||||
PduPart part = slide.getPart();
|
||||
body.addPart(part);
|
||||
}
|
||||
|
||||
return body;
|
||||
|
@ -1,48 +0,0 @@
|
||||
package org.thoughtcrime.securesms.mms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
|
||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
|
||||
import com.bumptech.glide.load.resource.bitmap.TransformationUtils;
|
||||
|
||||
public class ThumbnailTransform extends BitmapTransformation {
|
||||
private static final String TAG = ThumbnailTransform.class.getSimpleName();
|
||||
|
||||
public ThumbnailTransform(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public ThumbnailTransform(BitmapPool bitmapPool) {
|
||||
super(bitmapPool);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
|
||||
if (toTransform.getWidth() < (outWidth / 2) && toTransform.getHeight() < (outHeight / 2)) {
|
||||
return toTransform;
|
||||
}
|
||||
|
||||
final float inAspectRatio = (float) toTransform.getWidth() / toTransform.getHeight();
|
||||
final float outAspectRatio = (float) outWidth / outHeight;
|
||||
if (inAspectRatio < outAspectRatio) {
|
||||
outWidth = (int)(outHeight * inAspectRatio);
|
||||
}
|
||||
|
||||
final Bitmap toReuse = pool.get(outWidth, outHeight, toTransform.getConfig() != null
|
||||
? toTransform.getConfig()
|
||||
: Bitmap.Config.ARGB_8888);
|
||||
Bitmap transformed = TransformationUtils.centerCrop(toReuse, toTransform, outWidth, outHeight);
|
||||
if (toReuse != null && toReuse != transformed && !pool.put(toReuse)) {
|
||||
toReuse.recycle();
|
||||
}
|
||||
return transformed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ThumbnailTransform.class.getCanonicalName();
|
||||
}
|
||||
}
|
@ -43,7 +43,7 @@ public class PduBody {
|
||||
|
||||
public boolean containsPushInProgress() {
|
||||
for (int i=0;i<getPartsNum();i++) {
|
||||
if (getPart(i).isPendingPush()) {
|
||||
if (getPart(i).isInProgress()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -135,7 +135,7 @@ public class PduPart {
|
||||
private long rowId = -1;
|
||||
private long uniqueId = -1;
|
||||
private boolean isEncrypted;
|
||||
private boolean isPendingPush;
|
||||
private boolean isInProgress;
|
||||
private long dataSize;
|
||||
private Bitmap thumbnail;
|
||||
|
||||
@ -164,12 +164,12 @@ public class PduPart {
|
||||
}
|
||||
|
||||
|
||||
public void setPendingPush(boolean isPendingPush) {
|
||||
this.isPendingPush = isPendingPush;
|
||||
public void setInProgress(boolean isInProgress) {
|
||||
this.isInProgress = isInProgress;
|
||||
}
|
||||
|
||||
public boolean isPendingPush() {
|
||||
return isPendingPush;
|
||||
public boolean isInProgress() {
|
||||
return isInProgress;
|
||||
}
|
||||
|
||||
/**
|
||||
|