diff --git a/build.gradle b/build.gradle
index 516f4d375..422641c57 100644
--- a/build.gradle
+++ b/build.gradle
@@ -79,6 +79,7 @@ dependencies {
compile 'org.whispersystems:libpastelog:1.0.6'
compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
compile 'org.whispersystems:textsecure-android:1.6.2'
+ compile 'com.h6ah4i.android.compat:mulsellistprefcompat:1.0.0'
androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
diff --git a/res/drawable-hdpi/ic_file_download_white_36dp.png b/res/drawable-hdpi/ic_file_download_white_36dp.png
new file mode 100644
index 000000000..9f23ce8ee
Binary files /dev/null and b/res/drawable-hdpi/ic_file_download_white_36dp.png differ
diff --git a/res/drawable-hdpi/ic_forum_black_32dp.png b/res/drawable-hdpi/ic_forum_black_32dp.png
new file mode 100644
index 000000000..4911236d4
Binary files /dev/null and b/res/drawable-hdpi/ic_forum_black_32dp.png differ
diff --git a/res/drawable-hdpi/ic_forum_grey_32dp.png b/res/drawable-hdpi/ic_forum_grey_32dp.png
new file mode 100644
index 000000000..37c69a18a
Binary files /dev/null and b/res/drawable-hdpi/ic_forum_grey_32dp.png differ
diff --git a/res/drawable-hdpi/ic_textsms_black_32dp.png b/res/drawable-hdpi/ic_textsms_black_32dp.png
new file mode 100644
index 000000000..091c4487a
Binary files /dev/null and b/res/drawable-hdpi/ic_textsms_black_32dp.png differ
diff --git a/res/drawable-hdpi/ic_textsms_grey_32dp.png b/res/drawable-hdpi/ic_textsms_grey_32dp.png
new file mode 100644
index 000000000..6882ef608
Binary files /dev/null and b/res/drawable-hdpi/ic_textsms_grey_32dp.png differ
diff --git a/res/drawable-mdpi/ic_file_download_white_36dp.png b/res/drawable-mdpi/ic_file_download_white_36dp.png
new file mode 100644
index 000000000..6c2665d24
Binary files /dev/null and b/res/drawable-mdpi/ic_file_download_white_36dp.png differ
diff --git a/res/drawable-mdpi/ic_forum_black_32dp.png b/res/drawable-mdpi/ic_forum_black_32dp.png
new file mode 100644
index 000000000..7840302c1
Binary files /dev/null and b/res/drawable-mdpi/ic_forum_black_32dp.png differ
diff --git a/res/drawable-mdpi/ic_forum_grey_32dp.png b/res/drawable-mdpi/ic_forum_grey_32dp.png
new file mode 100644
index 000000000..00b00cea1
Binary files /dev/null and b/res/drawable-mdpi/ic_forum_grey_32dp.png differ
diff --git a/res/drawable-mdpi/ic_textsms_black_32dp.png b/res/drawable-mdpi/ic_textsms_black_32dp.png
new file mode 100644
index 000000000..efb482e66
Binary files /dev/null and b/res/drawable-mdpi/ic_textsms_black_32dp.png differ
diff --git a/res/drawable-mdpi/ic_textsms_grey_32dp.png b/res/drawable-mdpi/ic_textsms_grey_32dp.png
new file mode 100644
index 000000000..bad115a42
Binary files /dev/null and b/res/drawable-mdpi/ic_textsms_grey_32dp.png differ
diff --git a/res/drawable-xhdpi/ic_file_download_white_36dp.png b/res/drawable-xhdpi/ic_file_download_white_36dp.png
new file mode 100644
index 000000000..d508aa948
Binary files /dev/null and b/res/drawable-xhdpi/ic_file_download_white_36dp.png differ
diff --git a/res/drawable-xhdpi/ic_forum_black_32dp.png b/res/drawable-xhdpi/ic_forum_black_32dp.png
new file mode 100644
index 000000000..2048b9c1e
Binary files /dev/null and b/res/drawable-xhdpi/ic_forum_black_32dp.png differ
diff --git a/res/drawable-xhdpi/ic_forum_grey_32dp.png b/res/drawable-xhdpi/ic_forum_grey_32dp.png
new file mode 100644
index 000000000..a9981ee98
Binary files /dev/null and b/res/drawable-xhdpi/ic_forum_grey_32dp.png differ
diff --git a/res/drawable-xhdpi/ic_textsms_black_32dp.png b/res/drawable-xhdpi/ic_textsms_black_32dp.png
new file mode 100644
index 000000000..ef8cfe04f
Binary files /dev/null and b/res/drawable-xhdpi/ic_textsms_black_32dp.png differ
diff --git a/res/drawable-xhdpi/ic_textsms_grey_32dp.png b/res/drawable-xhdpi/ic_textsms_grey_32dp.png
new file mode 100644
index 000000000..8bf0991bd
Binary files /dev/null and b/res/drawable-xhdpi/ic_textsms_grey_32dp.png differ
diff --git a/res/drawable-xxhdpi/ic_file_download_white_36dp.png b/res/drawable-xxhdpi/ic_file_download_white_36dp.png
new file mode 100644
index 000000000..30b762d70
Binary files /dev/null and b/res/drawable-xxhdpi/ic_file_download_white_36dp.png differ
diff --git a/res/drawable-xxhdpi/ic_forum_black_32dp.png b/res/drawable-xxhdpi/ic_forum_black_32dp.png
new file mode 100644
index 000000000..22468201a
Binary files /dev/null and b/res/drawable-xxhdpi/ic_forum_black_32dp.png differ
diff --git a/res/drawable-xxhdpi/ic_forum_grey_32dp.png b/res/drawable-xxhdpi/ic_forum_grey_32dp.png
new file mode 100644
index 000000000..07031d106
Binary files /dev/null and b/res/drawable-xxhdpi/ic_forum_grey_32dp.png differ
diff --git a/res/drawable-xxhdpi/ic_textsms_black_32dp.png b/res/drawable-xxhdpi/ic_textsms_black_32dp.png
new file mode 100644
index 000000000..0b8b2b0f7
Binary files /dev/null and b/res/drawable-xxhdpi/ic_textsms_black_32dp.png differ
diff --git a/res/drawable-xxhdpi/ic_textsms_grey_32dp.png b/res/drawable-xxhdpi/ic_textsms_grey_32dp.png
new file mode 100644
index 000000000..ffb8a5a70
Binary files /dev/null and b/res/drawable-xxhdpi/ic_textsms_grey_32dp.png differ
diff --git a/res/drawable-xxxhdpi/ic_file_download_white_36dp.png b/res/drawable-xxxhdpi/ic_file_download_white_36dp.png
new file mode 100644
index 000000000..e261f9a87
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_file_download_white_36dp.png differ
diff --git a/res/drawable-xxxhdpi/ic_forum_black_32dp.png b/res/drawable-xxxhdpi/ic_forum_black_32dp.png
new file mode 100644
index 000000000..8063a274f
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_forum_black_32dp.png differ
diff --git a/res/drawable-xxxhdpi/ic_forum_grey_32dp.png b/res/drawable-xxxhdpi/ic_forum_grey_32dp.png
new file mode 100644
index 000000000..008345bbc
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_forum_grey_32dp.png differ
diff --git a/res/drawable-xxxhdpi/ic_textsms_black_32dp.png b/res/drawable-xxxhdpi/ic_textsms_black_32dp.png
new file mode 100644
index 000000000..c0712d0bc
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_textsms_black_32dp.png differ
diff --git a/res/drawable-xxxhdpi/ic_textsms_grey_32dp.png b/res/drawable-xxxhdpi/ic_textsms_grey_32dp.png
new file mode 100644
index 000000000..f47cc38ad
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_textsms_grey_32dp.png differ
diff --git a/res/layout/thumbnail_view.xml b/res/layout/thumbnail_view.xml
index ca2d0cf81..80590293d 100644
--- a/res/layout/thumbnail_view.xml
+++ b/res/layout/thumbnail_view.xml
@@ -20,4 +20,12 @@
android:layout_height="wrap_content"
android:layout_gravity="top|right"
android:layout="@layout/thumbnail_view_remove_button" />
+
+
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 701596684..1461817f1 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -193,4 +193,29 @@
- contact
- none
+
+
+
+ - image
+ - audio
+ - video
+
+
+
+ - @string/arrays__images
+ - @string/arrays__audio
+ - @string/arrays__video
+
+
+
+ - image
+
+
+
+ - image
+ - audio
+ - video
+
+
+
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index b59dd40a1..cbada251d 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -106,6 +106,7 @@
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5df9a2cf1..296ce0b58 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -762,6 +762,10 @@
Name only
Neither
+ Images
+ Audio
+ Video
+
- %d hour
@@ -847,6 +851,7 @@
Request a delivery report for each SMS message you send
Automatically delete older messages once a conversation thread exceeds a specified length
Delete old messages
+ Chats and media
Conversation length limit
Trim all threads now
Scan through all conversation threads and enforce conversation length limits
diff --git a/res/values/themes.xml b/res/values/themes.xml
index 26b8ed626..18f90be17 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -169,11 +169,11 @@
- #ff1d85d7
- - @drawable/ic_message_black
+ - @drawable/ic_textsms_black_32dp
- @drawable/ic_notifications_black
- @drawable/ic_app_protection_black
- @drawable/ic_brightness_6_black
- - @drawable/ic_delete_black
+ - @drawable/ic_forum_black_32dp
- @drawable/ic_devices_black_48dp
- @drawable/ic_advanced_black
@@ -281,11 +281,11 @@
- @color/textsecure_primary_dark
- - @drawable/ic_message_gray
+ - @drawable/ic_textsms_grey_32dp
- @drawable/ic_notifications_gray
- @drawable/ic_app_protection_gray
- @drawable/ic_brightness_6_gray
- - @drawable/ic_delete_gray
+ - @drawable/ic_forum_grey_32dp
- @drawable/ic_devices_grey600_48dp
- @drawable/ic_advanced_gray
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
index 79ba26004..459324a05 100644
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -17,9 +17,9 @@
android:title="@string/preferences__appearance"
android:icon="?pref_ic_appearance"/>
-
+
diff --git a/res/xml/preferences_chats.xml b/res/xml/preferences_chats.xml
new file mode 100644
index 000000000..ec5e4c70b
--- /dev/null
+++ b/res/xml/preferences_chats.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/xml/preferences_storage.xml b/res/xml/preferences_storage.xml
deleted file mode 100644
index 83a1d88cc..000000000
--- a/res/xml/preferences_storage.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java
index 97565aa64..2eeb5bd21 100644
--- a/src/org/thoughtcrime/securesms/ApplicationContext.java
+++ b/src/org/thoughtcrime/securesms/ApplicationContext.java
@@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.dependencies.TextSecureCommunicationModule;
import org.thoughtcrime.securesms.jobs.GcmRefreshJob;
import org.thoughtcrime.securesms.jobs.persistence.EncryptingJobSerializer;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirementProvider;
+import org.thoughtcrime.securesms.jobs.requirements.MediaNetworkRequirementProvider;
import org.thoughtcrime.securesms.jobs.requirements.ServiceRequirementProvider;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.jobqueue.JobManager;
@@ -49,9 +50,11 @@ import dagger.ObjectGraph;
*/
public class ApplicationContext extends Application implements DependencyInjector {
- private JobManager jobManager;
+ private JobManager jobManager;
private ObjectGraph objectGraph;
+ private MediaNetworkRequirementProvider mediaNetworkRequirementProvider = new MediaNetworkRequirementProvider();
+
public static ApplicationContext getInstance(Context context) {
return (ApplicationContext)context.getApplicationContext();
}
@@ -103,11 +106,16 @@ public class ApplicationContext extends Application implements DependencyInjecto
.withJobSerializer(new EncryptingJobSerializer())
.withRequirementProviders(new MasterSecretRequirementProvider(this),
new ServiceRequirementProvider(this),
- new NetworkRequirementProvider(this))
+ new NetworkRequirementProvider(this),
+ mediaNetworkRequirementProvider)
.withConsumerThreads(5)
.build();
}
+ public void notifyMediaControlEvent() {
+ mediaNetworkRequirementProvider.notifyMediaControlEvent();
+ }
+
private void initializeDependencyInjection() {
this.objectGraph = ObjectGraph.create(new TextSecureCommunicationModule(this),
new AxolotlStorageModule(this));
diff --git a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java
index 1ec02f886..d9866ea00 100644
--- a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java
+++ b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java
@@ -32,7 +32,7 @@ import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
import org.thoughtcrime.securesms.preferences.AppearancePreferenceFragment;
import org.thoughtcrime.securesms.preferences.NotificationsPreferenceFragment;
import org.thoughtcrime.securesms.preferences.SmsMmsPreferenceFragment;
-import org.thoughtcrime.securesms.preferences.StoragePreferenceFragment;
+import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
@@ -54,7 +54,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
private static final String PREFERENCE_CATEGORY_NOTIFICATIONS = "preference_category_notifications";
private static final String PREFERENCE_CATEGORY_APP_PROTECTION = "preference_category_app_protection";
private static final String PREFERENCE_CATEGORY_APPEARANCE = "preference_category_appearance";
- private static final String PREFERENCE_CATEGORY_STORAGE = "preference_category_storage";
+ private static final String PREFERENCE_CATEGORY_CHATS = "preference_category_chats";
private static final String PREFERENCE_CATEGORY_DEVICES = "preference_category_devices";
private static final String PREFERENCE_CATEGORY_ADVANCED = "preference_category_advanced";
@@ -130,8 +130,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
.setOnPreferenceClickListener(new CategoryClickListener(masterSecret, PREFERENCE_CATEGORY_APP_PROTECTION));
this.findPreference(PREFERENCE_CATEGORY_APPEARANCE)
.setOnPreferenceClickListener(new CategoryClickListener(masterSecret, PREFERENCE_CATEGORY_APPEARANCE));
- this.findPreference(PREFERENCE_CATEGORY_STORAGE)
- .setOnPreferenceClickListener(new CategoryClickListener(masterSecret, PREFERENCE_CATEGORY_STORAGE));
+ this.findPreference(PREFERENCE_CATEGORY_CHATS)
+ .setOnPreferenceClickListener(new CategoryClickListener(masterSecret, PREFERENCE_CATEGORY_CHATS));
// this.findPreference(PREFERENCE_CATEGORY_DEVICES)
// .setOnPreferenceClickListener(new CategoryClickListener(masterSecret, PREFERENCE_CATEGORY_DEVICES));
this.findPreference(PREFERENCE_CATEGORY_ADVANCED)
@@ -154,8 +154,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
.setSummary(AppProtectionPreferenceFragment.getSummary(getActivity()));
this.findPreference(PREFERENCE_CATEGORY_APPEARANCE)
.setSummary(AppearancePreferenceFragment.getSummary(getActivity()));
- this.findPreference(PREFERENCE_CATEGORY_STORAGE)
- .setSummary(StoragePreferenceFragment.getSummary(getActivity()));
+ this.findPreference(PREFERENCE_CATEGORY_CHATS)
+ .setSummary(ChatsPreferenceFragment.getSummary(getActivity()));
}
private class CategoryClickListener implements Preference.OnPreferenceClickListener {
@@ -184,8 +184,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
case PREFERENCE_CATEGORY_APPEARANCE:
fragment = new AppearancePreferenceFragment();
break;
- case PREFERENCE_CATEGORY_STORAGE:
- fragment = new StoragePreferenceFragment();
+ case PREFERENCE_CATEGORY_CHATS:
+ fragment = new ChatsPreferenceFragment();
break;
case PREFERENCE_CATEGORY_DEVICES:
Intent intent = new Intent(getActivity(), DeviceListActivity.class);
diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java
index f7cad6b2f..408fbcd1d 100644
--- a/src/org/thoughtcrime/securesms/ConversationItem.java
+++ b/src/org/thoughtcrime/securesms/ConversationItem.java
@@ -47,6 +47,7 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
+import org.thoughtcrime.securesms.database.PartDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
@@ -156,6 +157,7 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien
if (mediaThumbnail != null) {
mediaThumbnail.setThumbnailClickListener(new ThumbnailClickListener());
mediaThumbnail.setOnLongClickListener(new MultiSelectLongClickListener());
+ mediaThumbnail.setDownloadClickListener(new ThumbnailDownloadClickListener());
}
}
@@ -275,7 +277,7 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien
mediaThumbnail.setImageResource(masterSecret, messageRecord.getId(),
messageRecord.getDateReceived(),
((MediaMmsMessageRecord)messageRecord).getSlideDeckFuture());
- mediaThumbnail.setShowProgress(!messageRecord.isFailed() && (!messageRecord.isOutgoing() || messageRecord.isPending()));
+ mediaThumbnail.hideControls(messageRecord.isFailed() || (messageRecord.isOutgoing() && !messageRecord.isPending()));
bodyText.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
} else {
mediaThumbnail.setVisibility(View.GONE);
@@ -409,6 +411,11 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien
}
}
+ private class ThumbnailDownloadClickListener implements ThumbnailView.ThumbnailClickListener {
+ @Override public void onClick(View v, Slide slide) {
+ DatabaseFactory.getPartDatabase(context).setTransferState(messageRecord.getId(), slide.getPart().getPartId(), PartDatabase.TRANSFER_PROGRESS_STARTED);
+ }
+ }
private class ThumbnailClickListener implements ThumbnailView.ThumbnailClickListener {
private void fireIntent(Slide slide) {
Log.w(TAG, "Clicked: " + slide.getUri() + " , " + slide.getContentType());
diff --git a/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java b/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java
index f149d60f7..cbc09e40d 100644
--- a/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java
+++ b/src/org/thoughtcrime/securesms/DatabaseUpgradeActivity.java
@@ -32,7 +32,11 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore;
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
import org.thoughtcrime.securesms.database.DatabaseFactory;
+import org.thoughtcrime.securesms.database.MmsDatabase;
+import org.thoughtcrime.securesms.database.MmsDatabase.Reader;
import org.thoughtcrime.securesms.database.PushDatabase;
+import org.thoughtcrime.securesms.database.model.MessageRecord;
+import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
import org.thoughtcrime.securesms.jobs.PushDecryptJob;
@@ -41,10 +45,14 @@ import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.VersionTracker;
import java.io.File;
+import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
+import ws.com.google.android.mms.pdu.PduPart;
+
public class DatabaseUpgradeActivity extends BaseActivity {
+ private static final String TAG = DatabaseUpgradeActivity.class.getSimpleName();
public static final int NO_MORE_KEY_EXCHANGE_PREFIX_VERSION = 46;
public static final int MMS_BODY_VERSION = 46;
@@ -57,6 +65,7 @@ public class DatabaseUpgradeActivity extends BaseActivity {
public static final int PUSH_DECRYPT_SERIAL_ID_VERSION = 131;
public static final int MIGRATE_SESSION_PLAINTEXT = 136;
public static final int CONTACTS_ACCOUNT_VERSION = 136;
+ public static final int MEDIA_DOWNLOAD_CONTROLS_VERSION = 146;
private static final SortedSet UPGRADE_VERSIONS = new TreeSet() {{
add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION);
@@ -68,6 +77,7 @@ public class DatabaseUpgradeActivity extends BaseActivity {
add(NO_DECRYPT_QUEUE_VERSION);
add(PUSH_DECRYPT_SERIAL_ID_VERSION);
add(MIGRATE_SESSION_PLAINTEXT);
+ add(MEDIA_DOWNLOAD_CONTROLS_VERSION);
}};
private MasterSecret masterSecret;
@@ -205,9 +215,32 @@ public class DatabaseUpgradeActivity extends BaseActivity {
.add(new DirectoryRefreshJob(getApplicationContext()));
}
+ if (params[0] < MEDIA_DOWNLOAD_CONTROLS_VERSION) {
+ schedulePendingIncomingParts(context);
+ }
+
return null;
}
+ private void schedulePendingIncomingParts(Context context) {
+ MmsDatabase db = DatabaseFactory.getMmsDatabase(context);
+ List pendingParts = DatabaseFactory.getPartDatabase(context).getPendingParts();
+
+ Log.w(TAG, pendingParts.size() + " pending parts.");
+ for (PduPart part : pendingParts) {
+ final Reader reader = db.readerFor(masterSecret, db.getMessage(part.getMmsId()));
+ final MessageRecord record = reader.getNext();
+
+ if (record != null && !record.isOutgoing() && record.isPush()) {
+ Log.w(TAG, "queuing new attachment download job for incoming push part.");
+ ApplicationContext.getInstance(context)
+ .getJobManager()
+ .add(new AttachmentDownloadJob(context, part.getMmsId(), part.getPartId()));
+ }
+ reader.close();
+ }
+ }
+
private void scheduleMessagesInPushDatabase(Context context) {
PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(context);
Cursor pushReader = null;
diff --git a/src/org/thoughtcrime/securesms/components/ThumbnailView.java b/src/org/thoughtcrime/securesms/components/ThumbnailView.java
index 3931d29c4..34ebf5e22 100644
--- a/src/org/thoughtcrime/securesms/components/ThumbnailView.java
+++ b/src/org/thoughtcrime/securesms/components/ThumbnailView.java
@@ -31,6 +31,7 @@ import com.pnikosis.materialishprogress.ProgressWheel;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.database.PartDatabase;
import org.thoughtcrime.securesms.jobs.PartProgressEvent;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
import org.thoughtcrime.securesms.mms.RoundedCorners;
@@ -47,16 +48,18 @@ import ws.com.google.android.mms.pdu.PduPart;
public class ThumbnailView extends FrameLayout {
private static final String TAG = ThumbnailView.class.getSimpleName();
- private boolean showProgress = true;
+ private boolean hideControls;
private ImageView image;
private ProgressWheel progress;
private ImageView removeButton;
+ private ImageButton downloadButton;
private int backgroundColorHint;
private int radius;
private ListenableFutureTask slideDeckFuture = null;
private SlideDeckListener slideDeckListener = null;
private ThumbnailClickListener thumbnailClickListener = null;
+ private ThumbnailClickListener downloadClickListener = null;
private String slideId = null;
private Slide slide = null;
@@ -68,11 +71,13 @@ public class ThumbnailView extends FrameLayout {
this(context, attrs, 0);
}
- public ThumbnailView(Context context, AttributeSet attrs, int defStyle) {
+ public ThumbnailView(final Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
inflate(context, R.layout.thumbnail_view, this);
- radius = getResources().getDimensionPixelSize(R.dimen.message_bubble_corner_radius);
- image = (ImageView) findViewById(R.id.thumbnail_image);
+ radius = getResources().getDimensionPixelSize(R.dimen.message_bubble_corner_radius);
+ image = (ImageView) findViewById(R.id.thumbnail_image);
+ progress = (ProgressWheel) findViewById(R.id.progress_wheel);
+ downloadButton = (ImageButton) findViewById(R.id.download_button);
if (attrs != null) {
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ThumbnailView, 0, 0);
@@ -162,15 +167,24 @@ public class ThumbnailView extends FrameLayout {
return;
}
- this.slide = slide;
- if (slide.isInProgress() && showProgress) {
+ if (!hideControls && slide.getTransferProgress() == PartDatabase.TRANSFER_PROGRESS_STARTED) {
getProgressWheel().spin();
getProgressWheel().setVisibility(VISIBLE);
+ downloadButton.setVisibility(GONE);
+ } else if (!hideControls && slide.getTransferProgress() == PartDatabase.TRANSFER_PROGRESS_AUTO_PENDING ||
+ slide.getTransferProgress() == PartDatabase.TRANSFER_PROGRESS_FAILED)
+ {
+ hideProgressWheel();
+ downloadButton.setVisibility(VISIBLE);
} else {
hideProgressWheel();
+ downloadButton.setVisibility(GONE);
}
+
+ this.slide = slide;
buildGlideRequest(slide, masterSecret).into(image);
setOnClickListener(new ThumbnailClickDispatcher(thumbnailClickListener, slide));
+ downloadButton.setOnClickListener(new ThumbnailClickDispatcher(downloadClickListener, slide));
}
public void setThumbnailClickListener(ThumbnailClickListener listener) {
@@ -181,15 +195,17 @@ public class ThumbnailView extends FrameLayout {
getRemoveButton().setOnClickListener(listener);
}
+ public void setDownloadClickListener(ThumbnailClickListener listener) {
+ this.downloadClickListener = listener;
+ }
+
public void clear() {
if (isContextValid()) Glide.clear(this);
}
- public void setShowProgress(boolean showProgress) {
- this.showProgress = showProgress;
- if (progress != null && progress.getVisibility() == View.VISIBLE && !showProgress) {
- animateOutProgress();
- }
+ public void hideControls(boolean hideControls) {
+ this.hideControls = hideControls;
+ if (hideControls) hideProgressWheel();
}
@TargetApi(VERSION_CODES.JELLY_BEAN_MR1)
@@ -202,7 +218,6 @@ public class ThumbnailView extends FrameLayout {
private GenericRequestBuilder buildGlideRequest(@NonNull Slide slide,
@Nullable MasterSecret masterSecret)
{
- Log.w(TAG, "slide type " + slide.getContentType());
final GenericRequestBuilder builder;
if (slide.getThumbnailUri() != null) {
builder = buildThumbnailGlideRequest(slide, masterSecret);
@@ -210,7 +225,7 @@ public class ThumbnailView extends FrameLayout {
builder = buildPlaceholderGlideRequest(slide);
}
- if (slide.isInProgress() && showProgress) {
+ if (slide.isInProgress() && !hideControls) {
return builder;
} else {
return builder.error(R.drawable.ic_missing_thumbnail_picture);
diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java
index 8a269b86f..37349524e 100644
--- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java
@@ -752,7 +752,7 @@ public class MmsDatabase extends MessagingDatabase {
if (sendRequest.getBody() != null) {
for (int i = 0; i < sendRequest.getBody().getPartsNum(); i++) {
- sendRequest.getBody().getPart(i).setInProgress(true);
+ sendRequest.getBody().getPart(i).setTransferProgress(PartDatabase.TRANSFER_PROGRESS_STARTED);
}
}
diff --git a/src/org/thoughtcrime/securesms/database/PartDatabase.java b/src/org/thoughtcrime/securesms/database/PartDatabase.java
index d523e694e..17d40fc60 100644
--- a/src/org/thoughtcrime/securesms/database/PartDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/PartDatabase.java
@@ -23,14 +23,18 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;
import android.net.Uri;
+import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
+import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.EncryptingPartOutputStream;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
+import org.thoughtcrime.securesms.jobs.requirements.MediaNetworkRequirement;
+import org.thoughtcrime.securesms.jobs.requirements.MediaNetworkRequirementProvider;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.MediaUtil.ThumbnailData;
@@ -43,12 +47,14 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
+import de.greenrobot.event.EventBus;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.PduBody;
@@ -72,12 +78,17 @@ public class PartDatabase extends Database {
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 TRANSFER_STATE = "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";
+ public static final int TRANSFER_PROGRESS_DONE = 0;
+ public static final int TRANSFER_PROGRESS_STARTED = 1;
+ public static final int TRANSFER_PROGRESS_AUTO_PENDING = 2;
+ public static final int TRANSFER_PROGRESS_FAILED = 3;
+
private static final String PART_ID_WHERE = ROW_ID + " = ? AND " + UNIQUE_ID + " = ?";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ROW_ID + " INTEGER PRIMARY KEY, " +
@@ -86,12 +97,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, " +
- IN_PROGRESS + " INTEGER, "+ DATA + " TEXT, " + SIZE + " INTEGER, " +
+ TRANSFER_STATE + " 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 + " (" + IN_PROGRESS + ");",
+ "CREATE INDEX IF NOT EXISTS pending_push_index ON " + TABLE_NAME + " (" + TRANSFER_STATE + ");",
};
private final static String IMAGES_QUERY = "SELECT " + TABLE_NAME + "." + ROW_ID + ", "
@@ -127,7 +138,7 @@ public class PartDatabase extends Database {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
part.setContentDisposition(new byte[0]);
- part.setInProgress(false);
+ part.setTransferProgress(TRANSFER_PROGRESS_FAILED);
ContentValues values = getContentValuesForPart(part);
@@ -223,6 +234,7 @@ public class PartDatabase extends Database {
}
void insertParts(MasterSecretUnion masterSecret, long mmsId, PduBody body) throws MmsException {
+ Log.w(TAG, "insertParts(" + body.getPartsNum() + ")");
for (int i=0;i getPendingParts() {
+ final SQLiteDatabase database = databaseHelper.getReadableDatabase();
+ final List parts = new LinkedList<>();
+
+ Cursor cursor = null;
+ try {
+ cursor = database.query(TABLE_NAME, null, TRANSFER_STATE + " = ?", new String[] {String.valueOf(TRANSFER_PROGRESS_STARTED)}, null, null, null);
+ while (cursor != null && cursor.moveToNext()) {
+ parts.add(getPart(cursor));
+ }
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+
+ return parts;
+ }
+
private PartId insertPart(MasterSecretUnion masterSecret, PduPart part, long mmsId, Bitmap thumbnail) throws MmsException {
Log.w(TAG, "inserting part to mms " + mmsId);
SQLiteDatabase database = databaseHelper.getWritableDatabase();
@@ -472,7 +501,7 @@ public class PartDatabase extends Database {
Pair partData = writePartData(masterSecret, part, data);
part.setContentDisposition(new byte[0]);
- part.setInProgress(false);
+ part.setTransferProgress(TRANSFER_PROGRESS_DONE);
ContentValues values = getContentValuesForPart(part);
@@ -492,13 +521,23 @@ public class PartDatabase extends Database {
ContentValues values = new ContentValues(1);
SQLiteDatabase database = databaseHelper.getWritableDatabase();
- part.setInProgress(false);
- values.put(IN_PROGRESS, false);
+ part.setTransferProgress(TRANSFER_PROGRESS_DONE);
+ values.put(TRANSFER_STATE, TRANSFER_PROGRESS_DONE);
database.update(TABLE_NAME, values, PART_ID_WHERE, part.getPartId().toStrings());
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId));
}
+ public void setTransferState(long messageId, @NonNull PartId partId, int transferState) {
+ final ContentValues values = new ContentValues(1);
+ final SQLiteDatabase database = databaseHelper.getWritableDatabase();
+
+ values.put(TRANSFER_STATE, transferState);
+ database.update(TABLE_NAME, values, PART_ID_WHERE, partId.toStrings());
+ notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId));
+ ApplicationContext.getInstance(context).notifyMediaControlEvent();
+ }
+
public void updatePartData(MasterSecret masterSecret, PduPart part, InputStream data)
throws MmsException
{
diff --git a/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java
index 72293e567..a6b45afc0 100644
--- a/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java
@@ -12,6 +12,7 @@ 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.jobs.requirements.MediaNetworkRequirement;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.jobqueue.JobParameters;
@@ -26,7 +27,6 @@ import org.whispersystems.textsecure.api.push.exceptions.PushNetworkException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
-import java.util.List;
import javax.inject.Inject;
@@ -35,50 +35,49 @@ import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.PduPart;
public class AttachmentDownloadJob extends MasterSecretJob implements InjectableType {
-
- private static final String TAG = AttachmentDownloadJob.class.getSimpleName();
+ private static final long serialVersionUID = 1L;
+ private static final String TAG = AttachmentDownloadJob.class.getSimpleName();
@Inject transient TextSecureMessageReceiver messageReceiver;
private final long messageId;
+ private final long partRowId;
+ private final long partUniqueId;
- public AttachmentDownloadJob(Context context, long messageId) {
+ public AttachmentDownloadJob(Context context, long messageId, PartId partId) {
super(context, JobParameters.newBuilder()
+ .withGroupId(AttachmentDownloadJob.class.getCanonicalName())
.withRequirement(new MasterSecretRequirement(context))
.withRequirement(new NetworkRequirement(context))
+ .withRequirement(new MediaNetworkRequirement(context, messageId, partId))
.withPersistence()
.create());
- this.messageId = messageId;
+ this.messageId = messageId;
+ this.partRowId = partId.getRowId();
+ this.partUniqueId = partId.getUniqueId();
}
@Override
- public void onAdded() {}
+ public void onAdded() {
+ }
@Override
public void onRun(MasterSecret masterSecret) throws IOException {
- PartDatabase database = DatabaseFactory.getPartDatabase(context);
+ final PartId partId = new PartId(partRowId, partUniqueId);
+ final PduPart part = DatabaseFactory.getPartDatabase(context).getPart(partId);
- Log.w(TAG, "Downloading push parts for: " + messageId);
-
- List parts = database.getParts(messageId);
-
- for (PduPart part : parts) {
- retrievePart(masterSecret, part, messageId);
- Log.w(TAG, "Got part: " + part.getPartId());
- }
+ Log.w(TAG, "Downloading push part " + partId);
+ retrievePart(masterSecret, part, messageId);
MessageNotifier.updateNotification(context, masterSecret);
}
@Override
public void onCanceled() {
- PartDatabase database = DatabaseFactory.getPartDatabase(context);
- List parts = database.getParts(messageId);
-
- for (PduPart part : parts) {
- markFailed(messageId, part, part.getPartId());
- }
+ final PartId partId = new PartId(partRowId, partUniqueId);
+ final PduPart part = DatabaseFactory.getPartDatabase(context).getPart(partId);
+ markFailed(messageId, part, part.getPartId());
}
@Override
diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
index 5aef98659..6fefba422 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
@@ -58,9 +58,11 @@ import org.whispersystems.textsecure.api.messages.multidevice.SentTranscriptMess
import org.whispersystems.textsecure.api.messages.multidevice.TextSecureSyncMessage;
import org.whispersystems.textsecure.api.push.TextSecureAddress;
+import java.util.List;
import java.util.concurrent.TimeUnit;
import ws.com.google.android.mms.MmsException;
+import ws.com.google.android.mms.pdu.PduPart;
public class PushDecryptJob extends ContextJob {
@@ -252,11 +254,14 @@ public class PushDecryptJob extends ContextJob {
message.getGroupInfo(),
message.getAttachments());
- Pair messageAndThreadId = database.insertSecureDecryptedMessageInbox(masterSecret, mediaMessage, -1);
+ Pair messageAndThreadId = database.insertSecureDecryptedMessageInbox(masterSecret, mediaMessage, -1);
- ApplicationContext.getInstance(context)
- .getJobManager()
- .add(new AttachmentDownloadJob(context, messageAndThreadId.first));
+ List parts = DatabaseFactory.getPartDatabase(context).getParts(messageAndThreadId.first);
+ for (PduPart part : parts) {
+ ApplicationContext.getInstance(context)
+ .getJobManager()
+ .add(new AttachmentDownloadJob(context, messageAndThreadId.first, part.getPartId()));
+ }
if (smsMessageId.isPresent()) {
DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get());
@@ -284,9 +289,11 @@ public class PushDecryptJob extends ContextJob {
database.markAsSent(messageId, "push".getBytes(), 0);
database.markAsPush(messageId);
- ApplicationContext.getInstance(context)
- .getJobManager()
- .add(new AttachmentDownloadJob(context, messageId));
+ for (PduPart part : DatabaseFactory.getPartDatabase(context).getParts(messageId)) {
+ ApplicationContext.getInstance(context)
+ .getJobManager()
+ .add(new AttachmentDownloadJob(context, messageId, part.getPartId()));
+ }
if (smsMessageId.isPresent()) {
DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get());
diff --git a/src/org/thoughtcrime/securesms/jobs/requirements/MediaNetworkRequirement.java b/src/org/thoughtcrime/securesms/jobs/requirements/MediaNetworkRequirement.java
new file mode 100644
index 000000000..146ac2bfb
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/jobs/requirements/MediaNetworkRequirement.java
@@ -0,0 +1,99 @@
+package org.thoughtcrime.securesms.jobs.requirements;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+import org.thoughtcrime.securesms.database.DatabaseFactory;
+import org.thoughtcrime.securesms.database.PartDatabase;
+import org.thoughtcrime.securesms.database.PartDatabase.PartId;
+import org.thoughtcrime.securesms.util.MediaUtil;
+import org.thoughtcrime.securesms.util.ServiceUtil;
+import org.thoughtcrime.securesms.util.TextSecurePreferences;
+import org.whispersystems.jobqueue.dependencies.ContextDependent;
+import org.whispersystems.jobqueue.requirements.Requirement;
+
+import java.util.Collections;
+import java.util.Set;
+
+import ws.com.google.android.mms.pdu.PduPart;
+
+public class MediaNetworkRequirement implements Requirement, ContextDependent {
+ private static final long serialVersionUID = 0L;
+ private static final String TAG = MediaNetworkRequirement.class.getSimpleName();
+
+ private transient Context context;
+
+ private final long messageId;
+ private final long partRowId;
+ private final long partUniqueId;
+
+ public MediaNetworkRequirement(Context context, long messageId, PartId partId) {
+ this.context = context;
+ this.messageId = messageId;
+ this.partRowId = partId.getRowId();
+ this.partUniqueId = partId.getUniqueId();
+ }
+
+ @Override public void setContext(Context context) {
+ this.context = context;
+ }
+
+ private NetworkInfo getNetworkInfo() {
+ return ServiceUtil.getConnectivityManager(context).getActiveNetworkInfo();
+ }
+
+ public boolean isConnectedWifi() {
+ final NetworkInfo info = getNetworkInfo();
+ return info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_WIFI;
+ }
+
+ public boolean isConnectedMobile() {
+ final NetworkInfo info = getNetworkInfo();
+ return info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_MOBILE;
+ }
+
+ public boolean isConnectedRoaming() {
+ final NetworkInfo info = getNetworkInfo();
+ return info != null && info.isConnected() && info.isRoaming() && info.getType() == ConnectivityManager.TYPE_MOBILE;
+ }
+
+ private @NonNull Set getAllowedAutoDownloadTypes() {
+ if (isConnectedWifi()) {
+ return TextSecurePreferences.getWifiMediaDownloadAllowed(context);
+ } else if (isConnectedRoaming()) {
+ return TextSecurePreferences.getRoamingMediaDownloadAllowed(context);
+ } else if (isConnectedMobile()) {
+ return TextSecurePreferences.getMobileMediaDownloadAllowed(context);
+ } else {
+ return Collections.emptySet();
+ }
+ }
+
+ @Override
+ public boolean isPresent() {
+ final PartId partId = new PartId(partRowId, partUniqueId);
+ final PartDatabase db = DatabaseFactory.getPartDatabase(context);
+ final PduPart part = db.getPart(partId);
+ if (part == null) {
+ Log.w(TAG, "part was null");
+ return false;
+ }
+
+ Log.w(TAG, "part transfer progress is " + part.getTransferProgress());
+ switch (part.getTransferProgress()) {
+ case PartDatabase.TRANSFER_PROGRESS_STARTED:
+ return true;
+ case PartDatabase.TRANSFER_PROGRESS_AUTO_PENDING:
+ final Set allowedTypes = getAllowedAutoDownloadTypes();
+ final boolean isAllowed = allowedTypes.contains(MediaUtil.getDiscreteMimeType(part));
+
+ if (isAllowed) db.setTransferState(messageId, partId, PartDatabase.TRANSFER_PROGRESS_STARTED);
+ return isAllowed;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/jobs/requirements/MediaNetworkRequirementProvider.java b/src/org/thoughtcrime/securesms/jobs/requirements/MediaNetworkRequirementProvider.java
new file mode 100644
index 000000000..0019c461c
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/jobs/requirements/MediaNetworkRequirementProvider.java
@@ -0,0 +1,18 @@
+package org.thoughtcrime.securesms.jobs.requirements;
+
+import org.whispersystems.jobqueue.requirements.RequirementListener;
+import org.whispersystems.jobqueue.requirements.RequirementProvider;
+
+public class MediaNetworkRequirementProvider implements RequirementProvider {
+
+ private RequirementListener listener;
+
+ public void notifyMediaControlEvent() {
+ if (listener != null) listener.onRequirementStatusChanged();
+ }
+
+ @Override
+ public void setListener(RequirementListener listener) {
+ this.listener = listener;
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java
index 4659b647e..f5862e506 100644
--- a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java
+++ b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java
@@ -1,13 +1,11 @@
package org.thoughtcrime.securesms.mms;
import android.text.TextUtils;
+import android.util.Log;
-import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher;
-import org.thoughtcrime.securesms.crypto.MasterCipher;
-import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
import org.thoughtcrime.securesms.crypto.MediaKey;
-import org.thoughtcrime.securesms.util.Base64;
+import org.thoughtcrime.securesms.database.PartDatabase;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libaxolotl.util.guava.Optional;
@@ -83,7 +81,7 @@ public class IncomingMediaMessage {
media.setName(Util.toIsoBytes(relay.get()));
}
- media.setInProgress(true);
+ media.setTransferProgress(PartDatabase.TRANSFER_PROGRESS_AUTO_PENDING);
this.body.addPart(media);
}
diff --git a/src/org/thoughtcrime/securesms/mms/Slide.java b/src/org/thoughtcrime/securesms/mms/Slide.java
index 6eb5f1464..7f5288529 100644
--- a/src/org/thoughtcrime/securesms/mms/Slide.java
+++ b/src/org/thoughtcrime/securesms/mms/Slide.java
@@ -78,6 +78,10 @@ public abstract class Slide {
return part.isInProgress();
}
+ public long getTransferProgress() {
+ return part.getTransferProgress();
+ }
+
public @DrawableRes int getPlaceholderRes(Theme theme) {
throw new AssertionError("getPlaceholderRes() called for non-drawable slide");
}
@@ -111,7 +115,7 @@ public abstract class Slide {
this.hasImage() == that.hasImage() &&
this.hasVideo() == that.hasVideo() &&
this.isDraft() == that.isDraft() &&
- this.isInProgress() == that.isInProgress() &&
+ this.getTransferProgress() == that.getTransferProgress() &&
Util.equals(this.getUri(), that.getUri()) &&
Util.equals(this.getThumbnailUri(), that.getThumbnailUri());
}
@@ -119,7 +123,7 @@ public abstract class Slide {
@Override
public int hashCode() {
return Util.hashCode(getContentType(), hasAudio(), hasImage(),
- hasVideo(), isDraft(), getUri(), getThumbnailUri());
+ hasVideo(), isDraft(), getUri(), getThumbnailUri(), getTransferProgress());
}
diff --git a/src/org/thoughtcrime/securesms/preferences/ChatsPreferenceFragment.java b/src/org/thoughtcrime/securesms/preferences/ChatsPreferenceFragment.java
new file mode 100644
index 000000000..3def50cfa
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/preferences/ChatsPreferenceFragment.java
@@ -0,0 +1,143 @@
+package org.thoughtcrime.securesms.preferences;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.preference.EditTextPreference;
+import android.preference.MultiSelectListPreference;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.support.v4.preference.PreferenceFragment;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.afollestad.materialdialogs.AlertDialogWrapper;
+
+import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
+import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.util.TextSecurePreferences;
+import org.thoughtcrime.securesms.util.Trimmer;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class ChatsPreferenceFragment extends PreferenceFragment {
+ private static final String TAG = ChatsPreferenceFragment.class.getSimpleName();
+
+ @Override
+ public void onCreate(Bundle paramBundle) {
+ super.onCreate(paramBundle);
+ addPreferencesFromResource(R.xml.preferences_chats);
+
+ findPreference(TextSecurePreferences.MEDIA_DOWNLOAD_MOBILE_PREF)
+ .setOnPreferenceChangeListener(new MediaDownloadChangeListener());
+ findPreference(TextSecurePreferences.MEDIA_DOWNLOAD_WIFI_PREF)
+ .setOnPreferenceChangeListener(new MediaDownloadChangeListener());
+ findPreference(TextSecurePreferences.MEDIA_DOWNLOAD_ROAMING_PREF)
+ .setOnPreferenceChangeListener(new MediaDownloadChangeListener());
+
+ findPreference(TextSecurePreferences.THREAD_TRIM_NOW)
+ .setOnPreferenceClickListener(new TrimNowClickListener());
+ findPreference(TextSecurePreferences.THREAD_TRIM_LENGTH)
+ .setOnPreferenceChangeListener(new TrimLengthValidationListener());
+
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ ((ApplicationPreferencesActivity)getActivity()).getSupportActionBar().setTitle(R.string.preferences__chats);
+ setMediaDownloadSummaries();
+ }
+
+ private void setMediaDownloadSummaries() {
+ findPreference(TextSecurePreferences.MEDIA_DOWNLOAD_MOBILE_PREF)
+ .setSummary(getSummaryForMediaPreference(TextSecurePreferences.getMobileMediaDownloadAllowed(getActivity())));
+ findPreference(TextSecurePreferences.MEDIA_DOWNLOAD_WIFI_PREF)
+ .setSummary(getSummaryForMediaPreference(TextSecurePreferences.getWifiMediaDownloadAllowed(getActivity())));
+ findPreference(TextSecurePreferences.MEDIA_DOWNLOAD_ROAMING_PREF)
+ .setSummary(getSummaryForMediaPreference(TextSecurePreferences.getRoamingMediaDownloadAllowed(getActivity())));
+ }
+
+ private CharSequence getSummaryForMediaPreference(Set allowedNetworks) {
+ String[] keys = getResources().getStringArray(R.array.pref_media_download_entries);
+ String[] values = getResources().getStringArray(R.array.pref_media_download_values);
+ List outValues = new ArrayList<>(allowedNetworks.size());
+
+ for (int i=0; i < keys.length; i++) {
+ if (allowedNetworks.contains(keys[i])) outValues.add(values[i]);
+ }
+
+ return outValues.isEmpty() ? getResources().getString(R.string.preferences__none)
+ : TextUtils.join(", ", outValues);
+ }
+
+ private class TrimNowClickListener implements Preference.OnPreferenceClickListener {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ final int threadLengthLimit = TextSecurePreferences.getThreadTrimLength(getActivity());
+ AlertDialogWrapper.Builder builder = new AlertDialogWrapper.Builder(getActivity());
+ builder.setTitle(R.string.ApplicationPreferencesActivity_delete_all_old_messages_now);
+ builder.setMessage(getString(R.string.ApplicationPreferencesActivity_are_you_sure_you_would_like_to_immediately_trim_all_conversation_threads_to_the_s_most_recent_messages,
+ threadLengthLimit));
+ builder.setPositiveButton(R.string.ApplicationPreferencesActivity_delete,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Trimmer.trimAllThreads(getActivity(), threadLengthLimit);
+ }
+ });
+
+ builder.setNegativeButton(android.R.string.cancel, null);
+ builder.show();
+
+ return true;
+ }
+ }
+
+ private class MediaDownloadChangeListener implements OnPreferenceChangeListener {
+ @SuppressWarnings("unchecked")
+ @Override public boolean onPreferenceChange(Preference preference, Object newValue) {
+ Log.w(TAG, "onPreferenceChange");
+ preference.setSummary(getSummaryForMediaPreference((Set)newValue));
+ return true;
+ }
+ }
+
+ private class TrimLengthValidationListener implements Preference.OnPreferenceChangeListener {
+
+ public TrimLengthValidationListener() {
+ EditTextPreference preference = (EditTextPreference)findPreference(TextSecurePreferences.THREAD_TRIM_LENGTH);
+ preference.setSummary(getString(R.string.ApplicationPreferencesActivity_messages_per_conversation, preference.getText()));
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (newValue == null || ((String)newValue).trim().length() == 0) {
+ return false;
+ }
+
+ try {
+ Integer.parseInt((String)newValue);
+ } catch (NumberFormatException nfe) {
+ Log.w(TAG, nfe);
+ return false;
+ }
+
+ if (Integer.parseInt((String)newValue) < 1) {
+ return false;
+ }
+
+ preference.setSummary(getString(R.string.ApplicationPreferencesActivity_messages_per_conversation, newValue));
+ return true;
+ }
+ }
+
+ public static CharSequence getSummary(Context context) {
+ return null;
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/preferences/StoragePreferenceFragment.java b/src/org/thoughtcrime/securesms/preferences/StoragePreferenceFragment.java
deleted file mode 100644
index 616ba275b..000000000
--- a/src/org/thoughtcrime/securesms/preferences/StoragePreferenceFragment.java
+++ /dev/null
@@ -1,96 +0,0 @@
-package org.thoughtcrime.securesms.preferences;
-
-import android.content.Context;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.preference.EditTextPreference;
-import android.preference.Preference;
-import android.support.v4.preference.PreferenceFragment;
-import android.util.Log;
-
-import com.afollestad.materialdialogs.AlertDialogWrapper;
-
-import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
-import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.util.TextSecurePreferences;
-import org.thoughtcrime.securesms.util.Trimmer;
-
-public class StoragePreferenceFragment extends PreferenceFragment {
- private static final String TAG = StoragePreferenceFragment.class.getSimpleName();
-
- @Override
- public void onCreate(Bundle paramBundle) {
- super.onCreate(paramBundle);
- addPreferencesFromResource(R.xml.preferences_storage);
-
- this.findPreference(TextSecurePreferences.THREAD_TRIM_NOW)
- .setOnPreferenceClickListener(new TrimNowClickListener());
- this.findPreference(TextSecurePreferences.THREAD_TRIM_LENGTH)
- .setOnPreferenceChangeListener(new TrimLengthValidationListener());
- }
-
- @Override
- public void onResume() {
- super.onResume();
- ((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.preferences__delete_old_messages);
- }
-
- private class TrimNowClickListener implements Preference.OnPreferenceClickListener {
- @Override
- public boolean onPreferenceClick(Preference preference) {
- final int threadLengthLimit = TextSecurePreferences.getThreadTrimLength(getActivity());
- AlertDialogWrapper.Builder builder = new AlertDialogWrapper.Builder(getActivity());
- builder.setTitle(R.string.ApplicationPreferencesActivity_delete_all_old_messages_now);
- builder.setMessage(getString(R.string.ApplicationPreferencesActivity_are_you_sure_you_would_like_to_immediately_trim_all_conversation_threads_to_the_s_most_recent_messages,
- threadLengthLimit));
- builder.setPositiveButton(R.string.ApplicationPreferencesActivity_delete,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- Trimmer.trimAllThreads(getActivity(), threadLengthLimit);
- }
- });
-
- builder.setNegativeButton(android.R.string.cancel, null);
- builder.show();
-
- return true;
- }
- }
-
- private class TrimLengthValidationListener implements Preference.OnPreferenceChangeListener {
-
- public TrimLengthValidationListener() {
- EditTextPreference preference = (EditTextPreference)findPreference(TextSecurePreferences.THREAD_TRIM_LENGTH);
- preference.setSummary(getString(R.string.ApplicationPreferencesActivity_messages_per_conversation, preference.getText()));
- }
-
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- if (newValue == null || ((String)newValue).trim().length() == 0) {
- return false;
- }
-
- try {
- Integer.parseInt((String)newValue);
- } catch (NumberFormatException nfe) {
- Log.w(TAG, nfe);
- return false;
- }
-
- if (Integer.parseInt((String)newValue) < 1) {
- return false;
- }
-
- preference.setSummary(getString(R.string.ApplicationPreferencesActivity_messages_per_conversation, newValue));
- return true;
- }
- }
-
- public static CharSequence getSummary(Context context) {
- final int onCapsResId = R.string.ApplicationPreferencesActivity_On;
- final int offCapsResId = R.string.ApplicationPreferencesActivity_Off;
-
- return context.getString(TextSecurePreferences.isThreadLengthTrimmingEnabled(context) ? onCapsResId : offCapsResId);
- }
-}
diff --git a/src/org/thoughtcrime/securesms/util/MediaUtil.java b/src/org/thoughtcrime/securesms/util/MediaUtil.java
index d63ad362d..b04d4f522 100644
--- a/src/org/thoughtcrime/securesms/util/MediaUtil.java
+++ b/src/org/thoughtcrime/securesms/util/MediaUtil.java
@@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.util;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.MimeTypeMap;
@@ -108,6 +110,11 @@ public class MediaUtil {
return ContentType.isVideoType(Util.toIsoString(part.getContentType()));
}
+ public static @Nullable String getDiscreteMimeType(@NonNull PduPart part) {
+ final String[] sections = (Util.toIsoString(part.getContentType()).split("/", 2));
+ return sections.length > 1 ? sections[0] : null;
+ }
+
public static class ThumbnailData {
Bitmap bitmap;
float aspectRatio;
diff --git a/src/org/thoughtcrime/securesms/util/ServiceUtil.java b/src/org/thoughtcrime/securesms/util/ServiceUtil.java
index 049433d43..d1cfdae7a 100644
--- a/src/org/thoughtcrime/securesms/util/ServiceUtil.java
+++ b/src/org/thoughtcrime/securesms/util/ServiceUtil.java
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.util;
import android.app.Activity;
import android.content.Context;
+import android.net.ConnectivityManager;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
@@ -13,4 +14,8 @@ public class ServiceUtil {
public static WindowManager getWindowManager(Context context) {
return (WindowManager) context.getSystemService(Activity.WINDOW_SERVICE);
}
+
+ public static ConnectivityManager getConnectivityManager(Context context) {
+ return (ConnectivityManager) context.getSystemService(Activity.CONNECTIVITY_SERVICE);
+ }
}
diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java
index c5572da59..f94a4efa7 100644
--- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java
+++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java
@@ -4,11 +4,19 @@ import android.content.Context;
import android.os.Build;
import android.preference.PreferenceManager;
import android.provider.Settings;
+import android.support.annotation.ArrayRes;
+import android.support.annotation.NonNull;
import android.util.Log;
+import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.preferences.NotificationPrivacyPreference;
import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
public class TextSecurePreferences {
@@ -74,6 +82,10 @@ public class TextSecurePreferences {
public static final String REPEAT_ALERTS_PREF = "pref_repeat_alerts";
public static final String NOTIFICATION_PRIVACY_PREF = "pref_notification_privacy";
+ public static final String MEDIA_DOWNLOAD_MOBILE_PREF = "pref_media_download_mobile";
+ public static final String MEDIA_DOWNLOAD_WIFI_PREF = "pref_media_download_wifi";
+ public static final String MEDIA_DOWNLOAD_ROAMING_PREF = "pref_media_download_roaming";
+
public static NotificationPrivacyPreference getNotificationPrivacy(Context context) {
return new NotificationPrivacyPreference(getStringPreference(context, NOTIFICATION_PRIVACY_PREF, "all"));
}
@@ -433,6 +445,25 @@ public class TextSecurePreferences {
return Integer.parseInt(getStringPreference(context, THREAD_TRIM_LENGTH, "500"));
}
+ public static @NonNull Set getMobileMediaDownloadAllowed(Context context) {
+ return getMediaDownloadAllowed(context, MEDIA_DOWNLOAD_MOBILE_PREF, R.array.pref_media_download_mobile_data_default);
+ }
+
+ public static @NonNull Set getWifiMediaDownloadAllowed(Context context) {
+ return getMediaDownloadAllowed(context, MEDIA_DOWNLOAD_WIFI_PREF, R.array.pref_media_download_wifi_default);
+ }
+
+ public static @NonNull Set getRoamingMediaDownloadAllowed(Context context) {
+ return getMediaDownloadAllowed(context, MEDIA_DOWNLOAD_ROAMING_PREF, R.array.pref_media_download_roaming_default);
+ }
+
+ private static @NonNull Set getMediaDownloadAllowed(Context context, String key, @ArrayRes int defaultValuesRes) {
+ return getStringSetPreference(context,
+ key,
+ new HashSet<>(Arrays.asList(context.getResources().getStringArray(defaultValuesRes))));
+ }
+
+
public static void setBooleanPreference(Context context, String key, boolean value) {
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(key, value).apply();
}
@@ -468,4 +499,8 @@ public class TextSecurePreferences {
private static void setLongPreference(Context context, String key, long value) {
PreferenceManager.getDefaultSharedPreferences(context).edit().putLong(key, value).apply();
}
+
+ private static Set getStringSetPreference(Context context, String key, Set defaultValues) {
+ return PreferenceManager.getDefaultSharedPreferences(context).getStringSet(key, defaultValues);
+ }
}
diff --git a/src/org/thoughtcrime/securesms/util/Util.java b/src/org/thoughtcrime/securesms/util/Util.java
index a7ce4ce80..64f96f9dd 100644
--- a/src/org/thoughtcrime/securesms/util/Util.java
+++ b/src/org/thoughtcrime/securesms/util/Util.java
@@ -28,6 +28,7 @@ import android.os.Build.VERSION_CODES;
import android.os.Handler;
import android.os.Looper;
import android.provider.Telephony;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.telephony.TelephonyManager;
import android.text.Spannable;
@@ -119,7 +120,7 @@ public class Util {
return spanned;
}
- public static String toIsoString(byte[] bytes) {
+ public static @NonNull String toIsoString(byte[] bytes) {
try {
return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1);
} catch (UnsupportedEncodingException e) {
diff --git a/src/ws/com/google/android/mms/pdu/PduPart.java b/src/ws/com/google/android/mms/pdu/PduPart.java
index c17c74270..630f232d6 100644
--- a/src/ws/com/google/android/mms/pdu/PduPart.java
+++ b/src/ws/com/google/android/mms/pdu/PduPart.java
@@ -134,8 +134,9 @@ public class PduPart {
private long rowId = -1;
private long uniqueId = -1;
+ private long mmsId = -1;
private boolean isEncrypted;
- private boolean isInProgress;
+ private int transferProgress;
private long dataSize;
private Bitmap thumbnail;
@@ -163,13 +164,17 @@ public class PduPart {
return this.dataSize;
}
-
- public void setInProgress(boolean isInProgress) {
- this.isInProgress = isInProgress;
+ public boolean isInProgress() {
+ return transferProgress != PartDatabase.TRANSFER_PROGRESS_DONE &&
+ transferProgress != PartDatabase.TRANSFER_PROGRESS_FAILED;
}
- public boolean isInProgress() {
- return isInProgress;
+ public void setTransferProgress(int transferProgress) {
+ this.transferProgress = transferProgress;
+ }
+
+ public int getTransferProgress() {
+ return transferProgress;
}
/**
@@ -475,5 +480,13 @@ public class PduPart {
public void setUniqueId(long uniqueId) {
this.uniqueId = uniqueId;
}
+
+ public long getMmsId() {
+ return mmsId;
+ }
+
+ public void setMmsId(long mmsId) {
+ this.mmsId = mmsId;
+ }
}