diff --git a/build.gradle b/build.gradle index 1d5da530a6..060bda4eb8 100644 --- a/build.gradle +++ b/build.gradle @@ -50,10 +50,10 @@ dependencies { compile 'de.greenrobot:eventbus:2.4.0' compile 'pl.tajchert:waitingdots:0.1.0' compile 'com.soundcloud.android:android-crop:0.9.10@aar' - compile 'com.android.support:appcompat-v7:25.0.1' - compile 'com.android.support:recyclerview-v7:25.0.1' - compile 'com.android.support:design:25.0.1' - compile 'com.android.support:cardview-v7:25.0.1' + compile 'com.android.support:appcompat-v7:24.2.1' + compile 'com.android.support:recyclerview-v7:24.2.1' + compile 'com.android.support:design:24.2.1' + compile 'com.android.support:cardview-v7:24.2.1' compile 'com.melnykov:floatingactionbutton:1.3.0' compile 'com.google.zxing:android-integration:3.1.0' compile ('com.android.support:support-v4-preferencefragment:1.0.0@aar'){ @@ -117,10 +117,10 @@ dependencyVerification { 'de.greenrobot:eventbus:61d743a748156a372024d083de763b9e91ac2dcb3f6a1cbc74995c7ddab6e968', 'pl.tajchert:waitingdots:2835d49e0787dbcb606c5a60021ced66578503b1e9fddcd7a5ef0cd5f095ba2c', 'com.soundcloud.android:android-crop:ffd4b973cf6e97f7d64118a0dc088df50e9066fd5634fe6911dd0c0c5d346177', - 'com.android.support:appcompat-v7:7fead560a22ea4b15848ce3000f312ef611fac0953bf90ca8710a72a1f6e36ea', - 'com.android.support:recyclerview-v7:803baba7be537ace8c5cb8a775e37547c22a04c4b028833796c45c26ec1deca2', - 'com.android.support:design:07a72eb68c888b38d7b78e450e1af8a84e571406e0cf911889e0645d5a41f1e4', - 'com.android.support:cardview-v7:50d88fae8cd1076cb90504d36ca5ee9df4018555c8f041bd28f43274c0fc9e1f', + 'com.android.support:appcompat-v7:ead7ac8011fb40676df8adc2856cae934edab55fc4444654c0ac6ea443736088', + 'com.android.support:recyclerview-v7:9077766a1a0f4e89528fbf9dcdf6d5880a8686f0266fa852d58d803beeef18fa', + 'com.android.support:design:89842bb1243507fe3079066ea4ea58795effe69cdf9a819e05274d21760adfc2', + 'com.android.support:cardview-v7:2303b351686d1db060b5fcf1a9c709c79b4a54a85bfda0fb3c4849e244606ee1', 'com.melnykov:floatingactionbutton:15d58d4fac0f7a288d0e5301bbaf501a146f5b3f5921277811bf99bd3b397263', 'com.google.zxing:android-integration:89e56aadf1164bd71e57949163c53abf90af368b51669c0d4a47a163335f95c4', 'com.android.support:support-v4-preferencefragment:5470f5872514a6226fa1fc6f4e000991f38805691c534cf0bd2778911fc773ad', @@ -136,10 +136,9 @@ dependencyVerification { 'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259', 'cn.carbswang.android:NumberPickerView:18b3c316d62c7c277978a8d4ed57a5b8f4e943762264960f579a8a549c756729', 'com.google.android.gms:play-services-base:ef36e50fa5c0415ed41f74dd399a889efd2fa327c449036e140c7c3786aa0e1f', - 'com.android.support:support-annotations:bd94ab42c841db16fb480f4c65d33d297e544655ecc498b37c5cf33a0c5f1968', - 'com.android.support:support-compat:d04f15aa5f2ae9e8cb7d025bf02dfd4fd6f6800628ceb107e0589634c9e4e537', - 'com.android.support:support-core-ui:29205ac978a1839d92be3d32db2385dac10f8688bba649e51650023c76de2f00', - 'com.android.support:transition:9fd1e6d27cb70b3c5cd19f842b48bbb05cb4e5c93a22372769c342523393e8ea', + 'com.android.support:support-annotations:1e4d471c5378b283d95abfb128e7ed3c6b3cb19bb6f0c317a9b75e48e99365ff', + 'com.android.support:support-compat:8e4fe0078b68073e8f5bcb52aa5b6407fd456d47c51aa0f8e8d1e23c69da06c1', + 'com.android.support:support-core-ui:ecc9184b7f438980e1c4a08b089d62dbc53ff90091f442d91fec27322a02c73c', 'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a', 'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f', @@ -156,18 +155,17 @@ dependencyVerification { 'com.squareup.okio:okio:5e1098bd3fdee4c3347f5ab815b40ba851e4ab1b348c5e49a5b0362f0ce6e978', 'com.fasterxml.jackson.core:jackson-annotations:0ca408c24202a7626ec8b861e99d85eca5e38b73311dd6dd12e3e9deecc3fe94', 'com.fasterxml.jackson.core:jackson-core:cbf4604784b4de226262845447a1ad3bb38a6728cebe86562e2c5afada8be2c0', - 'com.android.support:support-v4:50da261acc4ca3d2dea9a43106bf65488711ca97b20a4daa095dba381c205c98', - 'com.android.support:support-media-compat:01cac57af687bed9a6cb4ce803bebd1b7e6b8469c14f1f9ac6b4596637ff73d6', - 'com.android.support:support-core-utils:632c3750bd991da8b591f24a8916e74ca6063ae7f525f005c96981725c9bf491', - 'com.android.support:support-fragment:da47261a1d7c3d33e6e911335a7f4ce01135923bb221d3ab84625d005fa1969f', - 'com.android.support:support-vector-drawable:071ae3695bf8427d3cbfc8791492a3d9c804a4b111aa2a72fbfe7790ea268e5d', - 'com.android.support:animated-vector-drawable:70443a2857f9968c4e2c27c107657ce2291d774f8a50f03444e12ab637451175', + 'com.android.support:support-v4:cac2956f5c4bb363cc0ba824ac16ea2a687d1c305d434416a34772a5f9375ed7', + 'com.android.support:support-media-compat:fa29a23eadd685631584b2c0c624a36e3bb79a33e257b00304501ad682fa2be3', + 'com.android.support:support-core-utils:0fbc508e41dd6e8c634f310ee88452aaf8f48b6a843a369b115130b80d2fc05f', + 'com.android.support:support-fragment:d8030f0bf0f64214a29dc4e14d5ccd225e59f66ed15eb37f3a5022e773dd1fda', + 'com.android.support:support-vector-drawable:6ee37a7f7b93c1df1294e6f6f97df3724ac989fcda0549faf677001085330548', + 'com.android.support:animated-vector-drawable:5aa30f578e1daefb26bef0ce06414266fbb4cdf5d4259f42a92c7bd83dcd81b4', ] } - android { - compileSdkVersion 25 + compileSdkVersion 24 buildToolsVersion '23.0.3' useLibrary 'org.apache.http.legacy' diff --git a/res/drawable-hdpi/ic_play_circle_outline_white_48dp.png b/res/drawable-hdpi/ic_play_circle_outline_white_48dp.png new file mode 100644 index 0000000000..6e1b578c54 Binary files /dev/null and b/res/drawable-hdpi/ic_play_circle_outline_white_48dp.png differ diff --git a/res/drawable-mdpi/ic_play_circle_outline_white_48dp.png b/res/drawable-mdpi/ic_play_circle_outline_white_48dp.png new file mode 100644 index 0000000000..615b80d085 Binary files /dev/null and b/res/drawable-mdpi/ic_play_circle_outline_white_48dp.png differ diff --git a/res/drawable-xhdpi/ic_play_circle_outline_white_48dp.png b/res/drawable-xhdpi/ic_play_circle_outline_white_48dp.png new file mode 100644 index 0000000000..516f643269 Binary files /dev/null and b/res/drawable-xhdpi/ic_play_circle_outline_white_48dp.png differ diff --git a/res/drawable-xxhdpi/ic_play_circle_outline_white_48dp.png b/res/drawable-xxhdpi/ic_play_circle_outline_white_48dp.png new file mode 100644 index 0000000000..0311f899dd Binary files /dev/null and b/res/drawable-xxhdpi/ic_play_circle_outline_white_48dp.png differ diff --git a/res/drawable-xxxhdpi/ic_play_circle_outline_white_48dp.png b/res/drawable-xxxhdpi/ic_play_circle_outline_white_48dp.png new file mode 100644 index 0000000000..7a5a16858b Binary files /dev/null and b/res/drawable-xxxhdpi/ic_play_circle_outline_white_48dp.png differ diff --git a/res/layout/thumbnail_view.xml b/res/layout/thumbnail_view.xml index 1e446b0552..24851538c3 100644 --- a/res/layout/thumbnail_view.xml +++ b/res/layout/thumbnail_view.xml @@ -10,6 +10,18 @@ android:scaleType="fitCenter" android:contentDescription="@string/conversation_item__mms_image_description" /> + + { @Override public void onClick(View v) { - Intent intent = new Intent(getContext(), MediaPreviewActivity.class); - intent.putExtra(MediaPreviewActivity.DATE_EXTRA, imageRecord.getDate()); - intent.putExtra(MediaPreviewActivity.THREAD_ID_EXTRA, threadId); + if (imageRecord.getAttachment().getDataUri() != null) { + Intent intent = new Intent(getContext(), MediaPreviewActivity.class); + intent.putExtra(MediaPreviewActivity.DATE_EXTRA, imageRecord.getDate()); + intent.putExtra(MediaPreviewActivity.THREAD_ID_EXTRA, threadId); - if (!TextUtils.isEmpty(imageRecord.getAddress())) { - Recipients recipients = RecipientFactory.getRecipientsFromString(getContext(), - imageRecord.getAddress(), - true); - if (recipients != null && recipients.getPrimaryRecipient() != null) { - intent.putExtra(MediaPreviewActivity.RECIPIENT_EXTRA, recipients.getPrimaryRecipient().getRecipientId()); + if (!TextUtils.isEmpty(imageRecord.getAddress())) { + Recipients recipients = RecipientFactory.getRecipientsFromString(getContext(), + imageRecord.getAddress(), + true); + if (recipients != null && recipients.getPrimaryRecipient() != null) { + intent.putExtra(MediaPreviewActivity.RECIPIENT_EXTRA, recipients.getPrimaryRecipient().getRecipientId()); + } } + intent.setDataAndType(imageRecord.getAttachment().getDataUri(), imageRecord.getContentType()); + getContext().startActivity(intent); } - intent.setDataAndType(imageRecord.getAttachment().getDataUri(), imageRecord.getContentType()); - getContext().startActivity(intent); - } } } diff --git a/src/org/thoughtcrime/securesms/attachments/Attachment.java b/src/org/thoughtcrime/securesms/attachments/Attachment.java index ce579d81e8..c872cc1b25 100644 --- a/src/org/thoughtcrime/securesms/attachments/Attachment.java +++ b/src/org/thoughtcrime/securesms/attachments/Attachment.java @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.attachments; -import android.graphics.Bitmap; import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -23,10 +22,6 @@ public abstract class Attachment { @Nullable private final String relay; - // XXX - This shouldn't be here. - @Nullable - private Bitmap thumbnail; - public Attachment(@NonNull String contentType, int transferState, long size, @Nullable String location, @Nullable String key, @Nullable String relay) { @@ -76,13 +71,4 @@ public abstract class Attachment { public String getRelay() { return relay; } - - public void setThumbnail(@Nullable Bitmap thumbnail) { - this.thumbnail = thumbnail; - } - - @Nullable - public Bitmap getThumbnail() { - return thumbnail; - } } diff --git a/src/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java b/src/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java index a8f47be164..84412c66b5 100644 --- a/src/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java +++ b/src/org/thoughtcrime/securesms/attachments/DatabaseAttachment.java @@ -1,7 +1,7 @@ package org.thoughtcrime.securesms.attachments; import android.net.Uri; -import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import org.thoughtcrime.securesms.mms.PartAuthority; @@ -10,27 +10,38 @@ public class DatabaseAttachment extends Attachment { private final AttachmentId attachmentId; private final long mmsId; private final boolean hasData; + private final boolean hasThumbnail; - public DatabaseAttachment(AttachmentId attachmentId, long mmsId, boolean hasData, + public DatabaseAttachment(AttachmentId attachmentId, long mmsId, + boolean hasData, boolean hasThumbnail, String contentType, int transferProgress, long size, String location, String key, String relay) { super(contentType, transferProgress, size, location, key, relay); this.attachmentId = attachmentId; this.hasData = hasData; + this.hasThumbnail = hasThumbnail; this.mmsId = mmsId; } @Override - @NonNull + @Nullable public Uri getDataUri() { - return PartAuthority.getAttachmentDataUri(attachmentId); + if (hasData) { + return PartAuthority.getAttachmentDataUri(attachmentId); + } else { + return null; + } } @Override - @NonNull + @Nullable public Uri getThumbnailUri() { - return PartAuthority.getAttachmentThumbnailUri(attachmentId); + if (hasThumbnail) { + return PartAuthority.getAttachmentThumbnailUri(attachmentId); + } else { + return null; + } } public AttachmentId getAttachmentId() { @@ -56,4 +67,8 @@ public class DatabaseAttachment extends Attachment { public boolean hasData() { return hasData; } + + public boolean hasThumbnail() { + return hasThumbnail; + } } diff --git a/src/org/thoughtcrime/securesms/attachments/UriAttachment.java b/src/org/thoughtcrime/securesms/attachments/UriAttachment.java index ea6828bc76..fdef4178b1 100644 --- a/src/org/thoughtcrime/securesms/attachments/UriAttachment.java +++ b/src/org/thoughtcrime/securesms/attachments/UriAttachment.java @@ -1,26 +1,19 @@ package org.thoughtcrime.securesms.attachments; -import android.content.Context; import android.net.Uri; import android.support.annotation.NonNull; - -import org.thoughtcrime.securesms.crypto.MasterSecret; -import org.thoughtcrime.securesms.util.MediaUtil; -import org.whispersystems.libsignal.util.guava.Optional; - -import java.io.IOException; -import java.io.InputStream; +import android.support.annotation.Nullable; public class UriAttachment extends Attachment { - private final @NonNull Uri dataUri; - private final @NonNull Uri thumbnailUri; + private final @NonNull Uri dataUri; + private final @Nullable Uri thumbnailUri; public UriAttachment(@NonNull Uri uri, @NonNull String contentType, int transferState, long size) { this(uri, uri, contentType, transferState, size); } - public UriAttachment(@NonNull Uri dataUri, @NonNull Uri thumbnailUri, + public UriAttachment(@NonNull Uri dataUri, @Nullable Uri thumbnailUri, @NonNull String contentType, int transferState, long size) { super(contentType, transferState, size, null, null, null); @@ -35,7 +28,7 @@ public class UriAttachment extends Attachment { } @Override - @NonNull + @Nullable public Uri getThumbnailUri() { return thumbnailUri; } diff --git a/src/org/thoughtcrime/securesms/components/ThumbnailView.java b/src/org/thoughtcrime/securesms/components/ThumbnailView.java index 29799b548e..ec30896f0c 100644 --- a/src/org/thoughtcrime/securesms/components/ThumbnailView.java +++ b/src/org/thoughtcrime/securesms/components/ThumbnailView.java @@ -36,6 +36,7 @@ public class ThumbnailView extends FrameLayout { private static final String TAG = ThumbnailView.class.getSimpleName(); private ImageView image; + private ImageView playOverlay; private int backgroundColorHint; private int radius; private OnClickListener parentClickListener; @@ -58,8 +59,9 @@ public class ThumbnailView extends FrameLayout { inflate(context, R.layout.thumbnail_view, this); - this.radius = getResources().getDimensionPixelSize(R.dimen.message_bubble_corner_radius); - this.image = (ImageView) findViewById(R.id.thumbnail_image); + this.radius = getResources().getDimensionPixelSize(R.dimen.message_bubble_corner_radius); + this.image = (ImageView) findViewById(R.id.thumbnail_image); + this.playOverlay = (ImageView) findViewById(R.id.play_overlay); super.setOnClickListener(new ThumbnailClickDispatcher()); if (attrs != null) { @@ -105,6 +107,12 @@ public class ThumbnailView extends FrameLayout { getTransferControls().setVisibility(View.GONE); } + if (slide.getThumbnailUri() != null && slide.hasPlayOverlay() && slide.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_DONE) { + this.playOverlay.setVisibility(View.VISIBLE); + } else { + this.playOverlay.setVisibility(View.GONE); + } + if (Util.equals(slide, this.slide)) { Log.w(TAG, "Not re-loading slide " + slide.asAttachment().getDataUri()); return; diff --git a/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java b/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java index f591765d1e..b78abc6031 100644 --- a/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java +++ b/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java @@ -21,7 +21,10 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.graphics.Bitmap; +import android.media.MediaMetadataRetriever; import android.net.Uri; +import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; @@ -42,6 +45,7 @@ import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.MediaUtil.ThumbnailData; import org.thoughtcrime.securesms.util.Util; +import org.thoughtcrime.securesms.video.EncryptedMediaDataSource; import java.io.File; import java.io.FileNotFoundException; @@ -54,6 +58,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; +import ws.com.google.android.mms.ContentType; import ws.com.google.android.mms.MmsException; public class AttachmentDatabase extends Database { @@ -71,7 +76,7 @@ public class AttachmentDatabase extends Database { static final String DATA = "_data"; static final String TRANSFER_STATE = "pending_push"; static final String SIZE = "data_size"; - private static final String THUMBNAIL = "thumbnail"; + static final String THUMBNAIL = "thumbnail"; static final String THUMBNAIL_ASPECT_RATIO = "aspect_ratio"; static final String UNIQUE_ID = "unique_id"; @@ -84,7 +89,7 @@ public class AttachmentDatabase extends Database { private static final String[] PROJECTION = new String[] {ROW_ID + " AS " + ATTACHMENT_ID_ALIAS, MMS_ID, CONTENT_TYPE, NAME, CONTENT_DISPOSITION, - CONTENT_LOCATION, DATA, TRANSFER_STATE, + CONTENT_LOCATION, DATA, THUMBNAIL, TRANSFER_STATE, SIZE, THUMBNAIL, THUMBNAIL_ASPECT_RATIO, UNIQUE_ID}; @@ -313,6 +318,7 @@ public class AttachmentDatabase extends Database { return new DatabaseAttachment(databaseAttachment.getAttachmentId(), databaseAttachment.getMmsId(), databaseAttachment.hasData(), + databaseAttachment.hasThumbnail(), mediaStream.getMimeType(), databaseAttachment.getTransferState(), dataSize, @@ -434,6 +440,7 @@ public class AttachmentDatabase extends Database { cursor.getLong(cursor.getColumnIndexOrThrow(UNIQUE_ID))), cursor.getLong(cursor.getColumnIndexOrThrow(MMS_ID)), !cursor.isNull(cursor.getColumnIndexOrThrow(DATA)), + !cursor.isNull(cursor.getColumnIndexOrThrow(THUMBNAIL)), cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_TYPE)), cursor.getInt(cursor.getColumnIndexOrThrow(TRANSFER_STATE)), cursor.getLong(cursor.getColumnIndexOrThrow(SIZE)), @@ -474,11 +481,8 @@ public class AttachmentDatabase extends Database { long rowId = database.insert(TABLE_NAME, null, contentValues); AttachmentId attachmentId = new AttachmentId(rowId, uniqueId); - if (attachment.getThumbnail() != null && masterSecret.getMasterSecret().isPresent()) { - Log.w(TAG, "inserting pre-generated thumbnail"); - ThumbnailData data = new ThumbnailData(attachment.getThumbnail()); - updateAttachmentThumbnail(masterSecret.getMasterSecret().get(), attachmentId, data.toDataStream(), data.getAspectRatio()); - } else if (!attachment.isInProgress()) { + if (partData != null) { + Log.w(TAG, "Submitting thumbnail generation job..."); thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret.getMasterSecret().get(), attachmentId)); } @@ -501,21 +505,33 @@ public class AttachmentDatabase extends Database { values.put(THUMBNAIL_ASPECT_RATIO, aspectRatio); database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings()); + + Cursor cursor = database.query(TABLE_NAME, new String[] {MMS_ID}, PART_ID_WHERE, attachmentId.toStrings(), null, null, null); + + try { + if (cursor != null && cursor.moveToFirst()) { + notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(cursor.getLong(cursor.getColumnIndexOrThrow(MMS_ID)))); + } + } finally { + if (cursor != null) cursor.close(); + } } @VisibleForTesting class ThumbnailFetchCallable implements Callable { + private final MasterSecret masterSecret; private final AttachmentId attachmentId; - public ThumbnailFetchCallable(MasterSecret masterSecret, AttachmentId attachmentId) { + ThumbnailFetchCallable(MasterSecret masterSecret, AttachmentId attachmentId) { this.masterSecret = masterSecret; this.attachmentId = attachmentId; } @Override public @Nullable InputStream call() throws Exception { + Log.w(TAG, "Executing thumbnail job..."); final InputStream stream = getDataStream(masterSecret, attachmentId, THUMBNAIL); if (stream != null) { @@ -528,7 +544,13 @@ public class AttachmentDatabase extends Database { return null; } - ThumbnailData data = MediaUtil.generateThumbnail(context, masterSecret, attachment.getContentType(), attachment.getDataUri()); + ThumbnailData data; + + if (ContentType.isVideoType(attachment.getContentType())) { + data = generateVideoThumbnail(masterSecret, attachmentId); + } else{ + data = MediaUtil.generateThumbnail(context, masterSecret, attachment.getContentType(), attachment.getDataUri()); + } if (data == null) { return null; @@ -538,5 +560,28 @@ public class AttachmentDatabase extends Database { return getDataStream(masterSecret, attachmentId, THUMBNAIL); } + + private ThumbnailData generateVideoThumbnail(MasterSecret masterSecret, AttachmentId attachmentId) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + Log.w(TAG, "Video thumbnails not supported..."); + return null; + } + + File mediaFile = getAttachmentDataFile(attachmentId, DATA); + + if (mediaFile == null) { + Log.w(TAG, "No data file found for video thumbnail..."); + return null; + } + + EncryptedMediaDataSource dataSource = new EncryptedMediaDataSource(masterSecret, mediaFile); + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + retriever.setDataSource(dataSource); + + Bitmap bitmap = retriever.getFrameAtTime(1000); + + Log.w(TAG, "Generated video thumbnail..."); + return new ThumbnailData(bitmap); + } } } diff --git a/src/org/thoughtcrime/securesms/database/ImageDatabase.java b/src/org/thoughtcrime/securesms/database/ImageDatabase.java index 5e7bb25179..6532501a8d 100644 --- a/src/org/thoughtcrime/securesms/database/ImageDatabase.java +++ b/src/org/thoughtcrime/securesms/database/ImageDatabase.java @@ -19,6 +19,7 @@ public class ImageDatabase extends Database { + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.TRANSFER_STATE + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.SIZE + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DATA + ", " + + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.THUMBNAIL + ", " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.MESSAGE_BOX + ", " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_SENT + ", " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_RECEIVED + ", " @@ -47,19 +48,22 @@ public class ImageDatabase extends Database { private final AttachmentId attachmentId; private final long mmsId; private final boolean hasData; + private final boolean hasThumbnail; private final String contentType; private final String address; private final long date; private final int transferState; private final long size; - private ImageRecord(AttachmentId attachmentId, long mmsId, boolean hasData, + private ImageRecord(AttachmentId attachmentId, long mmsId, + boolean hasData, boolean hasThumbnail, String contentType, String address, long date, int transferState, long size) { this.attachmentId = attachmentId; this.mmsId = mmsId; this.hasData = hasData; + this.hasThumbnail = hasThumbnail; this.contentType = contentType; this.address = address; this.date = date; @@ -82,6 +86,7 @@ public class ImageDatabase extends Database { return new ImageRecord(attachmentId, cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.MMS_ID)), !cursor.isNull(cursor.getColumnIndexOrThrow(AttachmentDatabase.DATA)), + !cursor.isNull(cursor.getColumnIndexOrThrow(AttachmentDatabase.THUMBNAIL)), cursor.getString(cursor.getColumnIndexOrThrow(AttachmentDatabase.CONTENT_TYPE)), cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS)), date, @@ -90,7 +95,7 @@ public class ImageDatabase extends Database { } public Attachment getAttachment() { - return new DatabaseAttachment(attachmentId, mmsId, hasData, contentType, transferState, size, null, null, null); + return new DatabaseAttachment(attachmentId, mmsId, hasData, hasThumbnail, contentType, transferState, size, null, null, null); } public String getContentType() { diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 0bcd958759..fdb5e5bb67 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -138,6 +138,7 @@ public class MmsDatabase extends MessagingDatabase { AttachmentDatabase.MMS_ID, AttachmentDatabase.SIZE, AttachmentDatabase.DATA, + AttachmentDatabase.THUMBNAIL, AttachmentDatabase.CONTENT_TYPE, AttachmentDatabase.CONTENT_LOCATION, AttachmentDatabase.CONTENT_DISPOSITION, @@ -672,6 +673,7 @@ public class MmsDatabase extends MessagingDatabase { attachments.add(new DatabaseAttachment(databaseAttachment.getAttachmentId(), databaseAttachment.getMmsId(), databaseAttachment.hasData(), + databaseAttachment.hasThumbnail(), databaseAttachment.getContentType(), AttachmentDatabase.TRANSFER_PROGRESS_DONE, databaseAttachment.getSize(), diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index 3ad42dec9f..106fd94093 100644 --- a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -62,6 +62,7 @@ public class MmsSmsDatabase extends Database { AttachmentDatabase.MMS_ID, AttachmentDatabase.SIZE, AttachmentDatabase.DATA, + AttachmentDatabase.THUMBNAIL, AttachmentDatabase.CONTENT_TYPE, AttachmentDatabase.CONTENT_LOCATION, AttachmentDatabase.CONTENT_DISPOSITION, @@ -153,6 +154,7 @@ public class MmsSmsDatabase extends Database { AttachmentDatabase.MMS_ID, AttachmentDatabase.SIZE, AttachmentDatabase.DATA, + AttachmentDatabase.THUMBNAIL, AttachmentDatabase.CONTENT_TYPE, AttachmentDatabase.CONTENT_LOCATION, AttachmentDatabase.CONTENT_DISPOSITION, @@ -178,6 +180,7 @@ public class MmsSmsDatabase extends Database { AttachmentDatabase.MMS_ID, AttachmentDatabase.SIZE, AttachmentDatabase.DATA, + AttachmentDatabase.THUMBNAIL, AttachmentDatabase.CONTENT_TYPE, AttachmentDatabase.CONTENT_LOCATION, AttachmentDatabase.CONTENT_DISPOSITION, @@ -226,7 +229,10 @@ public class MmsSmsDatabase extends Database { mmsColumnsPresent.add(AttachmentDatabase.ROW_ID); mmsColumnsPresent.add(AttachmentDatabase.UNIQUE_ID); + mmsColumnsPresent.add(AttachmentDatabase.MMS_ID); mmsColumnsPresent.add(AttachmentDatabase.SIZE); + mmsColumnsPresent.add(AttachmentDatabase.DATA); + mmsColumnsPresent.add(AttachmentDatabase.THUMBNAIL); mmsColumnsPresent.add(AttachmentDatabase.CONTENT_TYPE); mmsColumnsPresent.add(AttachmentDatabase.CONTENT_LOCATION); mmsColumnsPresent.add(AttachmentDatabase.CONTENT_DISPOSITION); diff --git a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java index a26e782ded..bb5941e14b 100644 --- a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -328,7 +328,7 @@ public class AttachmentManager { } private void previewImageDraft(final @NonNull Slide slide) { - if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) && slide.getThumbnailUri() != null) { + if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) { Intent intent = new Intent(context, MediaPreviewActivity.class); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setDataAndType(slide.getUri(), slide.getContentType()); diff --git a/src/org/thoughtcrime/securesms/mms/AudioSlide.java b/src/org/thoughtcrime/securesms/mms/AudioSlide.java index 46cb231079..805a61ec3c 100644 --- a/src/org/thoughtcrime/securesms/mms/AudioSlide.java +++ b/src/org/thoughtcrime/securesms/mms/AudioSlide.java @@ -37,11 +37,11 @@ import ws.com.google.android.mms.pdu.PduPart; public class AudioSlide extends Slide { public AudioSlide(Context context, Uri uri, long dataSize) { - super(context, constructAttachmentFromUri(context, uri, ContentType.AUDIO_UNSPECIFIED, dataSize)); + super(context, constructAttachmentFromUri(context, uri, ContentType.AUDIO_UNSPECIFIED, dataSize, false)); } public AudioSlide(Context context, Uri uri, long dataSize, String contentType) { - super(context, new UriAttachment(uri, contentType, AttachmentDatabase.TRANSFER_PROGRESS_STARTED, dataSize)); + super(context, new UriAttachment(uri, null, contentType, AttachmentDatabase.TRANSFER_PROGRESS_STARTED, dataSize)); } public AudioSlide(Context context, Attachment attachment) { diff --git a/src/org/thoughtcrime/securesms/mms/GifSlide.java b/src/org/thoughtcrime/securesms/mms/GifSlide.java index 614e5ad557..5a2c614b8d 100644 --- a/src/org/thoughtcrime/securesms/mms/GifSlide.java +++ b/src/org/thoughtcrime/securesms/mms/GifSlide.java @@ -20,7 +20,7 @@ public class GifSlide extends ImageSlide { } public GifSlide(Context context, Uri uri, long size) { - super(context, constructAttachmentFromUri(context, uri, ContentType.IMAGE_GIF, size)); + super(context, constructAttachmentFromUri(context, uri, ContentType.IMAGE_GIF, size, true)); } @Override diff --git a/src/org/thoughtcrime/securesms/mms/ImageSlide.java b/src/org/thoughtcrime/securesms/mms/ImageSlide.java index 2971086f39..2324309306 100644 --- a/src/org/thoughtcrime/securesms/mms/ImageSlide.java +++ b/src/org/thoughtcrime/securesms/mms/ImageSlide.java @@ -25,8 +25,6 @@ import android.support.annotation.NonNull; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.attachments.Attachment; -import java.io.IOException; - import ws.com.google.android.mms.ContentType; public class ImageSlide extends Slide { @@ -38,7 +36,7 @@ public class ImageSlide extends Slide { } public ImageSlide(Context context, Uri uri, long size) { - super(context, constructAttachmentFromUri(context, uri, ContentType.IMAGE_JPEG, size)); + super(context, constructAttachmentFromUri(context, uri, ContentType.IMAGE_JPEG, size, true)); } @Override @@ -51,7 +49,9 @@ public class ImageSlide extends Slide { return true; } - @NonNull @Override public String getContentDescription() { + @NonNull + @Override + public String getContentDescription() { return context.getString(R.string.Slide_image); } } diff --git a/src/org/thoughtcrime/securesms/mms/Slide.java b/src/org/thoughtcrime/securesms/mms/Slide.java index ba85a3d0a1..71550bee5f 100644 --- a/src/org/thoughtcrime/securesms/mms/Slide.java +++ b/src/org/thoughtcrime/securesms/mms/Slide.java @@ -103,13 +103,18 @@ public abstract class Slide { return false; } + public boolean hasPlayOverlay() { + return false; + } + protected static Attachment constructAttachmentFromUri(@NonNull Context context, @NonNull Uri uri, @NonNull String defaultMime, - long size) + long size, + boolean hasThumbnail) { Optional resolvedType = Optional.fromNullable(MediaUtil.getMimeType(context, uri)); - return new UriAttachment(uri, resolvedType.or(defaultMime), AttachmentDatabase.TRANSFER_PROGRESS_STARTED, size); + return new UriAttachment(uri, hasThumbnail ? uri : null, resolvedType.or(defaultMime), AttachmentDatabase.TRANSFER_PROGRESS_STARTED, size); } @Override diff --git a/src/org/thoughtcrime/securesms/mms/VideoSlide.java b/src/org/thoughtcrime/securesms/mms/VideoSlide.java index 8c2000b42e..465e91ff34 100644 --- a/src/org/thoughtcrime/securesms/mms/VideoSlide.java +++ b/src/org/thoughtcrime/securesms/mms/VideoSlide.java @@ -21,21 +21,17 @@ import android.content.res.Resources.Theme; import android.net.Uri; import android.support.annotation.DrawableRes; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.util.ResUtil; -import java.io.IOException; - import ws.com.google.android.mms.ContentType; -import ws.com.google.android.mms.pdu.PduPart; public class VideoSlide extends Slide { public VideoSlide(Context context, Uri uri, long dataSize) { - super(context, constructAttachmentFromUri(context, uri, ContentType.VIDEO_UNSPECIFIED, dataSize)); + super(context, constructAttachmentFromUri(context, uri, ContentType.VIDEO_UNSPECIFIED, dataSize, false)); } public VideoSlide(Context context, Attachment attachment) { @@ -43,13 +39,12 @@ public class VideoSlide extends Slide { } @Override - @Nullable - public Uri getThumbnailUri() { - return null; + public boolean hasPlaceholder() { + return true; } @Override - public boolean hasPlaceholder() { + public boolean hasPlayOverlay() { return true; } @@ -68,7 +63,8 @@ public class VideoSlide extends Slide { return true; } - @NonNull @Override public String getContentDescription() { + @NonNull @Override + public String getContentDescription() { return context.getString(R.string.Slide_video); } } diff --git a/src/org/thoughtcrime/securesms/util/MediaUtil.java b/src/org/thoughtcrime/securesms/util/MediaUtil.java index 0d04f0b533..a9adbec6f3 100644 --- a/src/org/thoughtcrime/securesms/util/MediaUtil.java +++ b/src/org/thoughtcrime/securesms/util/MediaUtil.java @@ -23,11 +23,11 @@ import org.thoughtcrime.securesms.providers.PersistentBlobProvider; import java.io.IOException; import java.io.InputStream; -import java.util.concurrent.ExecutionException; import ws.com.google.android.mms.ContentType; public class MediaUtil { + private static final String TAG = MediaUtil.class.getSimpleName(); public static @Nullable ThumbnailData generateThumbnail(Context context, MasterSecret masterSecret, String contentType, Uri uri) diff --git a/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java b/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java index 38500086bf..85bfa6f063 100644 --- a/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java +++ b/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java @@ -5,6 +5,7 @@ import android.content.DialogInterface.OnClickListener; import android.media.MediaScannerConnection; import android.net.Uri; import android.os.Environment; +import android.support.annotation.NonNull; import android.support.v7.app.AlertDialog; import android.util.Log; import android.webkit.MimeTypeMap; @@ -158,7 +159,7 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask 0) { + int read = inputStream.read(buffer, 0, Util.toIntExact(Math.min((long)buffer.length, headerRemaining))); + + if (read == -1) return -1; + + headerRemaining -= read; + } + + int returnValue = inputStream.read(bytes, offset, length); + inputStream.close(); + return returnValue; + } + + @Override + public long getSize() throws IOException { + DecryptingPartInputStream inputStream = new DecryptingPartInputStream(mediaFile, masterSecret); + byte[] buffer = new byte[4096]; + long size = 0; + + int read; + + while ((read = inputStream.read(buffer)) != -1) { + size += read; + } + + return size; + } + + @Override + public void close() throws IOException { + + } +} diff --git a/src/org/thoughtcrime/securesms/video/VideoPlayer.java b/src/org/thoughtcrime/securesms/video/VideoPlayer.java index 6cb3bbeddd..47135bb5cb 100644 --- a/src/org/thoughtcrime/securesms/video/VideoPlayer.java +++ b/src/org/thoughtcrime/securesms/video/VideoPlayer.java @@ -5,13 +5,16 @@ import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.AttributeSet; +import android.util.Log; import android.widget.FrameLayout; import android.widget.MediaController; +import android.widget.Toast; import android.widget.VideoView; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.attachments.AttachmentServer; import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.mms.VideoSlide; import org.thoughtcrime.securesms.util.ViewUtil; @@ -19,6 +22,8 @@ import java.io.IOException; public class VideoPlayer extends FrameLayout { + private static final String TAG = VideoPlayer.class.getName(); + @NonNull private final VideoView videoView; @Nullable private AttachmentServer attachmentServer; @@ -45,10 +50,20 @@ public class VideoPlayer extends FrameLayout { this.attachmentServer.stop(); } - this.attachmentServer = new AttachmentServer(getContext(), masterSecret, videoSource.asAttachment()); - this.attachmentServer.start(); + if (videoSource.getUri() != null && PartAuthority.isLocalUri(videoSource.getUri())) { + Log.w(TAG, "Starting video attachment server for part provider Uri..."); + this.attachmentServer = new AttachmentServer(getContext(), masterSecret, videoSource.asAttachment()); + this.attachmentServer.start(); + + this.videoView.setVideoURI(this.attachmentServer.getUri()); + } else if (videoSource.getUri() != null) { + Log.w(TAG, "Playing video directly from non-local Uri..."); + this.videoView.setVideoURI(videoSource.getUri()); + } else { + Toast.makeText(getContext(), "Error playing video...", Toast.LENGTH_LONG).show(); + return; + } - this.videoView.setVideoURI(this.attachmentServer.getUri()); this.videoView.start(); }