mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-13 13:32:45 +00:00
Support for generating video thumbnails
// FREEBIE
This commit is contained in:
@@ -1190,10 +1190,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
for (Slide slide : attachmentManager.buildSlideDeck().getSlides()) {
|
||||
if (slide.hasAudio()) 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.hasLocation()) drafts.add(new Draft(Draft.LOCATION, ((LocationSlide)slide).getPlace().serialize()));
|
||||
else if (slide.hasImage()) drafts.add(new Draft(Draft.IMAGE, slide.getUri().toString()));
|
||||
if (slide.hasAudio() && slide.getUri() != null) drafts.add(new Draft(Draft.AUDIO, 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.hasImage() && slide.getUri() != null) drafts.add(new Draft(Draft.IMAGE, slide.getUri().toString()));
|
||||
}
|
||||
|
||||
return drafts;
|
||||
|
||||
@@ -358,7 +358,7 @@ public class ConversationFragment extends Fragment
|
||||
SaveAttachmentTask.showWarningDialog(getActivity(), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
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);
|
||||
saveTask.execute(new Attachment(slide.getUri(), slide.getContentType(), message.getDateReceived()));
|
||||
return;
|
||||
|
||||
@@ -545,7 +545,7 @@ public class ConversationItem extends LinearLayout
|
||||
intent.putExtra(MediaPreviewActivity.THREAD_ID_EXTRA, messageRecord.getThreadId());
|
||||
|
||||
context.startActivity(intent);
|
||||
} else {
|
||||
} else if (slide.getUri() != null) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(R.string.ConversationItem_view_secure_media_question);
|
||||
builder.setIconAttribute(R.attr.dialog_alert_icon);
|
||||
|
||||
@@ -87,21 +87,22 @@ public class ImageMediaAdapter extends CursorRecyclerViewAdapter<ViewHolder> {
|
||||
|
||||
@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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<InputStream> {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<SaveAttachmentTa
|
||||
public String contentType;
|
||||
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) {
|
||||
throw new AssertionError("uri, content type, and date must all be specified");
|
||||
}
|
||||
|
||||
@@ -447,4 +447,12 @@ public class Util {
|
||||
clipboardManager.setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
public static int toIntExact(long value) {
|
||||
if ((int)value != value) {
|
||||
throw new ArithmeticException("integer overflow");
|
||||
}
|
||||
return (int)value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user