Support for generating video thumbnails

// FREEBIE
This commit is contained in:
Moxie Marlinspike 2016-12-11 13:37:27 -08:00
parent 6308e6491a
commit a4c17e5325
30 changed files with 268 additions and 108 deletions

View File

@ -50,10 +50,10 @@ dependencies {
compile 'de.greenrobot:eventbus:2.4.0' compile 'de.greenrobot:eventbus:2.4.0'
compile 'pl.tajchert:waitingdots:0.1.0' compile 'pl.tajchert:waitingdots:0.1.0'
compile 'com.soundcloud.android:android-crop:0.9.10@aar' compile 'com.soundcloud.android:android-crop:0.9.10@aar'
compile 'com.android.support:appcompat-v7:25.0.1' compile 'com.android.support:appcompat-v7:24.2.1'
compile 'com.android.support:recyclerview-v7:25.0.1' compile 'com.android.support:recyclerview-v7:24.2.1'
compile 'com.android.support:design:25.0.1' compile 'com.android.support:design:24.2.1'
compile 'com.android.support:cardview-v7:25.0.1' compile 'com.android.support:cardview-v7:24.2.1'
compile 'com.melnykov:floatingactionbutton:1.3.0' compile 'com.melnykov:floatingactionbutton:1.3.0'
compile 'com.google.zxing:android-integration:3.1.0' compile 'com.google.zxing:android-integration:3.1.0'
compile ('com.android.support:support-v4-preferencefragment:1.0.0@aar'){ compile ('com.android.support:support-v4-preferencefragment:1.0.0@aar'){
@ -117,10 +117,10 @@ dependencyVerification {
'de.greenrobot:eventbus:61d743a748156a372024d083de763b9e91ac2dcb3f6a1cbc74995c7ddab6e968', 'de.greenrobot:eventbus:61d743a748156a372024d083de763b9e91ac2dcb3f6a1cbc74995c7ddab6e968',
'pl.tajchert:waitingdots:2835d49e0787dbcb606c5a60021ced66578503b1e9fddcd7a5ef0cd5f095ba2c', 'pl.tajchert:waitingdots:2835d49e0787dbcb606c5a60021ced66578503b1e9fddcd7a5ef0cd5f095ba2c',
'com.soundcloud.android:android-crop:ffd4b973cf6e97f7d64118a0dc088df50e9066fd5634fe6911dd0c0c5d346177', 'com.soundcloud.android:android-crop:ffd4b973cf6e97f7d64118a0dc088df50e9066fd5634fe6911dd0c0c5d346177',
'com.android.support:appcompat-v7:7fead560a22ea4b15848ce3000f312ef611fac0953bf90ca8710a72a1f6e36ea', 'com.android.support:appcompat-v7:ead7ac8011fb40676df8adc2856cae934edab55fc4444654c0ac6ea443736088',
'com.android.support:recyclerview-v7:803baba7be537ace8c5cb8a775e37547c22a04c4b028833796c45c26ec1deca2', 'com.android.support:recyclerview-v7:9077766a1a0f4e89528fbf9dcdf6d5880a8686f0266fa852d58d803beeef18fa',
'com.android.support:design:07a72eb68c888b38d7b78e450e1af8a84e571406e0cf911889e0645d5a41f1e4', 'com.android.support:design:89842bb1243507fe3079066ea4ea58795effe69cdf9a819e05274d21760adfc2',
'com.android.support:cardview-v7:50d88fae8cd1076cb90504d36ca5ee9df4018555c8f041bd28f43274c0fc9e1f', 'com.android.support:cardview-v7:2303b351686d1db060b5fcf1a9c709c79b4a54a85bfda0fb3c4849e244606ee1',
'com.melnykov:floatingactionbutton:15d58d4fac0f7a288d0e5301bbaf501a146f5b3f5921277811bf99bd3b397263', 'com.melnykov:floatingactionbutton:15d58d4fac0f7a288d0e5301bbaf501a146f5b3f5921277811bf99bd3b397263',
'com.google.zxing:android-integration:89e56aadf1164bd71e57949163c53abf90af368b51669c0d4a47a163335f95c4', 'com.google.zxing:android-integration:89e56aadf1164bd71e57949163c53abf90af368b51669c0d4a47a163335f95c4',
'com.android.support:support-v4-preferencefragment:5470f5872514a6226fa1fc6f4e000991f38805691c534cf0bd2778911fc773ad', 'com.android.support:support-v4-preferencefragment:5470f5872514a6226fa1fc6f4e000991f38805691c534cf0bd2778911fc773ad',
@ -136,10 +136,9 @@ dependencyVerification {
'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259', 'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259',
'cn.carbswang.android:NumberPickerView:18b3c316d62c7c277978a8d4ed57a5b8f4e943762264960f579a8a549c756729', 'cn.carbswang.android:NumberPickerView:18b3c316d62c7c277978a8d4ed57a5b8f4e943762264960f579a8a549c756729',
'com.google.android.gms:play-services-base:ef36e50fa5c0415ed41f74dd399a889efd2fa327c449036e140c7c3786aa0e1f', 'com.google.android.gms:play-services-base:ef36e50fa5c0415ed41f74dd399a889efd2fa327c449036e140c7c3786aa0e1f',
'com.android.support:support-annotations:bd94ab42c841db16fb480f4c65d33d297e544655ecc498b37c5cf33a0c5f1968', 'com.android.support:support-annotations:1e4d471c5378b283d95abfb128e7ed3c6b3cb19bb6f0c317a9b75e48e99365ff',
'com.android.support:support-compat:d04f15aa5f2ae9e8cb7d025bf02dfd4fd6f6800628ceb107e0589634c9e4e537', 'com.android.support:support-compat:8e4fe0078b68073e8f5bcb52aa5b6407fd456d47c51aa0f8e8d1e23c69da06c1',
'com.android.support:support-core-ui:29205ac978a1839d92be3d32db2385dac10f8688bba649e51650023c76de2f00', 'com.android.support:support-core-ui:ecc9184b7f438980e1c4a08b089d62dbc53ff90091f442d91fec27322a02c73c',
'com.android.support:transition:9fd1e6d27cb70b3c5cd19f842b48bbb05cb4e5c93a22372769c342523393e8ea',
'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a', 'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f', 'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f',
@ -156,18 +155,17 @@ dependencyVerification {
'com.squareup.okio:okio:5e1098bd3fdee4c3347f5ab815b40ba851e4ab1b348c5e49a5b0362f0ce6e978', 'com.squareup.okio:okio:5e1098bd3fdee4c3347f5ab815b40ba851e4ab1b348c5e49a5b0362f0ce6e978',
'com.fasterxml.jackson.core:jackson-annotations:0ca408c24202a7626ec8b861e99d85eca5e38b73311dd6dd12e3e9deecc3fe94', 'com.fasterxml.jackson.core:jackson-annotations:0ca408c24202a7626ec8b861e99d85eca5e38b73311dd6dd12e3e9deecc3fe94',
'com.fasterxml.jackson.core:jackson-core:cbf4604784b4de226262845447a1ad3bb38a6728cebe86562e2c5afada8be2c0', 'com.fasterxml.jackson.core:jackson-core:cbf4604784b4de226262845447a1ad3bb38a6728cebe86562e2c5afada8be2c0',
'com.android.support:support-v4:50da261acc4ca3d2dea9a43106bf65488711ca97b20a4daa095dba381c205c98', 'com.android.support:support-v4:cac2956f5c4bb363cc0ba824ac16ea2a687d1c305d434416a34772a5f9375ed7',
'com.android.support:support-media-compat:01cac57af687bed9a6cb4ce803bebd1b7e6b8469c14f1f9ac6b4596637ff73d6', 'com.android.support:support-media-compat:fa29a23eadd685631584b2c0c624a36e3bb79a33e257b00304501ad682fa2be3',
'com.android.support:support-core-utils:632c3750bd991da8b591f24a8916e74ca6063ae7f525f005c96981725c9bf491', 'com.android.support:support-core-utils:0fbc508e41dd6e8c634f310ee88452aaf8f48b6a843a369b115130b80d2fc05f',
'com.android.support:support-fragment:da47261a1d7c3d33e6e911335a7f4ce01135923bb221d3ab84625d005fa1969f', 'com.android.support:support-fragment:d8030f0bf0f64214a29dc4e14d5ccd225e59f66ed15eb37f3a5022e773dd1fda',
'com.android.support:support-vector-drawable:071ae3695bf8427d3cbfc8791492a3d9c804a4b111aa2a72fbfe7790ea268e5d', 'com.android.support:support-vector-drawable:6ee37a7f7b93c1df1294e6f6f97df3724ac989fcda0549faf677001085330548',
'com.android.support:animated-vector-drawable:70443a2857f9968c4e2c27c107657ce2291d774f8a50f03444e12ab637451175', 'com.android.support:animated-vector-drawable:5aa30f578e1daefb26bef0ce06414266fbb4cdf5d4259f42a92c7bd83dcd81b4',
] ]
} }
android { android {
compileSdkVersion 25 compileSdkVersion 24
buildToolsVersion '23.0.3' buildToolsVersion '23.0.3'
useLibrary 'org.apache.http.legacy' useLibrary 'org.apache.http.legacy'

Binary file not shown.

After

Width:  |  Height:  |  Size: 1023 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -10,6 +10,18 @@
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:contentDescription="@string/conversation_item__mms_image_description" /> android:contentDescription="@string/conversation_item__mms_image_description" />
<ImageView android:id="@+id/play_overlay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:clickable="false"
android:longClickable="false"
android:src="@drawable/ic_play_circle_outline_white_48dp"
android:tint="#77ffffff"
android:tintMode="src_in"
android:visibility="gone"/>
<ViewStub android:id="@+id/transfer_controls_stub" <ViewStub android:id="@+id/transfer_controls_stub"
android:layout_width="70dp" android:layout_width="70dp"
android:layout_height="70dp" android:layout_height="70dp"

View File

@ -1190,10 +1190,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
for (Slide slide : attachmentManager.buildSlideDeck().getSlides()) { for (Slide slide : attachmentManager.buildSlideDeck().getSlides()) {
if (slide.hasAudio()) drafts.add(new Draft(Draft.AUDIO, slide.getUri().toString())); if (slide.hasAudio() && slide.getUri() != null) drafts.add(new Draft(Draft.AUDIO, slide.getUri().toString()));
else if (slide.hasVideo()) drafts.add(new Draft(Draft.VIDEO, slide.getUri().toString())); else if (slide.hasVideo() && slide.getUri() != null) drafts.add(new Draft(Draft.VIDEO, slide.getUri().toString()));
else if (slide.hasLocation()) drafts.add(new Draft(Draft.LOCATION, ((LocationSlide)slide).getPlace().serialize())); else if (slide.hasLocation()) drafts.add(new Draft(Draft.LOCATION, ((LocationSlide)slide).getPlace().serialize()));
else if (slide.hasImage()) drafts.add(new Draft(Draft.IMAGE, slide.getUri().toString())); else if (slide.hasImage() && slide.getUri() != null) drafts.add(new Draft(Draft.IMAGE, slide.getUri().toString()));
} }
return drafts; return drafts;

View File

@ -358,7 +358,7 @@ public class ConversationFragment extends Fragment
SaveAttachmentTask.showWarningDialog(getActivity(), new DialogInterface.OnClickListener() { SaveAttachmentTask.showWarningDialog(getActivity(), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
for (Slide slide : message.getSlideDeck().getSlides()) { for (Slide slide : message.getSlideDeck().getSlides()) {
if (slide.hasImage() || slide.hasVideo() || slide.hasAudio()) { if ((slide.hasImage() || slide.hasVideo() || slide.hasAudio()) && slide.getUri() != null) {
SaveAttachmentTask saveTask = new SaveAttachmentTask(getActivity(), masterSecret); SaveAttachmentTask saveTask = new SaveAttachmentTask(getActivity(), masterSecret);
saveTask.execute(new Attachment(slide.getUri(), slide.getContentType(), message.getDateReceived())); saveTask.execute(new Attachment(slide.getUri(), slide.getContentType(), message.getDateReceived()));
return; return;

View File

@ -545,7 +545,7 @@ public class ConversationItem extends LinearLayout
intent.putExtra(MediaPreviewActivity.THREAD_ID_EXTRA, messageRecord.getThreadId()); intent.putExtra(MediaPreviewActivity.THREAD_ID_EXTRA, messageRecord.getThreadId());
context.startActivity(intent); context.startActivity(intent);
} else { } else if (slide.getUri() != null) {
AlertDialog.Builder builder = new AlertDialog.Builder(context); AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.ConversationItem_view_secure_media_question); builder.setTitle(R.string.ConversationItem_view_secure_media_question);
builder.setIconAttribute(R.attr.dialog_alert_icon); builder.setIconAttribute(R.attr.dialog_alert_icon);

View File

@ -87,21 +87,22 @@ public class ImageMediaAdapter extends CursorRecyclerViewAdapter<ViewHolder> {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Intent intent = new Intent(getContext(), MediaPreviewActivity.class); if (imageRecord.getAttachment().getDataUri() != null) {
intent.putExtra(MediaPreviewActivity.DATE_EXTRA, imageRecord.getDate()); Intent intent = new Intent(getContext(), MediaPreviewActivity.class);
intent.putExtra(MediaPreviewActivity.THREAD_ID_EXTRA, threadId); intent.putExtra(MediaPreviewActivity.DATE_EXTRA, imageRecord.getDate());
intent.putExtra(MediaPreviewActivity.THREAD_ID_EXTRA, threadId);
if (!TextUtils.isEmpty(imageRecord.getAddress())) { if (!TextUtils.isEmpty(imageRecord.getAddress())) {
Recipients recipients = RecipientFactory.getRecipientsFromString(getContext(), Recipients recipients = RecipientFactory.getRecipientsFromString(getContext(),
imageRecord.getAddress(), imageRecord.getAddress(),
true); true);
if (recipients != null && recipients.getPrimaryRecipient() != null) { if (recipients != null && recipients.getPrimaryRecipient() != null) {
intent.putExtra(MediaPreviewActivity.RECIPIENT_EXTRA, recipients.getPrimaryRecipient().getRecipientId()); 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);
} }
} }
} }

View File

@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.attachments; package org.thoughtcrime.securesms.attachments;
import android.graphics.Bitmap;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
@ -23,10 +22,6 @@ public abstract class Attachment {
@Nullable @Nullable
private final String relay; private final String relay;
// XXX - This shouldn't be here.
@Nullable
private Bitmap thumbnail;
public Attachment(@NonNull String contentType, int transferState, long size, public Attachment(@NonNull String contentType, int transferState, long size,
@Nullable String location, @Nullable String key, @Nullable String relay) @Nullable String location, @Nullable String key, @Nullable String relay)
{ {
@ -76,13 +71,4 @@ public abstract class Attachment {
public String getRelay() { public String getRelay() {
return relay; return relay;
} }
public void setThumbnail(@Nullable Bitmap thumbnail) {
this.thumbnail = thumbnail;
}
@Nullable
public Bitmap getThumbnail() {
return thumbnail;
}
} }

View File

@ -1,7 +1,7 @@
package org.thoughtcrime.securesms.attachments; package org.thoughtcrime.securesms.attachments;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.NonNull; import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.mms.PartAuthority;
@ -10,27 +10,38 @@ public class DatabaseAttachment extends Attachment {
private final AttachmentId attachmentId; private final AttachmentId attachmentId;
private final long mmsId; private final long mmsId;
private final boolean hasData; 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 contentType, int transferProgress, long size,
String location, String key, String relay) String location, String key, String relay)
{ {
super(contentType, transferProgress, size, location, key, relay); super(contentType, transferProgress, size, location, key, relay);
this.attachmentId = attachmentId; this.attachmentId = attachmentId;
this.hasData = hasData; this.hasData = hasData;
this.hasThumbnail = hasThumbnail;
this.mmsId = mmsId; this.mmsId = mmsId;
} }
@Override @Override
@NonNull @Nullable
public Uri getDataUri() { public Uri getDataUri() {
return PartAuthority.getAttachmentDataUri(attachmentId); if (hasData) {
return PartAuthority.getAttachmentDataUri(attachmentId);
} else {
return null;
}
} }
@Override @Override
@NonNull @Nullable
public Uri getThumbnailUri() { public Uri getThumbnailUri() {
return PartAuthority.getAttachmentThumbnailUri(attachmentId); if (hasThumbnail) {
return PartAuthority.getAttachmentThumbnailUri(attachmentId);
} else {
return null;
}
} }
public AttachmentId getAttachmentId() { public AttachmentId getAttachmentId() {
@ -56,4 +67,8 @@ public class DatabaseAttachment extends Attachment {
public boolean hasData() { public boolean hasData() {
return hasData; return hasData;
} }
public boolean hasThumbnail() {
return hasThumbnail;
}
} }

View File

@ -1,26 +1,19 @@
package org.thoughtcrime.securesms.attachments; package org.thoughtcrime.securesms.attachments;
import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
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;
public class UriAttachment extends Attachment { public class UriAttachment extends Attachment {
private final @NonNull Uri dataUri; private final @NonNull Uri dataUri;
private final @NonNull Uri thumbnailUri; private final @Nullable Uri thumbnailUri;
public UriAttachment(@NonNull Uri uri, @NonNull String contentType, int transferState, long size) { public UriAttachment(@NonNull Uri uri, @NonNull String contentType, int transferState, long size) {
this(uri, uri, contentType, transferState, 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) @NonNull String contentType, int transferState, long size)
{ {
super(contentType, transferState, size, null, null, null); super(contentType, transferState, size, null, null, null);
@ -35,7 +28,7 @@ public class UriAttachment extends Attachment {
} }
@Override @Override
@NonNull @Nullable
public Uri getThumbnailUri() { public Uri getThumbnailUri() {
return thumbnailUri; return thumbnailUri;
} }

View File

@ -36,6 +36,7 @@ public class ThumbnailView extends FrameLayout {
private static final String TAG = ThumbnailView.class.getSimpleName(); private static final String TAG = ThumbnailView.class.getSimpleName();
private ImageView image; private ImageView image;
private ImageView playOverlay;
private int backgroundColorHint; private int backgroundColorHint;
private int radius; private int radius;
private OnClickListener parentClickListener; private OnClickListener parentClickListener;
@ -58,8 +59,9 @@ public class ThumbnailView extends FrameLayout {
inflate(context, R.layout.thumbnail_view, this); inflate(context, R.layout.thumbnail_view, this);
this.radius = getResources().getDimensionPixelSize(R.dimen.message_bubble_corner_radius); this.radius = getResources().getDimensionPixelSize(R.dimen.message_bubble_corner_radius);
this.image = (ImageView) findViewById(R.id.thumbnail_image); this.image = (ImageView) findViewById(R.id.thumbnail_image);
this.playOverlay = (ImageView) findViewById(R.id.play_overlay);
super.setOnClickListener(new ThumbnailClickDispatcher()); super.setOnClickListener(new ThumbnailClickDispatcher());
if (attrs != null) { if (attrs != null) {
@ -105,6 +107,12 @@ public class ThumbnailView extends FrameLayout {
getTransferControls().setVisibility(View.GONE); 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)) { if (Util.equals(slide, this.slide)) {
Log.w(TAG, "Not re-loading slide " + slide.asAttachment().getDataUri()); Log.w(TAG, "Not re-loading slide " + slide.asAttachment().getDataUri());
return; return;

View File

@ -21,7 +21,10 @@ import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;
import android.media.MediaMetadataRetriever;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting; 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;
import org.thoughtcrime.securesms.util.MediaUtil.ThumbnailData; import org.thoughtcrime.securesms.util.MediaUtil.ThumbnailData;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.video.EncryptedMediaDataSource;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@ -54,6 +58,7 @@ import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.MmsException; import ws.com.google.android.mms.MmsException;
public class AttachmentDatabase extends Database { public class AttachmentDatabase extends Database {
@ -71,7 +76,7 @@ public class AttachmentDatabase extends Database {
static final String DATA = "_data"; static final String DATA = "_data";
static final String TRANSFER_STATE = "pending_push"; static final String TRANSFER_STATE = "pending_push";
static final String SIZE = "data_size"; 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 THUMBNAIL_ASPECT_RATIO = "aspect_ratio";
static final String UNIQUE_ID = "unique_id"; 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, private static final String[] PROJECTION = new String[] {ROW_ID + " AS " + ATTACHMENT_ID_ALIAS,
MMS_ID, CONTENT_TYPE, NAME, CONTENT_DISPOSITION, MMS_ID, CONTENT_TYPE, NAME, CONTENT_DISPOSITION,
CONTENT_LOCATION, DATA, TRANSFER_STATE, CONTENT_LOCATION, DATA, THUMBNAIL, TRANSFER_STATE,
SIZE, THUMBNAIL, THUMBNAIL_ASPECT_RATIO, SIZE, THUMBNAIL, THUMBNAIL_ASPECT_RATIO,
UNIQUE_ID}; UNIQUE_ID};
@ -313,6 +318,7 @@ public class AttachmentDatabase extends Database {
return new DatabaseAttachment(databaseAttachment.getAttachmentId(), return new DatabaseAttachment(databaseAttachment.getAttachmentId(),
databaseAttachment.getMmsId(), databaseAttachment.getMmsId(),
databaseAttachment.hasData(), databaseAttachment.hasData(),
databaseAttachment.hasThumbnail(),
mediaStream.getMimeType(), mediaStream.getMimeType(),
databaseAttachment.getTransferState(), databaseAttachment.getTransferState(),
dataSize, dataSize,
@ -434,6 +440,7 @@ public class AttachmentDatabase extends Database {
cursor.getLong(cursor.getColumnIndexOrThrow(UNIQUE_ID))), cursor.getLong(cursor.getColumnIndexOrThrow(UNIQUE_ID))),
cursor.getLong(cursor.getColumnIndexOrThrow(MMS_ID)), cursor.getLong(cursor.getColumnIndexOrThrow(MMS_ID)),
!cursor.isNull(cursor.getColumnIndexOrThrow(DATA)), !cursor.isNull(cursor.getColumnIndexOrThrow(DATA)),
!cursor.isNull(cursor.getColumnIndexOrThrow(THUMBNAIL)),
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_TYPE)), cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_TYPE)),
cursor.getInt(cursor.getColumnIndexOrThrow(TRANSFER_STATE)), cursor.getInt(cursor.getColumnIndexOrThrow(TRANSFER_STATE)),
cursor.getLong(cursor.getColumnIndexOrThrow(SIZE)), cursor.getLong(cursor.getColumnIndexOrThrow(SIZE)),
@ -474,11 +481,8 @@ public class AttachmentDatabase extends Database {
long rowId = database.insert(TABLE_NAME, null, contentValues); long rowId = database.insert(TABLE_NAME, null, contentValues);
AttachmentId attachmentId = new AttachmentId(rowId, uniqueId); AttachmentId attachmentId = new AttachmentId(rowId, uniqueId);
if (attachment.getThumbnail() != null && masterSecret.getMasterSecret().isPresent()) { if (partData != null) {
Log.w(TAG, "inserting pre-generated thumbnail"); Log.w(TAG, "Submitting thumbnail generation job...");
ThumbnailData data = new ThumbnailData(attachment.getThumbnail());
updateAttachmentThumbnail(masterSecret.getMasterSecret().get(), attachmentId, data.toDataStream(), data.getAspectRatio());
} else if (!attachment.isInProgress()) {
thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret.getMasterSecret().get(), attachmentId)); thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret.getMasterSecret().get(), attachmentId));
} }
@ -501,21 +505,33 @@ public class AttachmentDatabase extends Database {
values.put(THUMBNAIL_ASPECT_RATIO, aspectRatio); values.put(THUMBNAIL_ASPECT_RATIO, aspectRatio);
database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings()); 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 @VisibleForTesting
class ThumbnailFetchCallable implements Callable<InputStream> { class ThumbnailFetchCallable implements Callable<InputStream> {
private final MasterSecret masterSecret; private final MasterSecret masterSecret;
private final AttachmentId attachmentId; private final AttachmentId attachmentId;
public ThumbnailFetchCallable(MasterSecret masterSecret, AttachmentId attachmentId) { ThumbnailFetchCallable(MasterSecret masterSecret, AttachmentId attachmentId) {
this.masterSecret = masterSecret; this.masterSecret = masterSecret;
this.attachmentId = attachmentId; this.attachmentId = attachmentId;
} }
@Override @Override
public @Nullable InputStream call() throws Exception { public @Nullable InputStream call() throws Exception {
Log.w(TAG, "Executing thumbnail job...");
final InputStream stream = getDataStream(masterSecret, attachmentId, THUMBNAIL); final InputStream stream = getDataStream(masterSecret, attachmentId, THUMBNAIL);
if (stream != null) { if (stream != null) {
@ -528,7 +544,13 @@ public class AttachmentDatabase extends Database {
return null; 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) { if (data == null) {
return null; return null;
@ -538,5 +560,28 @@ public class AttachmentDatabase extends Database {
return getDataStream(masterSecret, attachmentId, THUMBNAIL); 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);
}
} }
} }

View File

@ -19,6 +19,7 @@ public class ImageDatabase extends Database {
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.TRANSFER_STATE + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.TRANSFER_STATE + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.SIZE + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.SIZE + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DATA + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DATA + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.THUMBNAIL + ", "
+ MmsDatabase.TABLE_NAME + "." + MmsDatabase.MESSAGE_BOX + ", " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.MESSAGE_BOX + ", "
+ MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_SENT + ", " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_SENT + ", "
+ MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_RECEIVED + ", " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_RECEIVED + ", "
@ -47,19 +48,22 @@ public class ImageDatabase extends Database {
private final AttachmentId attachmentId; private final AttachmentId attachmentId;
private final long mmsId; private final long mmsId;
private final boolean hasData; private final boolean hasData;
private final boolean hasThumbnail;
private final String contentType; private final String contentType;
private final String address; private final String address;
private final long date; private final long date;
private final int transferState; private final int transferState;
private final long size; 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, String contentType, String address, long date,
int transferState, long size) int transferState, long size)
{ {
this.attachmentId = attachmentId; this.attachmentId = attachmentId;
this.mmsId = mmsId; this.mmsId = mmsId;
this.hasData = hasData; this.hasData = hasData;
this.hasThumbnail = hasThumbnail;
this.contentType = contentType; this.contentType = contentType;
this.address = address; this.address = address;
this.date = date; this.date = date;
@ -82,6 +86,7 @@ public class ImageDatabase extends Database {
return new ImageRecord(attachmentId, return new ImageRecord(attachmentId,
cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.MMS_ID)), cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.MMS_ID)),
!cursor.isNull(cursor.getColumnIndexOrThrow(AttachmentDatabase.DATA)), !cursor.isNull(cursor.getColumnIndexOrThrow(AttachmentDatabase.DATA)),
!cursor.isNull(cursor.getColumnIndexOrThrow(AttachmentDatabase.THUMBNAIL)),
cursor.getString(cursor.getColumnIndexOrThrow(AttachmentDatabase.CONTENT_TYPE)), cursor.getString(cursor.getColumnIndexOrThrow(AttachmentDatabase.CONTENT_TYPE)),
cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS)), cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS)),
date, date,
@ -90,7 +95,7 @@ public class ImageDatabase extends Database {
} }
public Attachment getAttachment() { 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() { public String getContentType() {

View File

@ -138,6 +138,7 @@ public class MmsDatabase extends MessagingDatabase {
AttachmentDatabase.MMS_ID, AttachmentDatabase.MMS_ID,
AttachmentDatabase.SIZE, AttachmentDatabase.SIZE,
AttachmentDatabase.DATA, AttachmentDatabase.DATA,
AttachmentDatabase.THUMBNAIL,
AttachmentDatabase.CONTENT_TYPE, AttachmentDatabase.CONTENT_TYPE,
AttachmentDatabase.CONTENT_LOCATION, AttachmentDatabase.CONTENT_LOCATION,
AttachmentDatabase.CONTENT_DISPOSITION, AttachmentDatabase.CONTENT_DISPOSITION,
@ -672,6 +673,7 @@ public class MmsDatabase extends MessagingDatabase {
attachments.add(new DatabaseAttachment(databaseAttachment.getAttachmentId(), attachments.add(new DatabaseAttachment(databaseAttachment.getAttachmentId(),
databaseAttachment.getMmsId(), databaseAttachment.getMmsId(),
databaseAttachment.hasData(), databaseAttachment.hasData(),
databaseAttachment.hasThumbnail(),
databaseAttachment.getContentType(), databaseAttachment.getContentType(),
AttachmentDatabase.TRANSFER_PROGRESS_DONE, AttachmentDatabase.TRANSFER_PROGRESS_DONE,
databaseAttachment.getSize(), databaseAttachment.getSize(),

View File

@ -62,6 +62,7 @@ public class MmsSmsDatabase extends Database {
AttachmentDatabase.MMS_ID, AttachmentDatabase.MMS_ID,
AttachmentDatabase.SIZE, AttachmentDatabase.SIZE,
AttachmentDatabase.DATA, AttachmentDatabase.DATA,
AttachmentDatabase.THUMBNAIL,
AttachmentDatabase.CONTENT_TYPE, AttachmentDatabase.CONTENT_TYPE,
AttachmentDatabase.CONTENT_LOCATION, AttachmentDatabase.CONTENT_LOCATION,
AttachmentDatabase.CONTENT_DISPOSITION, AttachmentDatabase.CONTENT_DISPOSITION,
@ -153,6 +154,7 @@ public class MmsSmsDatabase extends Database {
AttachmentDatabase.MMS_ID, AttachmentDatabase.MMS_ID,
AttachmentDatabase.SIZE, AttachmentDatabase.SIZE,
AttachmentDatabase.DATA, AttachmentDatabase.DATA,
AttachmentDatabase.THUMBNAIL,
AttachmentDatabase.CONTENT_TYPE, AttachmentDatabase.CONTENT_TYPE,
AttachmentDatabase.CONTENT_LOCATION, AttachmentDatabase.CONTENT_LOCATION,
AttachmentDatabase.CONTENT_DISPOSITION, AttachmentDatabase.CONTENT_DISPOSITION,
@ -178,6 +180,7 @@ public class MmsSmsDatabase extends Database {
AttachmentDatabase.MMS_ID, AttachmentDatabase.MMS_ID,
AttachmentDatabase.SIZE, AttachmentDatabase.SIZE,
AttachmentDatabase.DATA, AttachmentDatabase.DATA,
AttachmentDatabase.THUMBNAIL,
AttachmentDatabase.CONTENT_TYPE, AttachmentDatabase.CONTENT_TYPE,
AttachmentDatabase.CONTENT_LOCATION, AttachmentDatabase.CONTENT_LOCATION,
AttachmentDatabase.CONTENT_DISPOSITION, AttachmentDatabase.CONTENT_DISPOSITION,
@ -226,7 +229,10 @@ public class MmsSmsDatabase extends Database {
mmsColumnsPresent.add(AttachmentDatabase.ROW_ID); mmsColumnsPresent.add(AttachmentDatabase.ROW_ID);
mmsColumnsPresent.add(AttachmentDatabase.UNIQUE_ID); mmsColumnsPresent.add(AttachmentDatabase.UNIQUE_ID);
mmsColumnsPresent.add(AttachmentDatabase.MMS_ID);
mmsColumnsPresent.add(AttachmentDatabase.SIZE); mmsColumnsPresent.add(AttachmentDatabase.SIZE);
mmsColumnsPresent.add(AttachmentDatabase.DATA);
mmsColumnsPresent.add(AttachmentDatabase.THUMBNAIL);
mmsColumnsPresent.add(AttachmentDatabase.CONTENT_TYPE); mmsColumnsPresent.add(AttachmentDatabase.CONTENT_TYPE);
mmsColumnsPresent.add(AttachmentDatabase.CONTENT_LOCATION); mmsColumnsPresent.add(AttachmentDatabase.CONTENT_LOCATION);
mmsColumnsPresent.add(AttachmentDatabase.CONTENT_DISPOSITION); mmsColumnsPresent.add(AttachmentDatabase.CONTENT_DISPOSITION);

View File

@ -328,7 +328,7 @@ public class AttachmentManager {
} }
private void previewImageDraft(final @NonNull Slide slide) { 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 intent = new Intent(context, MediaPreviewActivity.class);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(slide.getUri(), slide.getContentType()); intent.setDataAndType(slide.getUri(), slide.getContentType());

View File

@ -37,11 +37,11 @@ import ws.com.google.android.mms.pdu.PduPart;
public class AudioSlide extends Slide { public class AudioSlide extends Slide {
public AudioSlide(Context context, Uri uri, long dataSize) { 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) { 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) { public AudioSlide(Context context, Attachment attachment) {

View File

@ -20,7 +20,7 @@ public class GifSlide extends ImageSlide {
} }
public GifSlide(Context context, Uri uri, long size) { 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 @Override

View File

@ -25,8 +25,6 @@ import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.Attachment;
import java.io.IOException;
import ws.com.google.android.mms.ContentType; import ws.com.google.android.mms.ContentType;
public class ImageSlide extends Slide { public class ImageSlide extends Slide {
@ -38,7 +36,7 @@ public class ImageSlide extends Slide {
} }
public ImageSlide(Context context, Uri uri, long size) { 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 @Override
@ -51,7 +49,9 @@ public class ImageSlide extends Slide {
return true; return true;
} }
@NonNull @Override public String getContentDescription() { @NonNull
@Override
public String getContentDescription() {
return context.getString(R.string.Slide_image); return context.getString(R.string.Slide_image);
} }
} }

View File

@ -103,13 +103,18 @@ public abstract class Slide {
return false; return false;
} }
public boolean hasPlayOverlay() {
return false;
}
protected static Attachment constructAttachmentFromUri(@NonNull Context context, protected static Attachment constructAttachmentFromUri(@NonNull Context context,
@NonNull Uri uri, @NonNull Uri uri,
@NonNull String defaultMime, @NonNull String defaultMime,
long size) long size,
boolean hasThumbnail)
{ {
Optional<String> resolvedType = Optional.fromNullable(MediaUtil.getMimeType(context, uri)); Optional<String> 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 @Override

View File

@ -21,21 +21,17 @@ import android.content.res.Resources.Theme;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.DrawableRes; import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.util.ResUtil; import org.thoughtcrime.securesms.util.ResUtil;
import java.io.IOException;
import ws.com.google.android.mms.ContentType; import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.pdu.PduPart;
public class VideoSlide extends Slide { public class VideoSlide extends Slide {
public VideoSlide(Context context, Uri uri, long dataSize) { 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) { public VideoSlide(Context context, Attachment attachment) {
@ -43,13 +39,12 @@ public class VideoSlide extends Slide {
} }
@Override @Override
@Nullable public boolean hasPlaceholder() {
public Uri getThumbnailUri() { return true;
return null;
} }
@Override @Override
public boolean hasPlaceholder() { public boolean hasPlayOverlay() {
return true; return true;
} }
@ -68,7 +63,8 @@ public class VideoSlide extends Slide {
return true; return true;
} }
@NonNull @Override public String getContentDescription() { @NonNull @Override
public String getContentDescription() {
return context.getString(R.string.Slide_video); return context.getString(R.string.Slide_video);
} }
} }

View File

@ -23,11 +23,11 @@ import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.concurrent.ExecutionException;
import ws.com.google.android.mms.ContentType; import ws.com.google.android.mms.ContentType;
public class MediaUtil { public class MediaUtil {
private static final String TAG = MediaUtil.class.getSimpleName(); private static final String TAG = MediaUtil.class.getSimpleName();
public static @Nullable ThumbnailData generateThumbnail(Context context, MasterSecret masterSecret, String contentType, Uri uri) public static @Nullable ThumbnailData generateThumbnail(Context context, MasterSecret masterSecret, String contentType, Uri uri)

View File

@ -5,6 +5,7 @@ import android.content.DialogInterface.OnClickListener;
import android.media.MediaScannerConnection; import android.media.MediaScannerConnection;
import android.net.Uri; import android.net.Uri;
import android.os.Environment; import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.util.Log; import android.util.Log;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
@ -158,7 +159,7 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
public String contentType; public String contentType;
public long date; public long date;
public Attachment(Uri uri, String contentType, long date) { public Attachment(@NonNull Uri uri, @NonNull String contentType, long date) {
if (uri == null || contentType == null || date < 0) { if (uri == null || contentType == null || date < 0) {
throw new AssertionError("uri, content type, and date must all be specified"); throw new AssertionError("uri, content type, and date must all be specified");
} }

View File

@ -447,4 +447,12 @@ public class Util {
clipboardManager.setText(text); clipboardManager.setText(text);
} }
} }
public static int toIntExact(long value) {
if ((int)value != value) {
throw new ArithmeticException("integer overflow");
}
return (int)value;
}
} }

View File

@ -0,0 +1,64 @@
package org.thoughtcrime.securesms.video;
import android.media.MediaDataSource;
import android.os.Build;
import android.support.annotation.RequiresApi;
import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.util.Util;
import java.io.File;
import java.io.IOException;
@RequiresApi(api = Build.VERSION_CODES.M)
public class EncryptedMediaDataSource extends MediaDataSource {
private final File mediaFile;
private final MasterSecret masterSecret;
public EncryptedMediaDataSource(MasterSecret masterSecret, File mediaFile) {
this.mediaFile = mediaFile;
this.masterSecret = masterSecret;
}
@Override
public int readAt(long position, byte[] bytes, int offset, int length) throws IOException {
DecryptingPartInputStream inputStream = new DecryptingPartInputStream(mediaFile, masterSecret);
byte[] buffer = new byte[4096];
long headerRemaining = position;
while (headerRemaining > 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 {
}
}

View File

@ -5,13 +5,16 @@ import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.MediaController; import android.widget.MediaController;
import android.widget.Toast;
import android.widget.VideoView; import android.widget.VideoView;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.attachments.AttachmentServer; import org.thoughtcrime.securesms.attachments.AttachmentServer;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.mms.VideoSlide; import org.thoughtcrime.securesms.mms.VideoSlide;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
@ -19,6 +22,8 @@ import java.io.IOException;
public class VideoPlayer extends FrameLayout { public class VideoPlayer extends FrameLayout {
private static final String TAG = VideoPlayer.class.getName();
@NonNull private final VideoView videoView; @NonNull private final VideoView videoView;
@Nullable private AttachmentServer attachmentServer; @Nullable private AttachmentServer attachmentServer;
@ -45,10 +50,20 @@ public class VideoPlayer extends FrameLayout {
this.attachmentServer.stop(); this.attachmentServer.stop();
} }
this.attachmentServer = new AttachmentServer(getContext(), masterSecret, videoSource.asAttachment()); if (videoSource.getUri() != null && PartAuthority.isLocalUri(videoSource.getUri())) {
this.attachmentServer.start(); 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(); this.videoView.start();
} }