mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 21:15:48 +00:00
Streamable Video.
This commit is contained in:
committed by
Nicholas Tinsley
parent
099c94c215
commit
64babe2e42
@@ -9,6 +9,7 @@ import org.thoughtcrime.securesms.blurhash.BlurHash;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable.TransformProperties;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
@@ -60,7 +61,7 @@ public class DatabaseAttachment extends Attachment {
|
||||
@Override
|
||||
@Nullable
|
||||
public Uri getUri() {
|
||||
if (hasData) {
|
||||
if (hasData || (FeatureFlags.instantVideoPlayback() && getIncrementalDigest() != null)) {
|
||||
return PartAuthority.getAttachmentDataUri(attachmentId);
|
||||
} else {
|
||||
return null;
|
||||
|
||||
@@ -255,6 +255,14 @@ class ConversationItemThumbnail @JvmOverloads constructor(
|
||||
state.applyState(thumbnail, album)
|
||||
}
|
||||
|
||||
fun setProgressWheelClickListener(listener: SlideClickListener?) {
|
||||
state = state.copy(
|
||||
thumbnailViewState = state.thumbnailViewState.copy(progressWheelClickListener = listener)
|
||||
)
|
||||
|
||||
state.applyState(thumbnail, album)
|
||||
}
|
||||
|
||||
private fun setThumbnailBounds(bounds: IntArray) {
|
||||
val (minWidth, maxWidth, minHeight, maxHeight) = bounds
|
||||
state = state.copy(
|
||||
|
||||
@@ -31,6 +31,8 @@ data class ConversationItemThumbnailState(
|
||||
@IgnoredOnParcel
|
||||
private val downloadClickListener: SlidesClickedListener? = null,
|
||||
@IgnoredOnParcel
|
||||
private val progressWheelClickListener: SlideClickListener? = null,
|
||||
@IgnoredOnParcel
|
||||
private val longClickListener: OnLongClickListener? = null,
|
||||
private val visibility: Int = View.GONE,
|
||||
private val minWidth: Int = -1,
|
||||
@@ -55,6 +57,7 @@ data class ConversationItemThumbnailState(
|
||||
thumbnailView.get().setRadii(cornerTopLeft, cornerTopRight, cornerBottomRight, cornerBottomLeft)
|
||||
thumbnailView.get().setThumbnailClickListener(clickListener)
|
||||
thumbnailView.get().setDownloadClickListener(downloadClickListener)
|
||||
thumbnailView.get().setProgressWheelClickListener(progressWheelClickListener)
|
||||
thumbnailView.get().setOnLongClickListener(longClickListener)
|
||||
thumbnailView.get().setBounds(minWidth, maxWidth, minHeight, maxHeight)
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
||||
import org.thoughtcrime.securesms.mms.VideoSlide;
|
||||
import org.thoughtcrime.securesms.stories.StoryTextPostModel;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
@@ -79,11 +80,12 @@ public class ThumbnailView extends FrameLayout {
|
||||
|
||||
private final CornerMask cornerMask;
|
||||
|
||||
private ThumbnailViewTransferControlsState transferControlsState = new ThumbnailViewTransferControlsState();
|
||||
private ThumbnailViewTransferControlsState transferControlsState = new ThumbnailViewTransferControlsState();
|
||||
private Stub<TransferControlView> transferControlViewStub;
|
||||
private SlideClickListener thumbnailClickListener = null;
|
||||
private SlidesClickedListener downloadClickListener = null;
|
||||
private Slide slide = null;
|
||||
private SlideClickListener thumbnailClickListener = null;
|
||||
private SlidesClickedListener downloadClickListener = null;
|
||||
private SlideClickListener progressWheelClickListener = null;
|
||||
private Slide slide = null;
|
||||
|
||||
|
||||
public ThumbnailView(Context context) {
|
||||
@@ -366,6 +368,11 @@ public class ThumbnailView extends FrameLayout {
|
||||
|
||||
transferControlsState = transferControlsState.withSlide(slide)
|
||||
.withDownloadClickListener(new DownloadClickDispatcher());
|
||||
|
||||
if (FeatureFlags.instantVideoPlayback()) {
|
||||
transferControlsState = transferControlsState.withProgressWheelClickListener(new ProgressWheelClickDispatcher());
|
||||
}
|
||||
|
||||
transferControlsState.applyState(transferControlViewStub);
|
||||
} else {
|
||||
transferControlViewStub.setVisibility(View.GONE);
|
||||
@@ -518,6 +525,10 @@ public class ThumbnailView extends FrameLayout {
|
||||
this.downloadClickListener = listener;
|
||||
}
|
||||
|
||||
public void setProgressWheelClickListener(SlideClickListener listener) {
|
||||
this.progressWheelClickListener = listener;
|
||||
}
|
||||
|
||||
public void clear(GlideRequests glideRequests) {
|
||||
glideRequests.clear(image);
|
||||
image.setImageDrawable(null);
|
||||
@@ -659,6 +670,18 @@ public class ThumbnailView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
private class ProgressWheelClickDispatcher implements View.OnClickListener {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Log.i(TAG, "onClick() for progress wheel");
|
||||
if (progressWheelClickListener != null && slide != null) {
|
||||
progressWheelClickListener.onClick(view, slide);
|
||||
} else {
|
||||
Log.w(TAG, "Received a progress wheel click, but unable to execute it. slide: " + slide + " progressWheelClickListener: " + progressWheelClickListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class BlurHashClearListener implements ListenableFuture.Listener<Boolean> {
|
||||
|
||||
private final GlideRequests glideRequests;
|
||||
|
||||
@@ -12,6 +12,7 @@ data class ThumbnailViewTransferControlsState(
|
||||
val isClickable: Boolean = true,
|
||||
val slide: Slide? = null,
|
||||
val downloadClickedListener: OnClickListener? = null,
|
||||
val progressWheelClickedListener: OnClickListener? = null,
|
||||
val showDownloadText: Boolean = true
|
||||
) {
|
||||
|
||||
@@ -19,6 +20,7 @@ data class ThumbnailViewTransferControlsState(
|
||||
fun withClickable(isClickable: Boolean): ThumbnailViewTransferControlsState = copy(isClickable = isClickable)
|
||||
fun withSlide(slide: Slide?): ThumbnailViewTransferControlsState = copy(slide = slide)
|
||||
fun withDownloadClickListener(downloadClickedListener: OnClickListener): ThumbnailViewTransferControlsState = copy(downloadClickedListener = downloadClickedListener)
|
||||
fun withProgressWheelClickListener(progressWheelClickedListener: OnClickListener): ThumbnailViewTransferControlsState = copy(progressWheelClickedListener = progressWheelClickedListener)
|
||||
fun withDownloadText(showDownloadText: Boolean): ThumbnailViewTransferControlsState = copy(showDownloadText = showDownloadText)
|
||||
|
||||
fun applyState(transferControlView: Stub<TransferControlView>) {
|
||||
@@ -29,6 +31,7 @@ data class ThumbnailViewTransferControlsState(
|
||||
transferControlView.get().setSlide(slide)
|
||||
}
|
||||
transferControlView.get().setDownloadClickListener(downloadClickedListener)
|
||||
transferControlView.get().setProgressWheelClickListener(progressWheelClickedListener)
|
||||
transferControlView.get().setShowDownloadText(showDownloadText)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import java.util.Map;
|
||||
|
||||
public final class TransferControlView extends FrameLayout {
|
||||
|
||||
private static final String TAG = "TransferControlView";
|
||||
private static final int UPLOAD_TASK_WEIGHT = 1;
|
||||
|
||||
/**
|
||||
@@ -152,6 +153,10 @@ public final class TransferControlView extends FrameLayout {
|
||||
downloadDetails.setOnClickListener(listener);
|
||||
}
|
||||
|
||||
public void setProgressWheelClickListener(final @Nullable OnClickListener listener) {
|
||||
progressWheel.setOnClickListener(listener);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
clearAnimation();
|
||||
setVisibility(GONE);
|
||||
@@ -247,13 +252,14 @@ public final class TransferControlView extends FrameLayout {
|
||||
|
||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||
public void onEventAsync(final PartProgressEvent event) {
|
||||
if (networkProgress.containsKey(event.attachment)) {
|
||||
final Attachment attachment = event.attachment;
|
||||
if (networkProgress.containsKey(attachment)) {
|
||||
float proportionCompleted = ((float) event.progress) / event.total;
|
||||
|
||||
if (event.type == PartProgressEvent.Type.COMPRESSION) {
|
||||
compresssionProgress.put(event.attachment, proportionCompleted);
|
||||
compresssionProgress.put(attachment, proportionCompleted);
|
||||
} else {
|
||||
networkProgress.put(event.attachment, proportionCompleted);
|
||||
networkProgress.put(attachment, proportionCompleted);
|
||||
}
|
||||
|
||||
progressWheel.setInstantProgress(calculateProgress(networkProgress, compresssionProgress));
|
||||
|
||||
@@ -135,6 +135,7 @@ import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.InterceptableLongClickCopyLinkSpan;
|
||||
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.MessageRecordUtil;
|
||||
import org.thoughtcrime.securesms.util.PlaceholderURLSpan;
|
||||
import org.thoughtcrime.securesms.util.Projection;
|
||||
@@ -240,6 +241,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
|
||||
private final PassthroughClickListener passthroughClickListener = new PassthroughClickListener();
|
||||
private final AttachmentDownloadClickListener downloadClickListener = new AttachmentDownloadClickListener();
|
||||
private final ProgressWheelClickListener progressWheelClickListener = new ProgressWheelClickListener();
|
||||
private final SlideClickPassthroughListener singleDownloadClickListener = new SlideClickPassthroughListener(downloadClickListener);
|
||||
private final SharedContactEventListener sharedContactEventListener = new SharedContactEventListener();
|
||||
private final SharedContactClickListener sharedContactClickListener = new SharedContactClickListener();
|
||||
@@ -1162,6 +1164,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
mediaThumbnailStub.require().setImageResource(glideRequests, Collections.singletonList(new ImageSlide(linkPreview.getThumbnail().get())), showControls, false);
|
||||
mediaThumbnailStub.require().setThumbnailClickListener(new LinkPreviewThumbnailClickListener());
|
||||
mediaThumbnailStub.require().setDownloadClickListener(downloadClickListener);
|
||||
mediaThumbnailStub.require().setProgressWheelClickListener(progressWheelClickListener);
|
||||
mediaThumbnailStub.require().setOnLongClickListener(passthroughClickListener);
|
||||
|
||||
linkPreviewStub.get().setLinkPreview(glideRequests, linkPreview, false);
|
||||
@@ -1301,6 +1304,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
false);
|
||||
mediaThumbnailStub.require().setThumbnailClickListener(new ThumbnailClickListener());
|
||||
mediaThumbnailStub.require().setDownloadClickListener(downloadClickListener);
|
||||
mediaThumbnailStub.require().setProgressWheelClickListener(progressWheelClickListener);
|
||||
mediaThumbnailStub.require().setOnLongClickListener(passthroughClickListener);
|
||||
mediaThumbnailStub.require().setOnClickListener(passthroughClickListener);
|
||||
mediaThumbnailStub.require().showShade(messageRecord.isDisplayBodyEmpty(getContext()) && !hasExtraText(messageRecord));
|
||||
@@ -1545,6 +1549,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
messageBody.setSpan(new MentionClickableSpan(RecipientId.from(annotation.getValue())), messageBody.getSpanStart(annotation), messageBody.getSpanEnd(annotation), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
|
||||
private void setStatusIcons(MessageRecord messageRecord, boolean hasWallpaper) {
|
||||
bodyText.setCompoundDrawablesWithIntrinsicBounds(0, 0, messageRecord.isKeyExchange() ? R.drawable.ic_menu_login : 0, 0);
|
||||
|
||||
@@ -2429,6 +2434,20 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
}
|
||||
}
|
||||
|
||||
private class ProgressWheelClickListener implements SlideClickListener {
|
||||
|
||||
@Override
|
||||
public void onClick(View v, Slide slide) {
|
||||
final boolean isIncremental = slide.asAttachment().getIncrementalDigest() != null;
|
||||
final boolean contentTypeSupported = MediaUtil.isVideoType(slide.getContentType());
|
||||
if (FeatureFlags.instantVideoPlayback() && isIncremental && contentTypeSupported) {
|
||||
launchMediaPreview(v, slide);
|
||||
} else {
|
||||
Log.d(TAG, "Non-eligible slide clicked: " + "\tisIncremental: " + isIncremental + "\tcontentTypeSupported: " + contentTypeSupported);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SlideClickPassthroughListener implements SlideClickListener {
|
||||
|
||||
private final SlidesClickedListener original;
|
||||
@@ -2462,34 +2481,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
} else if (!canPlayContent && mediaItem != null && eventListener != null) {
|
||||
eventListener.onPlayInlineContent(conversationMessage);
|
||||
} else if (MediaPreviewV2Fragment.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) {
|
||||
if (eventListener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
MediaIntentFactory.MediaPreviewArgs args = new MediaIntentFactory.MediaPreviewArgs(
|
||||
messageRecord.getThreadId(),
|
||||
messageRecord.getTimestamp(),
|
||||
slide.getUri(),
|
||||
slide.getContentType(),
|
||||
slide.asAttachment().getSize(),
|
||||
slide.getCaption().orElse(null),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
MediaTable.Sorting.Newest,
|
||||
slide.isVideoGif(),
|
||||
new MediaIntentFactory.SharedElementArgs(
|
||||
slide.asAttachment().getWidth(),
|
||||
slide.asAttachment().getHeight(),
|
||||
mediaThumbnailStub.require().getCorners().getTopLeft(),
|
||||
mediaThumbnailStub.require().getCorners().getTopRight(),
|
||||
mediaThumbnailStub.require().getCorners().getBottomRight(),
|
||||
mediaThumbnailStub.require().getCorners().getBottomLeft()
|
||||
),
|
||||
false);
|
||||
MediaPreviewCache.INSTANCE.setDrawable(((ThumbnailView) v).getImageDrawable());
|
||||
eventListener.goToMediaPreview(ConversationItem.this, v, args);
|
||||
launchMediaPreview(v, slide);
|
||||
} else if (slide.getUri() != null) {
|
||||
Log.i(TAG, "Clicked: " + slide.getUri() + " , " + slide.getContentType());
|
||||
Uri publicUri = PartAuthority.getAttachmentPublicUri(slide.getUri());
|
||||
@@ -2526,6 +2518,47 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
}
|
||||
}
|
||||
|
||||
private void launchMediaPreview(View v, Slide slide) {
|
||||
if (eventListener == null) {
|
||||
Log.w(TAG, "Could not launch media preview for item: eventListener was null");
|
||||
return;
|
||||
}
|
||||
|
||||
Uri mediaUri = slide.getUri();
|
||||
|
||||
if (mediaUri == null) {
|
||||
Log.w(TAG, "Could not launch media preview for item: uri was null");
|
||||
return;
|
||||
}
|
||||
|
||||
MediaIntentFactory.MediaPreviewArgs args = new MediaIntentFactory.MediaPreviewArgs(
|
||||
messageRecord.getThreadId(),
|
||||
messageRecord.getTimestamp(),
|
||||
mediaUri,
|
||||
slide.getContentType(),
|
||||
slide.asAttachment().getSize(),
|
||||
slide.getCaption().orElse(null),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
MediaTable.Sorting.Newest,
|
||||
slide.isVideoGif(),
|
||||
new MediaIntentFactory.SharedElementArgs(
|
||||
slide.asAttachment().getWidth(),
|
||||
slide.asAttachment().getHeight(),
|
||||
mediaThumbnailStub.require().getCorners().getTopLeft(),
|
||||
mediaThumbnailStub.require().getCorners().getTopRight(),
|
||||
mediaThumbnailStub.require().getCorners().getBottomRight(),
|
||||
mediaThumbnailStub.require().getCorners().getBottomLeft()
|
||||
),
|
||||
false);
|
||||
if (v instanceof ThumbnailView) {
|
||||
MediaPreviewCache.INSTANCE.setDrawable(((ThumbnailView) v).getImageDrawable());
|
||||
}
|
||||
eventListener.goToMediaPreview(ConversationItem.this, v, args);
|
||||
}
|
||||
|
||||
private class PassthroughClickListener implements View.OnLongClickListener, View.OnClickListener {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1343,39 +1343,39 @@ public class AttachmentTable extends DatabaseTable {
|
||||
private @NonNull DatabaseAttachment getAttachment(@NonNull Cursor cursor) {
|
||||
String contentType = cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_TYPE));
|
||||
return new DatabaseAttachment(new AttachmentId(cursor.getLong(cursor.getColumnIndexOrThrow(ROW_ID)),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(UNIQUE_ID))),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(MMS_ID)),
|
||||
!cursor.isNull(cursor.getColumnIndexOrThrow(DATA)),
|
||||
MediaUtil.isImageType(contentType) || MediaUtil.isVideoType(contentType),
|
||||
contentType,
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(TRANSFER_STATE)),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(SIZE)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(FILE_NAME)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(CDN_NUMBER)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_LOCATION)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_DISPOSITION)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(NAME)),
|
||||
cursor.getBlob(cursor.getColumnIndexOrThrow(DIGEST)),
|
||||
cursor.getBlob(cursor.getColumnIndexOrThrow(MAC_DIGEST)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(FAST_PREFLIGHT_ID)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(VOICE_NOTE)) == 1,
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(BORDERLESS)) == 1,
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(VIDEO_GIF)) == 1,
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(WIDTH)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(HEIGHT)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(QUOTE)) == 1,
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(CAPTION)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(STICKER_ID)) >= 0
|
||||
? new StickerLocator(CursorUtil.requireString(cursor, STICKER_PACK_ID),
|
||||
CursorUtil.requireString(cursor, STICKER_PACK_KEY),
|
||||
CursorUtil.requireInt(cursor, STICKER_ID),
|
||||
CursorUtil.requireString(cursor, STICKER_EMOJI))
|
||||
: null,
|
||||
MediaUtil.isAudioType(contentType) ? null : BlurHash.parseOrNull(cursor.getString(cursor.getColumnIndexOrThrow(VISUAL_HASH))),
|
||||
MediaUtil.isAudioType(contentType) ? AudioHash.parseOrNull(cursor.getString(cursor.getColumnIndexOrThrow(VISUAL_HASH))) : null,
|
||||
TransformProperties.parse(cursor.getString(cursor.getColumnIndexOrThrow(TRANSFORM_PROPERTIES))),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(DISPLAY_ORDER)),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(UPLOAD_TIMESTAMP)));
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(UNIQUE_ID))),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(MMS_ID)),
|
||||
!cursor.isNull(cursor.getColumnIndexOrThrow(DATA)),
|
||||
MediaUtil.isImageType(contentType) || MediaUtil.isVideoType(contentType),
|
||||
contentType,
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(TRANSFER_STATE)),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(SIZE)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(FILE_NAME)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(CDN_NUMBER)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_LOCATION)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_DISPOSITION)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(NAME)),
|
||||
cursor.getBlob(cursor.getColumnIndexOrThrow(DIGEST)),
|
||||
cursor.getBlob(cursor.getColumnIndexOrThrow(MAC_DIGEST)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(FAST_PREFLIGHT_ID)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(VOICE_NOTE)) == 1,
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(BORDERLESS)) == 1,
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(VIDEO_GIF)) == 1,
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(WIDTH)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(HEIGHT)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(QUOTE)) == 1,
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(CAPTION)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(STICKER_ID)) >= 0
|
||||
? new StickerLocator(CursorUtil.requireString(cursor, STICKER_PACK_ID),
|
||||
CursorUtil.requireString(cursor, STICKER_PACK_KEY),
|
||||
CursorUtil.requireInt(cursor, STICKER_ID),
|
||||
CursorUtil.requireString(cursor, STICKER_EMOJI))
|
||||
: null,
|
||||
MediaUtil.isAudioType(contentType) ? null : BlurHash.parseOrNull(cursor.getString(cursor.getColumnIndexOrThrow(VISUAL_HASH))),
|
||||
MediaUtil.isAudioType(contentType) ? AudioHash.parseOrNull(cursor.getString(cursor.getColumnIndexOrThrow(VISUAL_HASH))) : null,
|
||||
TransformProperties.parse(cursor.getString(cursor.getColumnIndexOrThrow(TRANSFORM_PROPERTIES))),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(DISPLAY_ORDER)),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(UPLOAD_TIMESTAMP)));
|
||||
}
|
||||
|
||||
private AttachmentId insertAttachment(long mmsId, Attachment attachment, boolean quote)
|
||||
@@ -1514,15 +1514,28 @@ public class AttachmentTable extends DatabaseTable {
|
||||
|
||||
|
||||
@RequiresApi(23)
|
||||
public @Nullable MediaDataSource mediaDataSourceFor(@NonNull AttachmentId attachmentId) {
|
||||
public @Nullable MediaDataSource mediaDataSourceFor(@NonNull AttachmentId attachmentId, Boolean allowReadingFromTempFile) {
|
||||
DataInfo dataInfo = getAttachmentDataFileInfo(attachmentId, DATA);
|
||||
|
||||
if (dataInfo == null) {
|
||||
Log.w(TAG, "No data file found for video attachment...");
|
||||
return null;
|
||||
if (dataInfo != null) {
|
||||
return EncryptedMediaDataSource.createFor(attachmentSecret, dataInfo.file, dataInfo.random, dataInfo.length);
|
||||
}
|
||||
|
||||
return EncryptedMediaDataSource.createFor(attachmentSecret, dataInfo.file, dataInfo.random, dataInfo.length);
|
||||
if (allowReadingFromTempFile) {
|
||||
Log.d(TAG, "Completed data file not found for video attachment, checking for in-progress files.");
|
||||
|
||||
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
|
||||
|
||||
File transferFile = getTransferFile(database, attachmentId);
|
||||
|
||||
if (transferFile != null) {
|
||||
return EncryptedMediaDataSource.createForDiskBlob(attachmentSecret, transferFile);
|
||||
}
|
||||
}
|
||||
|
||||
Log.w(TAG, "No data file found for video attachment!");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void duplicateAttachmentsForMessage(long destinationMessageId, long sourceMessageId, Collection<Long> excludedIds) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.signal.core.util.requireLong
|
||||
import org.signal.core.util.requireNonNullString
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.util.MediaUtil.SlideType
|
||||
|
||||
@@ -68,8 +69,7 @@ class MediaTable internal constructor(context: Context?, databaseHelper: SignalD
|
||||
) AND
|
||||
(%s) AND
|
||||
${MessageTable.VIEW_ONCE} = 0 AND
|
||||
${MessageTable.STORY_TYPE} = 0 AND
|
||||
${AttachmentTable.DATA} IS NOT NULL AND
|
||||
${MessageTable.STORY_TYPE} = 0 AND
|
||||
${MessageTable.LATEST_REVISION_ID} IS NULL AND
|
||||
(
|
||||
${AttachmentTable.QUOTE} = 0 OR
|
||||
@@ -98,32 +98,54 @@ class MediaTable internal constructor(context: Context?, databaseHelper: SignalD
|
||||
private val GALLERY_MEDIA_QUERY = String.format(
|
||||
BASE_MEDIA_QUERY,
|
||||
"""
|
||||
${AttachmentTable.DATA} IS NOT NULL AND
|
||||
${AttachmentTable.CONTENT_TYPE} NOT LIKE 'image/svg%' AND
|
||||
(${AttachmentTable.CONTENT_TYPE} LIKE 'image/%' OR ${AttachmentTable.CONTENT_TYPE} LIKE 'video/%')
|
||||
"""
|
||||
)
|
||||
|
||||
private val AUDIO_MEDIA_QUERY = String.format(BASE_MEDIA_QUERY, "${AttachmentTable.CONTENT_TYPE} LIKE 'audio/%'")
|
||||
private val GALLERY_MEDIA_QUERY_INCLUDING_TEMP_VIDEOS = String.format(
|
||||
BASE_MEDIA_QUERY,
|
||||
"""
|
||||
${AttachmentTable.DATA} IS NOT NULL OR (${AttachmentTable.CONTENT_TYPE} LIKE 'video/%' AND ${AttachmentTable.MAC_DIGEST} IS NOT NULL) AND
|
||||
${AttachmentTable.CONTENT_TYPE} NOT LIKE 'image/svg%' AND
|
||||
(${AttachmentTable.CONTENT_TYPE} LIKE 'image/%' OR ${AttachmentTable.CONTENT_TYPE} LIKE 'video/%')
|
||||
"""
|
||||
)
|
||||
|
||||
private val AUDIO_MEDIA_QUERY = String.format(
|
||||
BASE_MEDIA_QUERY,
|
||||
"""
|
||||
${AttachmentTable.DATA} IS NOT NULL AND
|
||||
${AttachmentTable.CONTENT_TYPE} LIKE 'audio/%'
|
||||
"""
|
||||
)
|
||||
private val ALL_MEDIA_QUERY = String.format(BASE_MEDIA_QUERY, "${AttachmentTable.CONTENT_TYPE} NOT LIKE 'text/x-signal-plain'")
|
||||
private val DOCUMENT_MEDIA_QUERY = String.format(
|
||||
BASE_MEDIA_QUERY,
|
||||
"""
|
||||
${AttachmentTable.CONTENT_TYPE} LIKE 'image/svg%' OR
|
||||
${AttachmentTable.DATA} IS NOT NULL AND
|
||||
(
|
||||
${AttachmentTable.CONTENT_TYPE} NOT LIKE 'image/%' AND
|
||||
${AttachmentTable.CONTENT_TYPE} NOT LIKE 'video/%' AND
|
||||
${AttachmentTable.CONTENT_TYPE} NOT LIKE 'audio/%' AND
|
||||
${AttachmentTable.CONTENT_TYPE} NOT LIKE 'text/x-signal-plain'
|
||||
${AttachmentTable.CONTENT_TYPE} LIKE 'image/svg%' OR
|
||||
(
|
||||
${AttachmentTable.CONTENT_TYPE} NOT LIKE 'image/%' AND
|
||||
${AttachmentTable.CONTENT_TYPE} NOT LIKE 'video/%' AND
|
||||
${AttachmentTable.CONTENT_TYPE} NOT LIKE 'audio/%' AND
|
||||
${AttachmentTable.CONTENT_TYPE} NOT LIKE 'text/x-signal-plain'
|
||||
)
|
||||
)"""
|
||||
)
|
||||
|
||||
private fun applyEqualityOperator(threadId: Long, query: String): String {
|
||||
return query.replace("__EQUALITY__", if (threadId == ALL_THREADS.toLong()) "!=" else "=")
|
||||
}
|
||||
}
|
||||
|
||||
fun getGalleryMediaForThread(threadId: Long, sorting: Sorting): Cursor {
|
||||
val query = sorting.applyToQuery(applyEqualityOperator(threadId, GALLERY_MEDIA_QUERY))
|
||||
val query = if (FeatureFlags.instantVideoPlayback()) {
|
||||
sorting.applyToQuery(applyEqualityOperator(threadId, GALLERY_MEDIA_QUERY_INCLUDING_TEMP_VIDEOS))
|
||||
} else {
|
||||
sorting.applyToQuery(applyEqualityOperator(threadId, GALLERY_MEDIA_QUERY))
|
||||
}
|
||||
val args = arrayOf(threadId.toString() + "")
|
||||
return readableDatabase.rawQuery(query, args)
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ public final class AttachmentCompressionJob extends BaseJob {
|
||||
|
||||
notification.setIndeterminateProgress();
|
||||
|
||||
try (MediaDataSource dataSource = attachmentDatabase.mediaDataSourceFor(attachment.getAttachmentId())) {
|
||||
try (MediaDataSource dataSource = attachmentDatabase.mediaDataSourceFor(attachment.getAttachmentId(), false)) {
|
||||
if (dataSource == null) {
|
||||
throw new UndeliverableMessageException("Cannot get media data source for attachment.");
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public final class DecryptableUriMediaInput {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
MediaDataSource mediaDataSource = SignalDatabase.attachments().mediaDataSourceFor(partId);
|
||||
MediaDataSource mediaDataSource = SignalDatabase.attachments().mediaDataSourceFor(partId, true);
|
||||
|
||||
if (mediaDataSource == null) {
|
||||
throw new AssertionError();
|
||||
|
||||
@@ -66,9 +66,7 @@ class MediaPreviewRepository {
|
||||
|
||||
for (i in 0..limit) {
|
||||
val element = MediaTable.MediaRecord.from(cursor)
|
||||
if (element != null) {
|
||||
mediaRecords.add(element)
|
||||
}
|
||||
mediaRecords.add(element)
|
||||
if (!cursor.moveToNext()) {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@ package org.thoughtcrime.securesms.mediapreview
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.attachments.Attachment
|
||||
import org.thoughtcrime.securesms.mediasend.Media
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.util.adapter.StableIdGenerator
|
||||
|
||||
class MediaPreviewV2Adapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
|
||||
private val TAG = Log.tag(MediaPreviewV2Adapter::class.java)
|
||||
private var items: List<Attachment> = listOf()
|
||||
private val stableIdGenerator = StableIdGenerator<Attachment>()
|
||||
private val currentIdSet: HashSet<Long> = HashSet()
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -42,6 +43,12 @@ public final class VideoMediaPreviewFragment extends MediaPreviewFragment {
|
||||
View itemView = inflater.inflate(R.layout.media_preview_video_fragment, container, false);
|
||||
Bundle arguments = requireArguments();
|
||||
Uri uri = arguments.getParcelable(DATA_URI);
|
||||
if (uri == null) {
|
||||
Log.w(TAG, "Media URI was null.");
|
||||
Toast.makeText(requireContext(), R.string.MediaPreviewActivity_media_no_longer_available, Toast.LENGTH_LONG).show();
|
||||
requireActivity().finish();
|
||||
return itemView;
|
||||
}
|
||||
String contentType = arguments.getString(DATA_CONTENT_TYPE);
|
||||
long size = arguments.getLong(DATA_SIZE);
|
||||
boolean autoPlay = arguments.getBoolean(AUTO_PLAY);
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.bumptech.glide.load.DataSource;
|
||||
import com.bumptech.glide.load.data.DataFetcher;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.InvalidMacException;
|
||||
import org.signal.libsignal.protocol.InvalidMessageException;
|
||||
import org.whispersystems.signalservice.api.crypto.AttachmentCipherInputStream;
|
||||
|
||||
@@ -39,7 +40,7 @@ class AttachmentStreamLocalUriFetcher implements DataFetcher<InputStream> {
|
||||
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
|
||||
try {
|
||||
if (!digest.isPresent()) throw new InvalidMessageException("No attachment digest!");
|
||||
is = AttachmentCipherInputStream.createForAttachment(attachment, plaintextLength, key, digest.get(), incrementalDigest.get());
|
||||
is = AttachmentCipherInputStream.createForAttachment(attachment, plaintextLength, key, digest.get(), incrementalDigest.orElse(null));
|
||||
callback.onDataReady(is);
|
||||
} catch (IOException | InvalidMessageException e) {
|
||||
callback.onLoadFailed(e);
|
||||
|
||||
@@ -113,6 +113,7 @@ public final class FeatureFlags {
|
||||
private static final String PROMPT_FOR_NOTIFICATION_CONFIG = "android.logs.promptNotificationsConfig";
|
||||
public static final String PROMPT_BATTERY_SAVER = "android.promptBatterySaver";
|
||||
public static final String USERNAMES = "android.usernames";
|
||||
public static final String INSTANT_VIDEO_PLAYBACK = "android.instantVideoPlayback";
|
||||
|
||||
/**
|
||||
* We will only store remote values for flags in this set. If you want a flag to be controllable
|
||||
@@ -177,7 +178,8 @@ public final class FeatureFlags {
|
||||
PROMPT_FOR_NOTIFICATION_LOGS,
|
||||
PROMPT_FOR_NOTIFICATION_CONFIG,
|
||||
PROMPT_BATTERY_SAVER,
|
||||
USERNAMES
|
||||
USERNAMES,
|
||||
INSTANT_VIDEO_PLAYBACK
|
||||
);
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -629,6 +631,14 @@ public final class FeatureFlags {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow the video players to read from the temporary download files for attachments.
|
||||
* @return whether this functionality is enabled.
|
||||
*/
|
||||
public static boolean instantVideoPlayback() {
|
||||
return getBoolean(INSTANT_VIDEO_PLAYBACK, false);
|
||||
}
|
||||
|
||||
public static String promptForDelayedNotificationLogs() {
|
||||
return getString(PROMPT_FOR_NOTIFICATION_LOGS, "*");
|
||||
}
|
||||
@@ -641,6 +651,7 @@ public final class FeatureFlags {
|
||||
return getString(PROMPT_BATTERY_SAVER, "*");
|
||||
}
|
||||
|
||||
|
||||
/** Only for rendering debug info. */
|
||||
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
|
||||
return new TreeMap<>(REMOTE_VALUES);
|
||||
|
||||
@@ -446,7 +446,7 @@ public class MediaUtil {
|
||||
{
|
||||
try {
|
||||
AttachmentId attachmentId = PartAuthority.requireAttachmentId(uri);
|
||||
MediaDataSource source = SignalDatabase.attachments().mediaDataSourceFor(attachmentId);
|
||||
MediaDataSource source = SignalDatabase.attachments().mediaDataSourceFor(attachmentId, false);
|
||||
return extractFrame(source, timeUs);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to extract frame for URI: " + uri, e);
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
package org.thoughtcrime.securesms.video.exo;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.datasource.DataSpec;
|
||||
import androidx.media3.datasource.TransferListener;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.InvalidMessageException;
|
||||
import org.signal.libsignal.protocol.incrementalmac.ChunkSizeChoice;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.mms.PartUriParser;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.whispersystems.signalservice.api.crypto.AttachmentCipherInputStream;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
@@ -28,14 +33,13 @@ import java.util.Map;
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
class PartDataSource implements DataSource {
|
||||
|
||||
private final @NonNull Context context;
|
||||
private final String TAG = Log.tag(PartDataSource.class);
|
||||
private final @Nullable TransferListener listener;
|
||||
|
||||
private Uri uri;
|
||||
private InputStream inputSteam;
|
||||
private InputStream inputStream;
|
||||
|
||||
PartDataSource(@NonNull Context context, @Nullable TransferListener listener) {
|
||||
this.context = context.getApplicationContext();
|
||||
PartDataSource(@Nullable TransferListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@@ -47,13 +51,42 @@ class PartDataSource implements DataSource {
|
||||
public long open(DataSpec dataSpec) throws IOException {
|
||||
this.uri = dataSpec.uri;
|
||||
|
||||
AttachmentTable attachmentDatabase = SignalDatabase.attachments();
|
||||
PartUriParser partUri = new PartUriParser(uri);
|
||||
Attachment attachment = attachmentDatabase.getAttachment(partUri.getPartId());
|
||||
AttachmentTable attachmentDatabase = SignalDatabase.attachments();
|
||||
PartUriParser partUri = new PartUriParser(uri);
|
||||
DatabaseAttachment attachment = attachmentDatabase.getAttachment(partUri.getPartId());
|
||||
|
||||
if (attachment == null) throw new IOException("Attachment not found");
|
||||
|
||||
this.inputSteam = attachmentDatabase.getAttachmentStream(partUri.getPartId(), dataSpec.position);
|
||||
final boolean hasIncrementalDigest = attachment.getIncrementalDigest() != null;
|
||||
final boolean inProgress = attachment.isInProgress();
|
||||
final String attachmentKey = attachment.getKey();
|
||||
final boolean hasData = attachment.hasData();
|
||||
if (inProgress && !hasData && hasIncrementalDigest && attachmentKey != null && FeatureFlags.instantVideoPlayback()) {
|
||||
final byte[] decode = Base64.decode(attachmentKey);
|
||||
final File transferFile = attachmentDatabase.getOrCreateTransferFile(attachment.getAttachmentId());
|
||||
try {
|
||||
this.inputStream = AttachmentCipherInputStream.createForAttachment(transferFile, attachment.getSize(), decode, attachment.getDigest(), attachment.getIncrementalDigest());
|
||||
|
||||
long skipped = 0;
|
||||
while (skipped < dataSpec.position) {
|
||||
skipped += this.inputStream.read();
|
||||
}
|
||||
|
||||
Log.d(TAG, "Successfully loaded partial attachment file.");
|
||||
|
||||
} catch (InvalidMessageException e) {
|
||||
throw new IOException("Error decrypting attachment stream!", e);
|
||||
}
|
||||
} else if (!inProgress || hasData) {
|
||||
this.inputStream = attachmentDatabase.getAttachmentStream(partUri.getPartId(), dataSpec.position);
|
||||
|
||||
Log.d(TAG, "Successfully loaded completed attachment file.");
|
||||
} else {
|
||||
throw new IOException("Ineligible " + attachment.getAttachmentId().toString()
|
||||
+ "\nTransfer state: " + attachment.getTransferState()
|
||||
+ "\nIncremental Digest Present: " + hasIncrementalDigest
|
||||
+ "\nAttachment Key Non-Empty: " + (attachmentKey != null && !attachmentKey.isEmpty()));
|
||||
}
|
||||
|
||||
if (listener != null) {
|
||||
listener.onTransferStart(this, dataSpec, false);
|
||||
@@ -66,7 +99,7 @@ class PartDataSource implements DataSource {
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] buffer, int offset, int readLength) throws IOException {
|
||||
int read = inputSteam.read(buffer, offset, readLength);
|
||||
int read = inputStream.read(buffer, offset, readLength);
|
||||
|
||||
if (read > 0 && listener != null) {
|
||||
listener.onBytesTransferred(this, null, false, read);
|
||||
@@ -87,6 +120,6 @@ class PartDataSource implements DataSource {
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
inputSteam.close();
|
||||
if (inputStream != null) inputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ import okhttp3.OkHttpClient;
|
||||
@Override
|
||||
public @NonNull SignalDataSource createDataSource() {
|
||||
return new SignalDataSource(new DefaultDataSourceFactory(context, "GenericUserAgent", null).createDataSource(),
|
||||
new PartDataSource(context, listener),
|
||||
new PartDataSource(listener),
|
||||
new BlobDataSource(context, listener),
|
||||
okHttpClient != null ? new ChunkedDataSource(okHttpClient, listener) : null);
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ public class SignalServiceMessageReceiver {
|
||||
if (!pointer.getDigest().isPresent()) throw new InvalidMessageException("No attachment digest!");
|
||||
|
||||
socket.retrieveAttachment(pointer.getCdnNumber(), pointer.getRemoteId(), destination, maxSizeBytes, listener);
|
||||
return AttachmentCipherInputStream.createForAttachment(destination, pointer.getSize().orElse(0), pointer.getKey(), pointer.getDigest().get(), pointer.getIncrementalDigest().orElse(new byte[0]));
|
||||
return AttachmentCipherInputStream.createForAttachment(destination, pointer.getSize().orElse(0), pointer.getKey(), pointer.getDigest().get(), null);
|
||||
}
|
||||
|
||||
public InputStream retrieveSticker(byte[] packId, byte[] packKey, int stickerId)
|
||||
|
||||
@@ -69,22 +69,22 @@ public class AttachmentCipherInputStream extends FilterInputStream {
|
||||
throw new InvalidMacException("Missing digest!");
|
||||
}
|
||||
|
||||
try (FileInputStream fin = new FileInputStream(file)) {
|
||||
verifyMac(fin, file.length(), mac, digest);
|
||||
}
|
||||
|
||||
final FileInputStream innerStream = new FileInputStream(file);
|
||||
|
||||
final InputStream wrappedStream;
|
||||
boolean hasIncrementalMac = incrementalDigest != null && incrementalDigest.length > 0;
|
||||
|
||||
InputStream wrap = !hasIncrementalMac ? innerStream
|
||||
: new IncrementalMacInputStream(
|
||||
innerStream,
|
||||
parts[1],
|
||||
ChunkSizeChoice.inferChunkSize(Math.max(Math.toIntExact(file.length()), 1)),
|
||||
incrementalDigest);
|
||||
|
||||
InputStream inputStream = new AttachmentCipherInputStream(wrap, parts[0], file.length() - BLOCK_SIZE - mac.getMacLength());
|
||||
if (!hasIncrementalMac) {
|
||||
try (FileInputStream macVerificationStream = new FileInputStream(file)) {
|
||||
verifyMac(macVerificationStream, file.length(), mac, digest);
|
||||
}
|
||||
wrappedStream = new FileInputStream(file);
|
||||
} else {
|
||||
wrappedStream = new IncrementalMacInputStream(
|
||||
new FileInputStream(file),
|
||||
parts[1],
|
||||
ChunkSizeChoice.inferChunkSize(Math.toIntExact(plaintextLength)),
|
||||
incrementalDigest);
|
||||
}
|
||||
InputStream inputStream = new AttachmentCipherInputStream(wrappedStream, parts[0], file.length() - BLOCK_SIZE - mac.getMacLength());
|
||||
|
||||
if (plaintextLength != 0) {
|
||||
inputStream = new ContentLengthInputStream(inputStream, plaintextLength);
|
||||
|
||||
@@ -81,7 +81,7 @@ class DigestingRequestBody(
|
||||
return if (contentLength > 0) contentLength - contentStart else -1
|
||||
}
|
||||
|
||||
fun getAttachmentDigest() = AttachmentDigest(transmittedDigest, incrementalDigest)
|
||||
fun getAttachmentDigest(): AttachmentDigest = AttachmentDigest(transmittedDigest, incrementalDigest)
|
||||
|
||||
private fun logMessage(actual: Long, expected: Long): String {
|
||||
val difference = actual - expected
|
||||
|
||||
@@ -67,7 +67,7 @@ public final class AttachmentCipherTest {
|
||||
try {
|
||||
byte[] key = Util.getSecretBytes(64);
|
||||
byte[] plaintextInput = "Gwen Stacy".getBytes();
|
||||
EncryptResult encryptResult = encryptData(plaintextInput, key, true);
|
||||
EncryptResult encryptResult = encryptData(plaintextInput, key, false);
|
||||
byte[] badKey = new byte[64];
|
||||
|
||||
cipherFile = writeToFile(encryptResult.ciphertext);
|
||||
@@ -92,7 +92,7 @@ public final class AttachmentCipherTest {
|
||||
try {
|
||||
byte[] key = Util.getSecretBytes(64);
|
||||
byte[] plaintextInput = "Mary Jane Watson".getBytes();
|
||||
EncryptResult encryptResult = encryptData(plaintextInput, key, true);
|
||||
EncryptResult encryptResult = encryptData(plaintextInput, key, false);
|
||||
byte[] badDigest = new byte[32];
|
||||
|
||||
cipherFile = writeToFile(encryptResult.ciphertext);
|
||||
@@ -213,7 +213,7 @@ public final class AttachmentCipherTest {
|
||||
try {
|
||||
byte[] key = Util.getSecretBytes(64);
|
||||
byte[] plaintextInput = "Uncle Ben".getBytes();
|
||||
EncryptResult encryptResult = encryptData(plaintextInput, key, true);
|
||||
EncryptResult encryptResult = encryptData(plaintextInput, key, false);
|
||||
byte[] badMacCiphertext = Arrays.copyOf(encryptResult.ciphertext, encryptResult.ciphertext.length);
|
||||
|
||||
badMacCiphertext[badMacCiphertext.length - 1] += 1;
|
||||
@@ -221,6 +221,7 @@ public final class AttachmentCipherTest {
|
||||
cipherFile = writeToFile(badMacCiphertext);
|
||||
|
||||
AttachmentCipherInputStream.createForAttachment(cipherFile, plaintextInput.length, key, encryptResult.digest, encryptResult.incrementalDigest);
|
||||
fail();
|
||||
} catch (InvalidMessageException e) {
|
||||
hitCorrectException = true;
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user