From 8e7c7a9c54076e9417eab49ce4d1ce061e5e02de Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Wed, 19 Apr 2017 21:23:57 -0700 Subject: [PATCH] Improve video thumbnail generation and handling on send side For direct attach only // FREEBIE --- .../securesms/ConversationItem.java | 2 +- .../thoughtcrime/securesms/MediaAdapter.java | 2 +- .../securesms/components/ThumbnailView.java | 6 ++-- .../database/AttachmentDatabase.java | 18 ++++++++++-- .../securesms/mms/AttachmentManager.java | 2 +- .../mms/DecryptableStreamLocalUriFetcher.java | 14 +++++++++ .../securesms/mms/VideoSlide.java | 3 +- .../securesms/util/MediaUtil.java | 29 +++++++++++++++++++ 8 files changed, 68 insertions(+), 8 deletions(-) diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java index fa0b4d6ef2..2cb63e3f75 100644 --- a/src/org/thoughtcrime/securesms/ConversationItem.java +++ b/src/org/thoughtcrime/securesms/ConversationItem.java @@ -355,7 +355,7 @@ public class ConversationItem extends LinearLayout //noinspection ConstantConditions mediaThumbnailStub.get().setImageResource(masterSecret, ((MmsMessageRecord)messageRecord).getSlideDeck().getThumbnailSlide(), - showControls); + showControls, false); mediaThumbnailStub.get().setThumbnailClickListener(new ThumbnailClickListener()); mediaThumbnailStub.get().setDownloadClickListener(downloadClickListener); mediaThumbnailStub.get().setOnLongClickListener(passthroughClickListener); diff --git a/src/org/thoughtcrime/securesms/MediaAdapter.java b/src/org/thoughtcrime/securesms/MediaAdapter.java index 140aacabb5..83769b1319 100644 --- a/src/org/thoughtcrime/securesms/MediaAdapter.java +++ b/src/org/thoughtcrime/securesms/MediaAdapter.java @@ -72,7 +72,7 @@ public class MediaAdapter extends CursorRecyclerViewAdapter { Slide slide = MediaUtil.getSlideForAttachment(getContext(), mediaRecord.getAttachment()); if (slide != null) { - imageView.setImageResource(masterSecret, slide, false); + imageView.setImageResource(masterSecret, slide, false, false); } imageView.setOnClickListener(new OnMediaClickListener(mediaRecord)); diff --git a/src/org/thoughtcrime/securesms/components/ThumbnailView.java b/src/org/thoughtcrime/securesms/components/ThumbnailView.java index ec30896f0c..542f2e271c 100644 --- a/src/org/thoughtcrime/securesms/components/ThumbnailView.java +++ b/src/org/thoughtcrime/securesms/components/ThumbnailView.java @@ -99,7 +99,7 @@ public class ThumbnailView extends FrameLayout { this.backgroundColorHint = color; } - public void setImageResource(@NonNull MasterSecret masterSecret, @NonNull Slide slide, boolean showControls) { + public void setImageResource(@NonNull MasterSecret masterSecret, @NonNull Slide slide, boolean showControls, boolean isPreview) { if (showControls) { getTransferControls().setSlide(slide); getTransferControls().setDownloadClickListener(new DownloadClickDispatcher()); @@ -107,7 +107,9 @@ public class ThumbnailView extends FrameLayout { getTransferControls().setVisibility(View.GONE); } - if (slide.getThumbnailUri() != null && slide.hasPlayOverlay() && slide.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_DONE) { + if (slide.getThumbnailUri() != null && slide.hasPlayOverlay() && + (slide.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_DONE || isPreview)) + { this.playOverlay.setVisibility(View.VISIBLE); } else { this.playOverlay.setVisibility(View.GONE); diff --git a/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java b/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java index 2b18822cc8..ecf9d84305 100644 --- a/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java +++ b/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java @@ -50,6 +50,8 @@ import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.video.EncryptedMediaDataSource; import org.whispersystems.libsignal.InvalidMessageException; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -527,8 +529,20 @@ public class AttachmentDatabase extends Database { AttachmentId attachmentId = new AttachmentId(rowId, uniqueId); if (partData != null) { - Log.w(TAG, "Submitting thumbnail generation job..."); - thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret.getMasterSecret().get(), attachmentId)); + if (MediaUtil.hasVideoThumbnail(attachment.getDataUri())) { + Bitmap bitmap = MediaUtil.getVideoThumbnail(context, attachment.getDataUri()); + + if (bitmap != null) { + ThumbnailData thumbnailData = new ThumbnailData(bitmap); + updateAttachmentThumbnail(masterSecret.getMasterSecret().get(), attachmentId, thumbnailData.toDataStream(), thumbnailData.getAspectRatio()); + } else { + Log.w(TAG, "Retrieving video thumbnail failed, submitting thumbnail generation job..."); + thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret.getMasterSecret().get(), attachmentId)); + } + } else { + Log.w(TAG, "Submitting thumbnail generation job..."); + thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret.getMasterSecret().get(), attachmentId)); + } } return attachmentId; diff --git a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java index 438f5d2943..0cf2054fce 100644 --- a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -247,7 +247,7 @@ public class AttachmentManager { documentView.setDocument((DocumentSlide) slide, false); removableMediaView.display(documentView, false); } else { - thumbnail.setImageResource(masterSecret, slide, false); + thumbnail.setImageResource(masterSecret, slide, false, true); removableMediaView.display(thumbnail, mediaType == MediaType.IMAGE); } diff --git a/src/org/thoughtcrime/securesms/mms/DecryptableStreamLocalUriFetcher.java b/src/org/thoughtcrime/securesms/mms/DecryptableStreamLocalUriFetcher.java index f5296c93f4..f87f12232f 100644 --- a/src/org/thoughtcrime/securesms/mms/DecryptableStreamLocalUriFetcher.java +++ b/src/org/thoughtcrime/securesms/mms/DecryptableStreamLocalUriFetcher.java @@ -2,13 +2,17 @@ package org.thoughtcrime.securesms.mms; import android.content.ContentResolver; import android.content.Context; +import android.graphics.Bitmap; import android.net.Uri; import android.util.Log; import com.bumptech.glide.load.data.StreamLocalUriFetcher; import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.util.MediaUtil; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -28,6 +32,16 @@ public class DecryptableStreamLocalUriFetcher extends StreamLocalUriFetcher { @Override protected InputStream loadResource(Uri uri, ContentResolver contentResolver) throws FileNotFoundException { + if (MediaUtil.hasVideoThumbnail(uri)) { + Bitmap thumbnail = MediaUtil.getVideoThumbnail(context, uri); + + if (thumbnail != null) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + thumbnail.compress(Bitmap.CompressFormat.JPEG, 100, baos); + return new ByteArrayInputStream(baos.toByteArray()); + } + } + try { return PartAuthority.getAttachmentStream(context, masterSecret, uri); } catch (IOException ioe) { diff --git a/src/org/thoughtcrime/securesms/mms/VideoSlide.java b/src/org/thoughtcrime/securesms/mms/VideoSlide.java index d5f85f0d98..2b24701d6d 100644 --- a/src/org/thoughtcrime/securesms/mms/VideoSlide.java +++ b/src/org/thoughtcrime/securesms/mms/VideoSlide.java @@ -24,6 +24,7 @@ import android.support.annotation.NonNull; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.attachments.Attachment; +import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.ResUtil; import ws.com.google.android.mms.ContentType; @@ -31,7 +32,7 @@ import ws.com.google.android.mms.ContentType; public class VideoSlide extends Slide { public VideoSlide(Context context, Uri uri, long dataSize) { - super(context, constructAttachmentFromUri(context, uri, ContentType.VIDEO_UNSPECIFIED, dataSize, false, null)); + super(context, constructAttachmentFromUri(context, uri, ContentType.VIDEO_UNSPECIFIED, dataSize, MediaUtil.hasVideoThumbnail(uri), null)); } public VideoSlide(Context context, Attachment attachment) { diff --git a/src/org/thoughtcrime/securesms/util/MediaUtil.java b/src/org/thoughtcrime/securesms/util/MediaUtil.java index 621fe5289e..ede01923d3 100644 --- a/src/org/thoughtcrime/securesms/util/MediaUtil.java +++ b/src/org/thoughtcrime/securesms/util/MediaUtil.java @@ -1,8 +1,10 @@ package org.thoughtcrime.securesms.util; +import android.content.ContentResolver; import android.content.Context; import android.graphics.Bitmap; import android.net.Uri; +import android.provider.MediaStore; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; @@ -162,6 +164,33 @@ public class MediaUtil { return !TextUtils.isEmpty(contentType) && contentType.trim().startsWith("video/"); } + public static boolean hasVideoThumbnail(Uri uri) { + Log.w(TAG, "Checking: " + uri); + + if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { + return false; + } + + if ("com.android.providers.media.documents".equals(uri.getAuthority())) { + return uri.getLastPathSegment().contains("video"); + } + + return false; + } + + public static @Nullable Bitmap getVideoThumbnail(Context context, Uri uri) { + if ("com.android.providers.media.documents".equals(uri.getAuthority())) { + long videoId = Long.parseLong(uri.getLastPathSegment().split(":")[1]); + + return MediaStore.Video.Thumbnails.getThumbnail(context.getContentResolver(), + videoId, + MediaStore.Images.Thumbnails.MINI_KIND, + null); + } + + return null; + } + public static @Nullable String getDiscreteMimeType(@NonNull String mimeType) { final String[] sections = mimeType.split("/", 2); return sections.length > 1 ? sections[0] : null;