diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a6cc1bd48f..9b0d5f7750 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -336,7 +336,7 @@ android:windowSoftInputMode="stateUnchanged" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> - - + - - - + diff --git a/res/values/strings.xml b/res/values/strings.xml index 9808b7a2b2..2a67b73f8e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -254,6 +254,7 @@ Deleting messages... Original message not found Original message no longer available + Failed to open message There is no browser installed on your device. diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java index 7d166bdb84..8cd621f99f 100644 --- a/src/org/thoughtcrime/securesms/ApplicationContext.java +++ b/src/org/thoughtcrime/securesms/ApplicationContext.java @@ -62,7 +62,7 @@ import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.service.IncomingMessageObserver; import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.service.LocalBackupListener; -import org.thoughtcrime.securesms.revealable.RevealableMessageManager; +import org.thoughtcrime.securesms.revealable.ViewOnceMessageManager; import org.thoughtcrime.securesms.service.RotateSenderCertificateListener; import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener; import org.thoughtcrime.securesms.service.UpdateApkRefreshListener; @@ -92,7 +92,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi private static final String TAG = ApplicationContext.class.getSimpleName(); private ExpiringMessageManager expiringMessageManager; - private RevealableMessageManager revealableMessageManager; + private ViewOnceMessageManager viewOnceMessageManager; private TypingStatusRepository typingStatusRepository; private TypingStatusSender typingStatusSender; private JobManager jobManager; @@ -157,8 +157,8 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi return expiringMessageManager; } - public RevealableMessageManager getRevealableMessageManager() { - return revealableMessageManager; + public ViewOnceMessageManager getViewOnceMessageManager() { + return viewOnceMessageManager; } public TypingStatusRepository getTypingStatusRepository() { @@ -252,7 +252,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi } private void initializeRevealableMessageManager() { - this.revealableMessageManager = new RevealableMessageManager(this); + this.viewOnceMessageManager = new ViewOnceMessageManager(this); } private void initializeTypingStatusRepository() { diff --git a/src/org/thoughtcrime/securesms/BindableConversationItem.java b/src/org/thoughtcrime/securesms/BindableConversationItem.java index fdded7b42c..88d5657177 100644 --- a/src/org/thoughtcrime/securesms/BindableConversationItem.java +++ b/src/org/thoughtcrime/securesms/BindableConversationItem.java @@ -38,7 +38,7 @@ public interface BindableConversationItem extends Unbindable { void onLinkPreviewClicked(@NonNull LinkPreview linkPreview); void onMoreTextClicked(@NonNull Address conversationAddress, long messageId, boolean isMms); void onStickerClicked(@NonNull StickerLocator stickerLocator); - void onRevealableMessageClicked(@NonNull MmsMessageRecord messageRecord); + void onViewOnceMessageClicked(@NonNull MmsMessageRecord messageRecord); void onSharedContactDetailsClicked(@NonNull Contact contact, @NonNull View avatarTransitionView); void onAddToContactsClicked(@NonNull Contact contact); void onMessageSharedContactClicked(@NonNull List choices); diff --git a/src/org/thoughtcrime/securesms/ShareActivity.java b/src/org/thoughtcrime/securesms/ShareActivity.java index 578ecd4c2e..0bd2c873bd 100644 --- a/src/org/thoughtcrime/securesms/ShareActivity.java +++ b/src/org/thoughtcrime/securesms/ShareActivity.java @@ -340,7 +340,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity .forData(inputStream, fileSize == null ? 0 : fileSize) .withMimeType(mimeType) .withFileName(fileName) - .createForMultipleSessionsOnDisk(context, e -> Log.w(TAG, "Failed to write to disk.", e)); + .createForMultipleSessionsOnDisk(context); } catch (IOException ioe) { Log.w(TAG, ioe); return null; diff --git a/src/org/thoughtcrime/securesms/attachments/TombstoneAttachment.java b/src/org/thoughtcrime/securesms/attachments/TombstoneAttachment.java index 79feb548c3..ed3b3f6e0d 100644 --- a/src/org/thoughtcrime/securesms/attachments/TombstoneAttachment.java +++ b/src/org/thoughtcrime/securesms/attachments/TombstoneAttachment.java @@ -7,6 +7,12 @@ import androidx.annotation.Nullable; import org.thoughtcrime.securesms.database.AttachmentDatabase; +/** + * An attachment that represents where an attachment used to be. Useful when you need to know that + * a message had an attachment and some metadata about it (like the contentType), even though the + * underlying media no longer exists. An example usecase would be view-once messages, so that we can + * quote them and know their contentType even though the media has been deleted. + */ public class TombstoneAttachment extends Attachment { public TombstoneAttachment(@NonNull String contentType, boolean quote) { diff --git a/src/org/thoughtcrime/securesms/audio/AudioRecorder.java b/src/org/thoughtcrime/securesms/audio/AudioRecorder.java index bb390f36c7..0eeb48cff0 100644 --- a/src/org/thoughtcrime/securesms/audio/AudioRecorder.java +++ b/src/org/thoughtcrime/securesms/audio/AudioRecorder.java @@ -50,7 +50,7 @@ public class AudioRecorder { captureUri = BlobProvider.getInstance() .forData(new ParcelFileDescriptor.AutoCloseInputStream(fds[0]), 0) .withMimeType(MediaUtil.AUDIO_AAC) - .createForSingleSessionOnDisk(context, e -> Log.w(TAG, "Error during recording", e)); + .createForSingleSessionOnDiskAsync(context, () -> Log.i(TAG, "Write successful."), e -> Log.w(TAG, "Error during recording", e)); audioCodec = new AudioCodec(); audioCodec.start(new ParcelFileDescriptor.AutoCloseOutputStream(fds[1])); diff --git a/src/org/thoughtcrime/securesms/backup/FullBackupExporter.java b/src/org/thoughtcrime/securesms/backup/FullBackupExporter.java index c698a5dfd3..0ffe1a40a5 100644 --- a/src/org/thoughtcrime/securesms/backup/FullBackupExporter.java +++ b/src/org/thoughtcrime/securesms/backup/FullBackupExporter.java @@ -27,7 +27,6 @@ import org.thoughtcrime.securesms.database.OneTimePreKeyDatabase; import org.thoughtcrime.securesms.database.SearchDatabase; import org.thoughtcrime.securesms.database.SessionDatabase; import org.thoughtcrime.securesms.database.SignedPreKeyDatabase; -import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.StickerDatabase; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.profiles.AvatarHelper; @@ -254,19 +253,19 @@ public class FullBackupExporter extends FullBackupBase { } private static boolean isNonExpiringMessage(@NonNull Cursor cursor) { - return cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsColumns.EXPIRES_IN)) <= 0 && - cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.REVEAL_DURATION)) <= 0; + return cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsColumns.EXPIRES_IN)) <= 0 && + cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.VIEW_ONCE)) <= 0; } private static boolean isForNonExpiringMessage(@NonNull SQLiteDatabase db, long mmsId) { - String[] columns = new String[] { MmsDatabase.EXPIRES_IN, MmsDatabase.REVEAL_DURATION }; + String[] columns = new String[] { MmsDatabase.EXPIRES_IN, MmsDatabase.VIEW_ONCE}; String where = MmsDatabase.ID + " = ?"; String[] args = new String[] { String.valueOf(mmsId) }; try (Cursor mmsCursor = db.query(MmsDatabase.TABLE_NAME, columns, where, args, null, null, null)) { if (mmsCursor != null && mmsCursor.moveToFirst()) { - return mmsCursor.getLong(mmsCursor.getColumnIndexOrThrow(MmsDatabase.EXPIRES_IN)) == 0 && - mmsCursor.getLong(mmsCursor.getColumnIndexOrThrow(MmsDatabase.REVEAL_DURATION)) == 0; + return mmsCursor.getLong(mmsCursor.getColumnIndexOrThrow(MmsDatabase.EXPIRES_IN)) == 0 && + mmsCursor.getLong(mmsCursor.getColumnIndexOrThrow(MmsDatabase.VIEW_ONCE)) == 0; } } diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 3934fedbae..a088e737f2 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -542,8 +542,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity boolean initiating = threadId == -1; TransportOption transport = data.getParcelableExtra(MediaSendActivity.EXTRA_TRANSPORT); String message = data.getStringExtra(MediaSendActivity.EXTRA_MESSAGE); - long revealDuration = data.getLongExtra(MediaSendActivity.EXTRA_REVEAL_DURATION, 0); - QuoteModel quote = (revealDuration == 0) ? inputPanel.getQuote().orNull() : null; + boolean viewOnce = data.getBooleanExtra(MediaSendActivity.EXTRA_VIEW_ONCE, false); + QuoteModel quote = viewOnce ? inputPanel.getQuote().orNull() : null; SlideDeck slideDeck = new SlideDeck(); if (transport == null) { @@ -575,7 +575,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity Collections.emptyList(), Collections.emptyList(), expiresIn, - revealDuration, + viewOnce, subscriptionId, initiating, true).addListener(new AssertedSuccessListener() { @@ -1813,7 +1813,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity long expiresIn = recipient.getExpireMessages() * 1000L; boolean initiating = threadId == -1; - sendMediaMessage(isSmsForced(), "", attachmentManager.buildSlideDeck(), null, contacts, Collections.emptyList(), expiresIn, 0, subscriptionId, initiating, false); + sendMediaMessage(isSmsForced(), "", attachmentManager.buildSlideDeck(), null, contacts, Collections.emptyList(), expiresIn, false, subscriptionId, initiating, false); } private void selectContactInfo(ContactData contactData) { @@ -2135,7 +2135,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } else if (!forceSms && identityRecords.isUntrusted()) { handleUntrustedRecipients(); } else if (isMediaMessage) { - sendMediaMessage(forceSms, expiresIn, 0, subscriptionId, initiating); + sendMediaMessage(forceSms, expiresIn, false, subscriptionId, initiating); } else { sendTextMessage(forceSms, expiresIn, subscriptionId, initiating); } @@ -2151,11 +2151,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } } - private void sendMediaMessage(final boolean forceSms, final long expiresIn, final long revealDuration, final int subscriptionId, boolean initiating) + private void sendMediaMessage(final boolean forceSms, final long expiresIn, final boolean viewOnce, final int subscriptionId, boolean initiating) throws InvalidMessageException { Log.i(TAG, "Sending media message..."); - sendMediaMessage(forceSms, getMessage(), attachmentManager.buildSlideDeck(), inputPanel.getQuote().orNull(), Collections.emptyList(), linkPreviewViewModel.getActiveLinkPreviews(), expiresIn, revealDuration, subscriptionId, initiating, true); + sendMediaMessage(forceSms, getMessage(), attachmentManager.buildSlideDeck(), inputPanel.getQuote().orNull(), Collections.emptyList(), linkPreviewViewModel.getActiveLinkPreviews(), expiresIn, viewOnce, subscriptionId, initiating, true); } private ListenableFuture sendMediaMessage(final boolean forceSms, @@ -2165,7 +2165,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity List contacts, List previews, final long expiresIn, - final long revealDuration, + final boolean viewOnce, final int subscriptionId, final boolean initiating, final boolean clearComposeBox) @@ -2184,7 +2184,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } } - OutgoingMediaMessage outgoingMessageCandidate = new OutgoingMediaMessage(recipient, slideDeck, body, System.currentTimeMillis(), subscriptionId, expiresIn, revealDuration, distributionType, quote, contacts, previews); + OutgoingMediaMessage outgoingMessageCandidate = new OutgoingMediaMessage(recipient, slideDeck, body, System.currentTimeMillis(), subscriptionId, expiresIn, viewOnce, distributionType, quote, contacts, previews); final SettableFuture future = new SettableFuture<>(); final Context context = getApplicationContext(); @@ -2385,7 +2385,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity SlideDeck slideDeck = new SlideDeck(); slideDeck.addSlide(audioSlide); - sendMediaMessage(forceSms, "", slideDeck, inputPanel.getQuote().orNull(), Collections.emptyList(), Collections.emptyList(), expiresIn, 0, subscriptionId, initiating, true).addListener(new AssertedSuccessListener() { + sendMediaMessage(forceSms, "", slideDeck, inputPanel.getQuote().orNull(), Collections.emptyList(), Collections.emptyList(), expiresIn, false, subscriptionId, initiating, true).addListener(new AssertedSuccessListener() { @Override public void onSuccess(Void nothing) { new AsyncTask() { @@ -2513,7 +2513,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity slideDeck.addSlide(stickerSlide); - sendMediaMessage(transport.isSms(), "", slideDeck, null, Collections.emptyList(), Collections.emptyList(), expiresIn, 0, subscriptionId, initiating, clearCompose); + sendMediaMessage(transport.isSms(), "", slideDeck, null, Collections.emptyList(), Collections.emptyList(), expiresIn, false, subscriptionId, initiating, clearCompose); } @@ -2696,7 +2696,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } else { SlideDeck slideDeck = messageRecord.isMms() ? ((MmsMessageRecord) messageRecord).getSlideDeck() : new SlideDeck(); - if (messageRecord.isMms() && ((MmsMessageRecord) messageRecord).getRevealDuration() > 0 && slideDeck.getSlides().size() > 0) { + if (messageRecord.isMms() && ((MmsMessageRecord) messageRecord).isViewOnce() && slideDeck.getSlides().size() > 0) { Attachment attachment = new TombstoneAttachment(slideDeck.getSlides().get(0).getContentType(), true); slideDeck = new SlideDeck(); slideDeck.addSlide(MediaUtil.getSlideForAttachment(this, attachment)); diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java index 4ed11fed3c..1b4d10f488 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -79,7 +79,7 @@ import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob; -import org.thoughtcrime.securesms.jobs.MultiDeviceRevealUpdateJob; +import org.thoughtcrime.securesms.jobs.MultiDeviceViewOnceOpenJob; import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.longmessage.LongMessageActivity; @@ -89,9 +89,10 @@ import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.profiles.UnknownSenderView; +import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.revealable.RevealableMessageActivity; -import org.thoughtcrime.securesms.revealable.RevealableUtil; +import org.thoughtcrime.securesms.revealable.ViewOnceMessageActivity; +import org.thoughtcrime.securesms.revealable.ViewOnceUtil; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.stickers.StickerLocator; @@ -102,6 +103,7 @@ import org.thoughtcrime.securesms.util.StickyHeaderDecoration; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; +import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.whispersystems.libsignal.util.guava.Optional; @@ -963,35 +965,46 @@ public class ConversationFragment extends Fragment } @Override - public void onRevealableMessageClicked(@NonNull MmsMessageRecord messageRecord) { - if (messageRecord.getRevealDuration() == 0) { + public void onViewOnceMessageClicked(@NonNull MmsMessageRecord messageRecord) { + if (!messageRecord.isViewOnce()) { throw new AssertionError("Non-revealable message clicked."); } - if (messageRecord.getRevealStartTime() == 0) { - SimpleTask.run(getLifecycle(), () -> { - if (!messageRecord.isOutgoing()) { - Log.i(TAG, "Marking revealable message as opened."); - - DatabaseFactory.getMmsDatabase(requireContext()).markRevealStarted(messageRecord.getId()); - - ApplicationContext.getInstance(requireContext()) - .getRevealableMessageManager() - .scheduleIfNecessary(); - - ApplicationContext.getInstance(requireContext()) - .getJobManager() - .add(new MultiDeviceRevealUpdateJob(new MessagingDatabase.SyncMessageId(messageRecord.getIndividualRecipient().getAddress(), messageRecord.getDateSent()))); - } else { - Log.i(TAG, "Opening your own revealable message. It will automatically be marked as opened when it is sent."); - } - return null; - }, (nothing) -> { - startActivity(RevealableMessageActivity.getIntent(requireContext(), messageRecord.getId())); - }); - } else if (RevealableUtil.isViewable(messageRecord)) { - startActivity(RevealableMessageActivity.getIntent(requireContext(), messageRecord.getId())); + if (!ViewOnceUtil.isViewable(messageRecord)) { + Log.w(TAG, "View-once photo is not viewable!"); + return; } + + SimpleTask.run(getLifecycle(), () -> { + Log.i(TAG, "Copying the view-once photo to temp storage and deleting underlying media."); + + try { + InputStream inputStream = PartAuthority.getAttachmentStream(requireContext(), messageRecord.getSlideDeck().getThumbnailSlide().getUri()); + Uri tempUri = BlobProvider.getInstance().forData(inputStream, 0).createForSingleSessionOnDisk(requireContext()); + + DatabaseFactory.getAttachmentDatabase(requireContext()).deleteAttachmentFilesForMessage(messageRecord.getId()); + + ApplicationContext.getInstance(requireContext()) + .getViewOnceMessageManager() + .scheduleIfNecessary(); + + ApplicationContext.getInstance(requireContext()) + .getJobManager() + .add(new MultiDeviceViewOnceOpenJob(new MessagingDatabase.SyncMessageId(messageRecord.getIndividualRecipient().getAddress(), messageRecord.getDateSent()))); + + return tempUri; + } catch (IOException e) { + return null; + } + }, (uri) -> { + if (uri != null) { + startActivity(ViewOnceMessageActivity.getIntent(requireContext(), messageRecord.getId(), uri)); + } else { + Log.w(TAG, "Failed to open view-once photo. Showing a toast and deleting the attachments for the message just in case."); + Toast.makeText(requireContext(), R.string.ConversationFragment_failed_to_open_message, Toast.LENGTH_SHORT).show(); + SignalExecutors.BOUNDED.execute(() -> DatabaseFactory.getAttachmentDatabase(requireContext()).deleteAttachmentFilesForMessage(messageRecord.getId())); + } + }); } @Override diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationItem.java b/src/org/thoughtcrime/securesms/conversation/ConversationItem.java index ee8b44c974..b6ae879d10 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -68,7 +68,7 @@ import org.thoughtcrime.securesms.components.DocumentView; import org.thoughtcrime.securesms.components.LinkPreviewView; import org.thoughtcrime.securesms.components.Outliner; import org.thoughtcrime.securesms.components.QuoteView; -import org.thoughtcrime.securesms.revealable.RevealableMessageView; +import org.thoughtcrime.securesms.revealable.ViewOnceMessageView; import org.thoughtcrime.securesms.components.SharedContactView; import org.thoughtcrime.securesms.components.StickerView; import org.thoughtcrime.securesms.components.emoji.EmojiTextView; @@ -99,7 +99,7 @@ import org.thoughtcrime.securesms.mms.SlidesClickedListener; import org.thoughtcrime.securesms.mms.TextSlide; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; -import org.thoughtcrime.securesms.revealable.RevealableUtil; +import org.thoughtcrime.securesms.revealable.ViewOnceUtil; import org.thoughtcrime.securesms.stickers.StickerUrl; import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.DynamicTheme; @@ -163,7 +163,7 @@ public class ConversationItem extends LinearLayout private Stub sharedContactStub; private Stub linkPreviewStub; private Stub stickerStub; - private Stub revealableStub; + private Stub revealableStub; private @Nullable EventListener eventListener; private int defaultBubbleColor; @@ -175,7 +175,7 @@ public class ConversationItem extends LinearLayout private final SharedContactEventListener sharedContactEventListener = new SharedContactEventListener(); private final SharedContactClickListener sharedContactClickListener = new SharedContactClickListener(); private final LinkPreviewClickListener linkPreviewClickListener = new LinkPreviewClickListener(); - private final RevealableMessageClickListener revealableClickListener = new RevealableMessageClickListener(); + private final ViewOnceMessageClickListener revealableClickListener = new ViewOnceMessageClickListener(); private final Context context; @@ -314,7 +314,7 @@ public class ConversationItem extends LinearLayout protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); - if (!messageRecord.isOutgoing() && hasRevealableMessage(messageRecord) && RevealableUtil.isRevealExpired((MmsMessageRecord) messageRecord)) { + if (!messageRecord.isOutgoing() && isViewOnceMessage(messageRecord) && ViewOnceUtil.isViewed((MmsMessageRecord) messageRecord)) { outliner.setColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_sent_text_secondary_color)); outliner.draw(canvas, bodyBubble.getTop() + getPaddingTop(), bodyBubble.getRight(), bodyBubble.getBottom() + getPaddingTop(), bodyBubble.getLeft()); } @@ -324,7 +324,7 @@ public class ConversationItem extends LinearLayout int availableWidth; if (hasAudio(messageRecord)) { availableWidth = audioViewStub.get().getMeasuredWidth() + ViewUtil.getLeftMargin(audioViewStub.get()) + ViewUtil.getRightMargin(audioViewStub.get()); - } else if (!hasRevealableMessage(messageRecord) && (hasThumbnail(messageRecord) || hasBigImageLinkPreview(messageRecord))) { + } else if (!isViewOnceMessage(messageRecord) && (hasThumbnail(messageRecord) || hasBigImageLinkPreview(messageRecord))) { availableWidth = mediaThumbnailStub.get().getMeasuredWidth(); } else { availableWidth = bodyBubble.getMeasuredWidth() - bodyBubble.getPaddingLeft() - bodyBubble.getPaddingRight(); @@ -361,7 +361,7 @@ public class ConversationItem extends LinearLayout bodyBubble.getBackground().setColorFilter(defaultBubbleColor, PorterDuff.Mode.MULTIPLY); footer.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_sent_text_secondary_color)); footer.setIconColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_sent_icon_color)); - } else if (hasRevealableMessage(messageRecord) && RevealableUtil.isRevealExpired((MmsMessageRecord) messageRecord)) { + } else if (isViewOnceMessage(messageRecord) && ViewOnceUtil.isViewed((MmsMessageRecord) messageRecord)) { bodyBubble.getBackground().setColorFilter(ThemeUtil.getThemedColor(context, R.attr.conversation_item_reveal_viewed_background_color), PorterDuff.Mode.MULTIPLY); footer.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_sent_text_secondary_color)); footer.setIconColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_sent_icon_color)); @@ -440,7 +440,7 @@ public class ConversationItem extends LinearLayout !hasDocument(messageRecord) && !hasSharedContact(messageRecord) && !hasSticker(messageRecord) && - !hasRevealableMessage(messageRecord); + !isViewOnceMessage(messageRecord); } private boolean hasDocument(MessageRecord messageRecord) { @@ -477,8 +477,8 @@ public class ConversationItem extends LinearLayout !StickerUrl.isValidShareLink(linkPreview.getUrl()); } - private boolean hasRevealableMessage(MessageRecord messageRecord) { - return messageRecord.isMms() && ((MmsMessageRecord) messageRecord).getRevealDuration() > 0; + private boolean isViewOnceMessage(MessageRecord messageRecord) { + return messageRecord.isMms() && ((MmsMessageRecord) messageRecord).isViewOnce(); } private void setBodyText(MessageRecord messageRecord, @Nullable String searchQuery) { @@ -512,7 +512,7 @@ public class ConversationItem extends LinearLayout { boolean showControls = !messageRecord.isFailed(); - if (hasRevealableMessage(messageRecord)) { + if (isViewOnceMessage(messageRecord)) { revealableStub.get().setVisibility(VISIBLE); if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE); if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); @@ -928,7 +928,7 @@ public class ConversationItem extends LinearLayout } private void setGroupAuthorColor(@NonNull MessageRecord messageRecord) { - if (!messageRecord.isOutgoing() && hasRevealableMessage(messageRecord) && RevealableUtil.isRevealExpired((MmsMessageRecord) messageRecord)) { + if (!messageRecord.isOutgoing() && isViewOnceMessage(messageRecord) && ViewOnceUtil.isViewed((MmsMessageRecord) messageRecord)) { groupSender.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_sticker_author_color)); groupSenderProfileName.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_sticker_author_color)); } else if (hasSticker(messageRecord)) { @@ -1169,13 +1169,13 @@ public class ConversationItem extends LinearLayout } } - private class RevealableMessageClickListener implements View.OnClickListener { + private class ViewOnceMessageClickListener implements View.OnClickListener { @Override public void onClick(View view) { - RevealableMessageView revealView = (RevealableMessageView) view; + ViewOnceMessageView revealView = (ViewOnceMessageView) view; - if (eventListener != null && batchSelected.isEmpty() && messageRecord.isMms() && RevealableUtil.isViewable((MmsMessageRecord) messageRecord)) { - eventListener.onRevealableMessageClicked((MmsMessageRecord) messageRecord); + if (eventListener != null && batchSelected.isEmpty() && messageRecord.isMms() && ViewOnceUtil.isViewable((MmsMessageRecord) messageRecord)) { + eventListener.onViewOnceMessageClicked((MmsMessageRecord) messageRecord); } else if (batchSelected.isEmpty() && messageRecord.isMms() && revealView.requiresTapToDownload((MmsMessageRecord) messageRecord)) { singleDownloadClickListener.onClick(view, ((MmsMessageRecord) messageRecord).getSlideDeck().getThumbnailSlide()); } else { diff --git a/src/org/thoughtcrime/securesms/database/MediaDatabase.java b/src/org/thoughtcrime/securesms/database/MediaDatabase.java index d169f26568..7ba327c8da 100644 --- a/src/org/thoughtcrime/securesms/database/MediaDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MediaDatabase.java @@ -47,7 +47,7 @@ public class MediaDatabase extends Database { + "WHERE " + AttachmentDatabase.MMS_ID + " IN (SELECT " + MmsSmsColumns.ID + " FROM " + MmsDatabase.TABLE_NAME + " WHERE " + MmsDatabase.THREAD_ID + " = ?) AND (%s) AND " - + MmsDatabase.REVEAL_DURATION + " = 0 AND " + + MmsDatabase.VIEW_ONCE + " = 0 AND " + AttachmentDatabase.DATA + " IS NOT NULL AND " + AttachmentDatabase.QUOTE + " = 0 AND " + AttachmentDatabase.STICKER_PACK_ID + " IS NULL " diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 53ded94af1..8fdafe582c 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -63,8 +63,8 @@ import org.thoughtcrime.securesms.mms.QuoteModel; import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; -import org.thoughtcrime.securesms.revealable.RevealExpirationInfo; -import org.thoughtcrime.securesms.revealable.RevealableUtil; +import org.thoughtcrime.securesms.revealable.ViewOnceExpirationInfo; +import org.thoughtcrime.securesms.revealable.ViewOnceUtil; import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; @@ -109,8 +109,7 @@ public class MmsDatabase extends MessagingDatabase { static final String SHARED_CONTACTS = "shared_contacts"; static final String LINK_PREVIEWS = "previews"; - public static final String REVEAL_DURATION = "reveal_duration"; - public static final String REVEAL_START_TIME = "reveal_start_time"; + public static final String VIEW_ONCE = "reveal_duration"; public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + THREAD_ID + " INTEGER, " + DATE_SENT + " INTEGER, " + DATE_RECEIVED + " INTEGER, " + MESSAGE_BOX + " INTEGER, " + @@ -131,7 +130,7 @@ public class MmsDatabase extends MessagingDatabase { READ_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + QUOTE_ID + " INTEGER DEFAULT 0, " + QUOTE_AUTHOR + " TEXT, " + QUOTE_BODY + " TEXT, " + QUOTE_ATTACHMENT + " INTEGER DEFAULT -1, " + QUOTE_MISSING + " INTEGER DEFAULT 0, " + SHARED_CONTACTS + " TEXT, " + UNIDENTIFIED + " INTEGER DEFAULT 0, " + - LINK_PREVIEWS + " TEXT, " + REVEAL_DURATION + " INTEGER DEFAULT 0, " + REVEAL_START_TIME + " INTEGER DEFAULT 0);"; + LINK_PREVIEWS + " TEXT, " + VIEW_ONCE + " INTEGER DEFAULT 0);"; public static final String[] CREATE_INDEXS = { "CREATE INDEX IF NOT EXISTS mms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");", @@ -152,7 +151,7 @@ public class MmsDatabase extends MessagingDatabase { BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID, DELIVERY_RECEIPT_COUNT, READ_RECEIPT_COUNT, MISMATCHED_IDENTITIES, NETWORK_FAILURE, SUBSCRIPTION_ID, EXPIRES_IN, EXPIRE_STARTED, NOTIFIED, QUOTE_ID, QUOTE_AUTHOR, QUOTE_BODY, QUOTE_ATTACHMENT, QUOTE_MISSING, - SHARED_CONTACTS, LINK_PREVIEWS, UNIDENTIFIED, REVEAL_DURATION, REVEAL_START_TIME, + SHARED_CONTACTS, LINK_PREVIEWS, UNIDENTIFIED, VIEW_ONCE, "json_group_array(json_object(" + "'" + AttachmentDatabase.ROW_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + ", " + "'" + AttachmentDatabase.UNIQUE_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UNIQUE_ID + ", " + @@ -437,60 +436,6 @@ public class MmsDatabase extends MessagingDatabase { notifyConversationListeners(threadId); } - public void markRevealStarted(long messageId) { - markRevealStarted(messageId, System.currentTimeMillis()); - } - - public void markRevealStarted(long messageId, long startTime) { - ContentValues contentValues = new ContentValues(); - contentValues.put(REVEAL_START_TIME, startTime); - - SQLiteDatabase db = databaseHelper.getWritableDatabase(); - db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)}); - - long threadId = getThreadIdForMessage(messageId); - notifyConversationListeners(threadId); - } - - public List markRevealStarted(@NonNull SyncMessageId messageId, long proposedStartTime) { - SQLiteDatabase db = databaseHelper.getWritableDatabase(); - List expirationInfos = new LinkedList<>(); - - String[] projection = new String[] { ID, ADDRESS, THREAD_ID, DATE_SENT, DATE_RECEIVED, REVEAL_DURATION, REVEAL_START_TIME }; - String selection = DATE_SENT + " = ?"; - String[] args = new String[] { String.valueOf(messageId.getTimetamp()) }; - - try (Cursor cursor = db.query(TABLE_NAME, projection, selection, args, null, null, null)) { - while (cursor != null && cursor.moveToNext()) { - Address theirAddress = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))); - Address ourAddress = messageId.getAddress(); - - if (ourAddress.equals(theirAddress) || theirAddress.isGroup()) { - long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID)); - long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID)); - long receiveTime = cursor.getLong(cursor.getColumnIndexOrThrow(DATE_RECEIVED)); - long revealDuration = cursor.getLong(cursor.getColumnIndexOrThrow(REVEAL_DURATION)); - long revealStartTime = cursor.getLong(cursor.getColumnIndexOrThrow(REVEAL_START_TIME)); - - revealStartTime = revealStartTime > 0 ? Math.min(proposedStartTime, revealStartTime) : proposedStartTime; - revealStartTime = Math.min(revealStartTime, System.currentTimeMillis()); - - ContentValues values = new ContentValues(); - - values.put(REVEAL_START_TIME, revealStartTime); - expirationInfos.add(new RevealExpirationInfo(id, receiveTime, revealStartTime, revealDuration)); - - db.update(TABLE_NAME, values, ID_WHERE, new String[] { String.valueOf(id) }); - - DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId); - notifyConversationListeners(threadId); - } - } - } - - return expirationInfos; - } - public void markAsNotified(long id) { SQLiteDatabase database = databaseHelper.getWritableDatabase(); ContentValues contentValues = new ContentValues(); @@ -668,7 +613,7 @@ public class MmsDatabase extends MessagingDatabase { long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(NORMALIZED_DATE_SENT)); int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SUBSCRIPTION_ID)); long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN)); - long revealDuration = cursor.getLong(cursor.getColumnIndexOrThrow(REVEAL_DURATION)); + boolean viewOnce = cursor.getLong(cursor.getColumnIndexOrThrow(VIEW_ONCE)) == 1; String address = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)); long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID)); int distributionType = DatabaseFactory.getThreadDatabase(context).getDistributionType(threadId); @@ -715,12 +660,12 @@ public class MmsDatabase extends MessagingDatabase { } if (body != null && (Types.isGroupQuit(outboxType) || Types.isGroupUpdate(outboxType))) { - return new OutgoingGroupMediaMessage(recipient, body, attachments, timestamp, 0, 0, quote, contacts, previews); + return new OutgoingGroupMediaMessage(recipient, body, attachments, timestamp, 0, false, quote, contacts, previews); } else if (Types.isExpirationTimerUpdate(outboxType)) { return new OutgoingExpirationUpdateMessage(recipient, timestamp, expiresIn); } - OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, revealDuration, distributionType, quote, contacts, previews, networkFailures, mismatches); + OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, viewOnce, distributionType, quote, contacts, previews, networkFailures, mismatches); if (Types.isSecureType(outboxType)) { return new OutgoingSecureMediaMessage(message); @@ -824,7 +769,7 @@ public class MmsDatabase extends MessagingDatabase { contentValues.put(READ, 1); contentValues.put(DATE_RECEIVED, contentValues.getAsLong(DATE_SENT)); contentValues.put(EXPIRES_IN, request.getExpiresIn()); - contentValues.put(REVEAL_DURATION, request.getRevealDuration()); + contentValues.put(VIEW_ONCE, request.isViewOnce()); List attachments = new LinkedList<>(); @@ -892,7 +837,7 @@ public class MmsDatabase extends MessagingDatabase { contentValues.put(PART_COUNT, retrieved.getAttachments().size()); contentValues.put(SUBSCRIPTION_ID, retrieved.getSubscriptionId()); contentValues.put(EXPIRES_IN, retrieved.getExpiresIn()); - contentValues.put(REVEAL_DURATION, retrieved.getRevealDuration()); + contentValues.put(VIEW_ONCE, retrieved.isViewOnce() ? 1 : 0); contentValues.put(READ, retrieved.isExpirationUpdate() ? 1 : 0); contentValues.put(UNIDENTIFIED, retrieved.isUnidentified()); @@ -1048,7 +993,7 @@ public class MmsDatabase extends MessagingDatabase { contentValues.put(DATE_RECEIVED, System.currentTimeMillis()); contentValues.put(SUBSCRIPTION_ID, message.getSubscriptionId()); contentValues.put(EXPIRES_IN, message.getExpiresIn()); - contentValues.put(REVEAL_DURATION, message.getRevealDuration()); + contentValues.put(VIEW_ONCE, message.isViewOnce()); contentValues.put(ADDRESS, message.getRecipient().getAddress().serialize()); contentValues.put(DELIVERY_RECEIPT_COUNT, Stream.of(earlyDeliveryReceipts.values()).mapToLong(Long::longValue).sum()); contentValues.put(READ_RECEIPT_COUNT, Stream.of(earlyReadReceipts.values()).mapToLong(Long::longValue).sum()); @@ -1307,34 +1252,31 @@ public class MmsDatabase extends MessagingDatabase { database.delete(TABLE_NAME, null, null); } - public @Nullable RevealExpirationInfo getNearestExpiringRevealableMessage() { + public @Nullable + ViewOnceExpirationInfo getNearestExpiringViewOnceMessage() { SQLiteDatabase db = databaseHelper.getReadableDatabase(); - RevealExpirationInfo info = null; + ViewOnceExpirationInfo info = null; long nearestExpiration = Long.MAX_VALUE; String query = "SELECT " + TABLE_NAME + "." + ID + ", " + - REVEAL_DURATION + ", " + - REVEAL_START_TIME + ", " + + VIEW_ONCE + ", " + DATE_RECEIVED + " " + "FROM " + TABLE_NAME + " INNER JOIN " + AttachmentDatabase.TABLE_NAME + " " + "ON " + TABLE_NAME + "." + ID + " = " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.MMS_ID + " " + "WHERE " + - REVEAL_DURATION + " > 0 AND " + + VIEW_ONCE + " > 0 AND " + "(" + AttachmentDatabase.DATA + " NOT NULL OR " + AttachmentDatabase.TRANSFER_STATE + " != ?)"; String[] args = new String[] { String.valueOf(AttachmentDatabase.TRANSFER_PROGRESS_DONE) }; try (Cursor cursor = db.rawQuery(query, args)) { while (cursor != null && cursor.moveToNext()) { long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID)); - long revealDuration = cursor.getLong(cursor.getColumnIndexOrThrow(REVEAL_DURATION)); - long revealStartTime = cursor.getLong(cursor.getColumnIndexOrThrow(REVEAL_START_TIME)); long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(DATE_RECEIVED)); - long expiresAt = revealStartTime > 0 ? revealStartTime + revealDuration - : dateReceived + RevealableUtil.MAX_LIFESPAN; + long expiresAt = dateReceived + ViewOnceUtil.MAX_LIFESPAN; if (info == null || expiresAt < nearestExpiration) { - info = new RevealExpirationInfo(id, dateReceived, revealStartTime, revealDuration); + info = new ViewOnceExpirationInfo(id, dateReceived); nearestExpiration = expiresAt; } } @@ -1442,8 +1384,7 @@ public class MmsDatabase extends MessagingDatabase { message.getSubscriptionId(), message.getExpiresIn(), System.currentTimeMillis(), - message.getRevealDuration(), - 0, + message.isViewOnce(), 0, message.getOutgoingQuote() != null ? new Quote(message.getOutgoingQuote().getId(), @@ -1541,8 +1482,7 @@ public class MmsDatabase extends MessagingDatabase { long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRES_IN)); long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRE_STARTED)); boolean unidentified = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.UNIDENTIFIED)) == 1; - long revealDuration = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.REVEAL_DURATION)); - long revealStartTime = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.REVEAL_START_TIME)); + boolean isViewOnce = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.VIEW_ONCE)) == 1; if (!TextSecurePreferences.isReadReceiptsEnabled(context)) { readReceiptCount = 0; @@ -1563,8 +1503,7 @@ public class MmsDatabase extends MessagingDatabase { addressDeviceId, dateSent, dateReceived, deliveryReceiptCount, threadId, body, slideDeck, partCount, box, mismatches, networkFailures, subscriptionId, expiresIn, expireStarted, - revealDuration, revealStartTime, - readReceiptCount, quote, contacts, previews, unidentified); + isViewOnce, readReceiptCount, quote, contacts, previews, unidentified); } private Recipient getRecipientFor(String serialized) { diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index dfa9614d98..fd2962f2fd 100644 --- a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -71,8 +71,7 @@ public class MmsSmsDatabase extends Database { MmsDatabase.QUOTE_ATTACHMENT, MmsDatabase.SHARED_CONTACTS, MmsDatabase.LINK_PREVIEWS, - MmsDatabase.REVEAL_DURATION, - MmsDatabase.REVEAL_START_TIME}; + MmsDatabase.VIEW_ONCE}; public MmsSmsDatabase(Context context, SQLCipherOpenHelper databaseHelper) { super(context, databaseHelper); @@ -273,8 +272,7 @@ public class MmsSmsDatabase extends Database { MmsDatabase.QUOTE_ATTACHMENT, MmsDatabase.SHARED_CONTACTS, MmsDatabase.LINK_PREVIEWS, - MmsDatabase.REVEAL_DURATION, - MmsDatabase.REVEAL_START_TIME}; + MmsDatabase.VIEW_ONCE}; String[] smsProjection = {SmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT, SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED, @@ -301,8 +299,7 @@ public class MmsSmsDatabase extends Database { MmsDatabase.QUOTE_ATTACHMENT, MmsDatabase.SHARED_CONTACTS, MmsDatabase.LINK_PREVIEWS, - MmsDatabase.REVEAL_DURATION, - MmsDatabase.REVEAL_START_TIME}; + MmsDatabase.VIEW_ONCE}; SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder(); SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder(); @@ -373,8 +370,7 @@ public class MmsSmsDatabase extends Database { mmsColumnsPresent.add(MmsDatabase.QUOTE_ATTACHMENT); mmsColumnsPresent.add(MmsDatabase.SHARED_CONTACTS); mmsColumnsPresent.add(MmsDatabase.LINK_PREVIEWS); - mmsColumnsPresent.add(MmsDatabase.REVEAL_DURATION); - mmsColumnsPresent.add(MmsDatabase.REVEAL_START_TIME); + mmsColumnsPresent.add(MmsDatabase.VIEW_ONCE); Set smsColumnsPresent = new HashSet<>(); smsColumnsPresent.add(MmsSmsColumns.ID); diff --git a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java index c378c6373c..bd24659770 100644 --- a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -46,7 +46,6 @@ import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.DelimiterUtil; import org.thoughtcrime.securesms.util.JsonUtils; -import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.Pair; @@ -630,7 +629,7 @@ public class ThreadDatabase extends Database { SlideDeck slideDeck = ((MediaMmsMessageRecord)record).getSlideDeck(); Slide thumbnail = slideDeck.getThumbnailSlide(); - if (thumbnail != null && ((MmsMessageRecord) record).getRevealDuration() == 0) { + if (thumbnail != null && !((MmsMessageRecord) record).isViewOnce()) { return thumbnail.getThumbnailUri(); } @@ -650,7 +649,7 @@ public class ThreadDatabase extends Database { } private @Nullable Extra getExtrasFor(MessageRecord record) { - if (record.isMms() && ((MmsMessageRecord) record).getRevealDuration() > 0) { + if (record.isMms() && ((MmsMessageRecord) record).isViewOnce()) { return Extra.forRevealableMessage(); } else if (record.isMms() && ((MmsMessageRecord) record).getSlideDeck().getStickerSlide() != null) { return Extra.forSticker(); diff --git a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 67aafa94a8..d9a2307f03 100644 --- a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -67,11 +67,13 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int JOBMANAGER_STRIKES_BACK = 20; private static final int STICKERS = 21; private static final int REVEALABLE_MESSAGES = 22; + private static final int VIEW_ONCE_ONLY = 23; - private static final int DATABASE_VERSION = 22; + private static final int DATABASE_VERSION = 23; private static final String DATABASE_NAME = "signal.db"; private final Context context; + private final DatabaseSecret databaseSecret; public SQLCipherOpenHelper(@NonNull Context context, @NonNull DatabaseSecret databaseSecret) { @@ -471,6 +473,11 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL("ALTER TABLE thread ADD COLUMN snippet_extras TEXT DEFAULT NULL"); } + if (oldVersion < VIEW_ONCE_ONLY) { + db.execSQL("UPDATE mms SET reveal_duration = 1 WHERE reveal_duration > 0"); + db.execSQL("UPDATE mms SET reveal_start_time = 0"); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java index 3ad7384af9..6b3108eca9 100644 --- a/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java @@ -55,13 +55,13 @@ public class MediaMmsMessageRecord extends MmsMessageRecord { List mismatches, List failures, int subscriptionId, long expiresIn, long expireStarted, - long revealDuration, long revealStartTime, int readReceiptCount, + boolean viewOnce, int readReceiptCount, @Nullable Quote quote, @NonNull List contacts, @NonNull List linkPreviews, boolean unidentified) { super(id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent, dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures, - subscriptionId, expiresIn, expireStarted, revealDuration, revealStartTime, slideDeck, + subscriptionId, expiresIn, expireStarted, viewOnce, slideDeck, readReceiptCount, quote, contacts, linkPreviews, unidentified); this.partCount = partCount; } diff --git a/src/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java index a4bf04975b..add9c7bf05 100644 --- a/src/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java @@ -22,25 +22,23 @@ public abstract class MmsMessageRecord extends MessageRecord { private final @NonNull List contacts = new LinkedList<>(); private final @NonNull List linkPreviews = new LinkedList<>(); - private final long revealDuration; - private final long revealStartTime; + private final boolean viewOnce; MmsMessageRecord(long id, String body, Recipient conversationRecipient, Recipient individualRecipient, int recipientDeviceId, long dateSent, long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount, long type, List mismatches, List networkFailures, int subscriptionId, long expiresIn, - long expireStarted, long revealDuration, long revealStartTime, + long expireStarted, boolean viewOnce, @NonNull SlideDeck slideDeck, int readReceiptCount, @Nullable Quote quote, @NonNull List contacts, @NonNull List linkPreviews, boolean unidentified) { super(id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, mismatches, networkFailures, subscriptionId, expiresIn, expireStarted, readReceiptCount, unidentified); - this.slideDeck = slideDeck; - this.quote = quote; - this.revealDuration = revealDuration; - this.revealStartTime = revealStartTime; + this.slideDeck = slideDeck; + this.quote = quote; + this.viewOnce = viewOnce; this.contacts.addAll(contacts); this.linkPreviews.addAll(linkPreviews); @@ -83,11 +81,7 @@ public abstract class MmsMessageRecord extends MessageRecord { return linkPreviews; } - public long getRevealDuration() { - return revealDuration; - } - - public long getRevealStartTime() { - return revealStartTime; + public boolean isViewOnce() { + return viewOnce; } } diff --git a/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java index fbdafae288..7ce9a14803 100644 --- a/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java @@ -57,7 +57,7 @@ public class NotificationMmsMessageRecord extends MmsMessageRecord { super(id, "", conversationRecipient, individualRecipient, recipientDeviceId, dateSent, dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, new LinkedList(), new LinkedList(), subscriptionId, - 0, 0, 0, 0, slideDeck, readReceiptCount, null, Collections.emptyList(), Collections.emptyList(), false); + 0, 0, false, slideDeck, readReceiptCount, null, Collections.emptyList(), Collections.emptyList(), false); this.contentLocation = contentLocation; this.messageSize = messageSize; diff --git a/src/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java b/src/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java index 9601ea0452..2611e46b81 100644 --- a/src/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java +++ b/src/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java @@ -121,7 +121,7 @@ public class GiphyActivity extends PassphraseRequiredActionBarActivity return BlobProvider.getInstance() .forData(data) .withMimeType(MediaUtil.IMAGE_GIF) - .createForSingleSessionOnDisk(GiphyActivity.this, e -> Log.w(TAG, "Failed to write to disk.", e)); + .createForSingleSessionOnDisk(GiphyActivity.this); } catch (InterruptedException | ExecutionException | IOException e) { Log.w(TAG, e); return null; diff --git a/src/org/thoughtcrime/securesms/groups/GroupManager.java b/src/org/thoughtcrime/securesms/groups/GroupManager.java index 7ec883a459..924e402b98 100644 --- a/src/org/thoughtcrime/securesms/groups/GroupManager.java +++ b/src/org/thoughtcrime/securesms/groups/GroupManager.java @@ -115,7 +115,7 @@ public class GroupManager { avatarAttachment = new UriAttachment(avatarUri, MediaUtil.IMAGE_PNG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length, null, false, false, null, null); } - OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, groupContext, avatarAttachment, System.currentTimeMillis(), 0, 0, null, Collections.emptyList(), Collections.emptyList()); + OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, groupContext, avatarAttachment, System.currentTimeMillis(), 0, false, null, Collections.emptyList(), Collections.emptyList()); long threadId = MessageSender.send(context, outgoingMessage, -1, false, null); return new GroupActionResult(groupRecipient, threadId); diff --git a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java index c7eb6cc595..77f53a65ef 100644 --- a/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java +++ b/src/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java @@ -212,7 +212,7 @@ public class GroupMessageProcessor { MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context); Address addres = Address.fromExternal(context, GroupUtil.getEncodedId(group.getGroupId(), false)); Recipient recipient = Recipient.from(context, addres, false); - OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipient, storage, null, content.getTimestamp(), 0, 0, null, Collections.emptyList(), Collections.emptyList()); + OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipient, storage, null, content.getTimestamp(), 0, false, null, Collections.emptyList(), Collections.emptyList()); long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); long messageId = mmsDatabase.insertMessageOutbox(outgoingMessage, threadId, false, null); @@ -222,7 +222,7 @@ public class GroupMessageProcessor { } else { SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); String body = Base64.encodeBytes(storage.toByteArray()); - IncomingTextMessage incoming = new IncomingTextMessage(Address.fromExternal(context, content.getSender()), content.getSenderDevice(), content.getTimestamp(), body, Optional.of(group), 0, 0, content.isNeedsReceipt()); + IncomingTextMessage incoming = new IncomingTextMessage(Address.fromExternal(context, content.getSender()), content.getSenderDevice(), content.getTimestamp(), body, Optional.of(group), 0, content.isNeedsReceipt()); IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, storage, body); Optional insertResult = smsDatabase.insertMessageInbox(groupMessage); diff --git a/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index 07b6162a09..fe9d2ce24d 100644 --- a/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -41,10 +41,10 @@ public final class JobManagerFactories { put(MultiDeviceGroupUpdateJob.KEY, new MultiDeviceGroupUpdateJob.Factory()); put(MultiDeviceProfileKeyUpdateJob.KEY, new MultiDeviceProfileKeyUpdateJob.Factory()); put(MultiDeviceReadUpdateJob.KEY, new MultiDeviceReadUpdateJob.Factory()); - put(MultiDeviceRevealUpdateJob.KEY, new MultiDeviceRevealUpdateJob.Factory()); put(MultiDeviceStickerPackOperationJob.KEY, new MultiDeviceStickerPackOperationJob.Factory()); put(MultiDeviceStickerPackSyncJob.KEY, new MultiDeviceStickerPackSyncJob.Factory()); put(MultiDeviceVerifiedUpdateJob.KEY, new MultiDeviceVerifiedUpdateJob.Factory()); + put(MultiDeviceViewOnceOpenJob.KEY, new MultiDeviceViewOnceOpenJob.Factory()); put(PushContentReceiveJob.KEY, new PushContentReceiveJob.Factory()); put(PushDecryptJob.KEY, new PushDecryptJob.Factory()); put(PushGroupSendJob.KEY, new PushGroupSendJob.Factory()); diff --git a/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java index ca57a3cc5a..af7577f097 100644 --- a/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java @@ -247,7 +247,7 @@ public class MmsDownloadJob extends BaseJob { group = Optional.of(Address.fromSerialized(DatabaseFactory.getGroupDatabase(context).getOrCreateGroupForMembers(new LinkedList<>(members), true))); } - IncomingMediaMessage message = new IncomingMediaMessage(from, group, body, retrieved.getDate() * 1000L, attachments, subscriptionId, 0, false, 0, false); + IncomingMediaMessage message = new IncomingMediaMessage(from, group, body, retrieved.getDate() * 1000L, attachments, subscriptionId, 0, false, false, false); Optional insertResult = database.insertMessageInbox(message, contentLocation, threadId); if (insertResult.isPresent()) { diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceRevealUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceViewOnceOpenJob.java similarity index 83% rename from src/org/thoughtcrime/securesms/jobs/MultiDeviceRevealUpdateJob.java rename to src/org/thoughtcrime/securesms/jobs/MultiDeviceViewOnceOpenJob.java index a14015e10e..ed33fe5ebf 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceRevealUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceViewOnceOpenJob.java @@ -16,25 +16,25 @@ import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; -import org.whispersystems.signalservice.api.messages.multidevice.MessageTimerReadMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; +import org.whispersystems.signalservice.api.messages.multidevice.ViewOnceOpenMessage; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import java.io.IOException; import java.io.Serializable; import java.util.concurrent.TimeUnit; -public class MultiDeviceRevealUpdateJob extends BaseJob { +public class MultiDeviceViewOnceOpenJob extends BaseJob { public static final String KEY = "MultiDeviceRevealUpdateJob"; - private static final String TAG = MultiDeviceRevealUpdateJob.class.getSimpleName(); + private static final String TAG = Log.tag(MultiDeviceViewOnceOpenJob.class); private static final String KEY_MESSAGE_ID = "message_id"; private SerializableSyncMessageId messageId; - public MultiDeviceRevealUpdateJob(SyncMessageId messageId) { + public MultiDeviceViewOnceOpenJob(SyncMessageId messageId) { this(new Parameters.Builder() .addConstraint(NetworkConstraint.KEY) .setLifespan(TimeUnit.DAYS.toMillis(1)) @@ -43,7 +43,7 @@ public class MultiDeviceRevealUpdateJob extends BaseJob { messageId); } - private MultiDeviceRevealUpdateJob(@NonNull Parameters parameters, @NonNull SyncMessageId syncMessageId) { + private MultiDeviceViewOnceOpenJob(@NonNull Parameters parameters, @NonNull SyncMessageId syncMessageId) { super(parameters); this.messageId = new SerializableSyncMessageId(syncMessageId.getAddress().toPhoneString(), syncMessageId.getTimetamp()); } @@ -74,9 +74,9 @@ public class MultiDeviceRevealUpdateJob extends BaseJob { } SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); - MessageTimerReadMessage timerMessage = new MessageTimerReadMessage(messageId.sender, messageId.timestamp); + ViewOnceOpenMessage openMessage = new ViewOnceOpenMessage(messageId.sender, messageId.timestamp); - messageSender.sendMessage(SignalServiceSyncMessage.forMessageTimerRead(timerMessage), UnidentifiedAccessUtil.getAccessForSync(context)); + messageSender.sendMessage(SignalServiceSyncMessage.forViewOnceOpen(openMessage), UnidentifiedAccessUtil.getAccessForSync(context)); } @Override @@ -105,9 +105,9 @@ public class MultiDeviceRevealUpdateJob extends BaseJob { } } - public static final class Factory implements Job.Factory { + public static final class Factory implements Job.Factory { @Override - public @NonNull MultiDeviceRevealUpdateJob create(@NonNull Parameters parameters, @NonNull Data data) { + public @NonNull MultiDeviceViewOnceOpenJob create(@NonNull Parameters parameters, @NonNull Data data) { SerializableSyncMessageId messageId; try { @@ -118,7 +118,7 @@ public class MultiDeviceRevealUpdateJob extends BaseJob { SyncMessageId syncMessageId = new SyncMessageId(Address.fromSerialized(messageId.sender), messageId.timestamp); - return new MultiDeviceRevealUpdateJob(parameters, syncMessageId); + return new MultiDeviceViewOnceOpenJob(parameters, syncMessageId); } } } diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 07c5dc3ad4..1502b3dae9 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -80,8 +80,6 @@ import org.thoughtcrime.securesms.mms.StickerSlide; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.revealable.RevealExpirationInfo; -import org.thoughtcrime.securesms.revealable.RevealableMessageManager; import org.thoughtcrime.securesms.service.WebRtcCallService; import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage; @@ -112,13 +110,13 @@ import org.whispersystems.signalservice.api.messages.calls.HangupMessage; import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage; import org.whispersystems.signalservice.api.messages.calls.OfferMessage; import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage; -import org.whispersystems.signalservice.api.messages.multidevice.MessageTimerReadMessage; import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage; import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage; import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage; import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage; +import org.whispersystems.signalservice.api.messages.multidevice.ViewOnceOpenMessage; import org.whispersystems.signalservice.api.messages.shared.SharedContact; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException; @@ -283,7 +281,7 @@ public class PushDecryptJob extends BaseJob { if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(content, syncMessage.getSent().get()); else if (syncMessage.getRequest().isPresent()) handleSynchronizeRequestMessage(syncMessage.getRequest().get()); else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(syncMessage.getRead().get(), content.getTimestamp()); - else if (syncMessage.getMessageTimerRead().isPresent()) handleSynchronizeMessageTimerReadMessage(syncMessage.getMessageTimerRead().get(), content.getTimestamp()); + else if (syncMessage.getViewOnceOpen().isPresent()) handleSynchronizeViewOnceOpenMessage(syncMessage.getViewOnceOpen().get(), content.getTimestamp()); else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get()); else if (syncMessage.getStickerPackOperations().isPresent()) handleSynchronizeStickerPackOperation(syncMessage.getStickerPackOperations().get()); else Log.w(TAG, "Contains no known sync types..."); @@ -431,7 +429,7 @@ public class PushDecryptJob extends BaseJob { IncomingTextMessage incomingTextMessage = new IncomingTextMessage(Address.fromExternal(context, content.getSender()), content.getSenderDevice(), content.getTimestamp(), - "", Optional.absent(), 0, 0, + "", Optional.absent(), 0, content.isNeedsReceipt()); Long threadId; @@ -515,7 +513,7 @@ public class PushDecryptJob extends BaseJob { IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, content.getSender()), message.getTimestamp(), -1, message.getExpiresInSeconds() * 1000L, true, - 0, + false, content.isNeedsReceipt(), Optional.absent(), message.getGroupInfo(), @@ -676,11 +674,14 @@ public class PushDecryptJob extends BaseJob { MessageNotifier.updateNotification(context); } - private void handleSynchronizeMessageTimerReadMessage(@NonNull MessageTimerReadMessage timerMessage, long envelopeTimestamp) { - SyncMessageId messageId = new SyncMessageId(Address.fromExternal(context, timerMessage.getSender()), timerMessage.getTimestamp()); + private void handleSynchronizeViewOnceOpenMessage(@NonNull ViewOnceOpenMessage openMessage, long envelopeTimestamp) { + Address author = Address.fromExternal(context, openMessage.getSender()); + long timestamp = openMessage.getTimestamp(); + MessageRecord record = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(timestamp, author); - DatabaseFactory.getMmsDatabase(context).markRevealStarted(messageId, envelopeTimestamp); - ApplicationContext.getInstance(context).getRevealableMessageManager().scheduleIfNecessary(); + if (record != null && record.isMms()) { + DatabaseFactory.getAttachmentDatabase(context).deleteAttachmentFilesForMessage(record.getId()); + } MessageNotifier.setLastDesktopActivityTimestamp(envelopeTimestamp); MessageNotifier.cancelDelayedNotifications(); @@ -707,7 +708,7 @@ public class PushDecryptJob extends BaseJob { IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, content.getSender()), message.getTimestamp(), -1, message.getExpiresInSeconds() * 1000L, false, - message.getMessageTimerInSeconds() * 1000, + message.isViewOnce(), content.isNeedsReceipt(), message.getBody(), message.getGroupInfo(), @@ -747,8 +748,8 @@ public class PushDecryptJob extends BaseJob { if (insertResult.isPresent()) { MessageNotifier.updateNotification(context, insertResult.get().getThreadId()); - if (message.getMessageTimerInSeconds() > 0) { - ApplicationContext.getInstance(context).getRevealableMessageManager().scheduleIfNecessary(); + if (message.isViewOnce()) { + ApplicationContext.getInstance(context).getViewOnceMessageManager().scheduleIfNecessary(); } } } @@ -780,8 +781,8 @@ public class PushDecryptJob extends BaseJob { Optional sticker = getStickerAttachment(message.getMessage().getSticker()); Optional> sharedContacts = getContacts(message.getMessage().getSharedContacts()); Optional> previews = getLinkPreviews(message.getMessage().getPreviews(), message.getMessage().getBody().or("")); - long messageTimer = message.getMessage().getMessageTimerInSeconds() * 1000; - List syncAttachments = messageTimer == 0 ? PointerAttachment.forPointers(message.getMessage().getAttachments()) : Collections.emptyList(); + boolean viewOnce = message.getMessage().isViewOnce(); + List syncAttachments = viewOnce ? Collections.emptyList() : PointerAttachment.forPointers(message.getMessage().getAttachments()); if (sticker.isPresent()) { syncAttachments.add(sticker.get()); @@ -791,7 +792,7 @@ public class PushDecryptJob extends BaseJob { syncAttachments, message.getTimestamp(), -1, message.getMessage().getExpiresInSeconds() * 1000, - messageTimer, + viewOnce, ThreadDatabase.DistributionTypes.DEFAULT, quote.orNull(), sharedContacts.or(Collections.emptyList()), previews.or(Collections.emptyList()), @@ -921,7 +922,6 @@ public class PushDecryptJob extends BaseJob { message.getTimestamp(), body, message.getGroupInfo(), message.getExpiresInSeconds() * 1000L, - message.getMessageTimerInSeconds() * 1000L, content.isNeedsReceipt()); textMessage = new IncomingEncryptedMessage(textMessage, body); @@ -956,7 +956,7 @@ public class PushDecryptJob extends BaseJob { long messageId; if (isGroup) { - OutgoingMediaMessage outgoingMediaMessage = new OutgoingMediaMessage(recipient, new SlideDeck(), body, message.getTimestamp(), -1, expiresInMillis, 0, ThreadDatabase.DistributionTypes.DEFAULT, null, Collections.emptyList(), Collections.emptyList()); + OutgoingMediaMessage outgoingMediaMessage = new OutgoingMediaMessage(recipient, new SlideDeck(), body, message.getTimestamp(), -1, expiresInMillis, false, ThreadDatabase.DistributionTypes.DEFAULT, null, Collections.emptyList(), Collections.emptyList()); outgoingMediaMessage = new OutgoingSecureMediaMessage(outgoingMediaMessage); messageId = DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(outgoingMediaMessage, threadId, false, GroupReceiptDatabase.STATUS_UNKNOWN, null); @@ -1195,7 +1195,7 @@ public class PushDecryptJob extends BaseJob { } private boolean isInvalidMessage(@NonNull SignalServiceDataMessage message) { - if (message.getMessageTimerInSeconds() > 0) { + if (message.isViewOnce()) { return !message.getAttachments().isPresent() || message.getAttachments().get().size() != 1 || !MediaUtil.isImageType(message.getAttachments().get().get(0).getContentType().toLowerCase()); @@ -1229,7 +1229,7 @@ public class PushDecryptJob extends BaseJob { if (message.isMms()) { MmsMessageRecord mmsMessage = (MmsMessageRecord) message; - if (mmsMessage.getRevealDuration() == 0) { + if (!mmsMessage.isViewOnce()) { attachments = mmsMessage.getSlideDeck().asAttachments(); if (attachments.isEmpty()) { @@ -1335,7 +1335,7 @@ public class PushDecryptJob extends BaseJob { SmsDatabase database = DatabaseFactory.getSmsDatabase(context); IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, sender), senderDevice, timestamp, "", - group, 0, 0, false); + group, 0, false); textMessage = new IncomingEncryptedMessage(textMessage, ""); return database.insertMessageInbox(textMessage); diff --git a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java index 6077c4e062..4888e270b1 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java @@ -195,11 +195,8 @@ public class PushGroupSendJob extends PushSendJob { .scheduleDeletion(messageId, true, message.getExpiresIn()); } - if (message.getRevealDuration() > 0) { - database.markRevealStarted(messageId); - ApplicationContext.getInstance(context) - .getRevealableMessageManager() - .scheduleIfNecessary(); + if (message.isViewOnce()) { + DatabaseFactory.getAttachmentDatabase(context).deleteAttachmentFilesForMessage(messageId); } } else if (!networkFailures.isEmpty()) { throw new RetryLaterException(); @@ -269,7 +266,7 @@ public class PushGroupSendJob extends PushSendJob { .withAttachments(attachmentPointers) .withBody(message.getBody()) .withExpiration((int)(message.getExpiresIn() / 1000)) - .withMessageTimer((int)(message.getRevealDuration() / 1000)) + .withViewOnce(message.isViewOnce()) .asExpirationUpdate(message.isExpirationUpdate()) .withProfileKey(profileKey.orNull()) .withQuote(quote.orNull()) diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java index fc2b73ddb2..cd5b990e55 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java @@ -164,11 +164,8 @@ public class PushMediaSendJob extends PushSendJob { expirationManager.scheduleDeletion(messageId, true, message.getExpiresIn()); } - if (message.getRevealDuration() > 0) { - database.markRevealStarted(messageId); - ApplicationContext.getInstance(context) - .getRevealableMessageManager() - .scheduleIfNecessary(); + if (message.isViewOnce()) { + DatabaseFactory.getAttachmentDatabase(context).deleteAttachmentFilesForMessage(messageId); } log(TAG, "Sent message: " + messageId); @@ -230,7 +227,7 @@ public class PushMediaSendJob extends PushSendJob { .withAttachments(serviceAttachments) .withTimestamp(message.getSentTimeMillis()) .withExpiration((int)(message.getExpiresIn() / 1000)) - .withMessageTimer((int) message.getRevealDuration() / 1000) + .withViewOnce(message.isViewOnce()) .withProfileKey(profileKey.orNull()) .withQuote(quote.orNull()) .withSticker(sticker.orNull()) diff --git a/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java b/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java index a95f7b5225..1329e5330c 100644 --- a/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java +++ b/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java @@ -50,7 +50,7 @@ import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.imageeditor.model.EditorModel; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter; -import org.thoughtcrime.securesms.mediasend.MediaSendViewModel.RevealState; +import org.thoughtcrime.securesms.mediasend.MediaSendViewModel.ViewOnceState; import org.thoughtcrime.securesms.mms.GifSlide; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.ImageSlide; @@ -101,10 +101,10 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple { private static final String TAG = MediaSendActivity.class.getSimpleName(); - public static final String EXTRA_MEDIA = "media"; - public static final String EXTRA_MESSAGE = "message"; - public static final String EXTRA_TRANSPORT = "transport"; - public static final String EXTRA_REVEAL_DURATION = "reveal_duration"; + public static final String EXTRA_MEDIA = "media"; + public static final String EXTRA_MESSAGE = "message"; + public static final String EXTRA_TRANSPORT = "transport"; + public static final String EXTRA_VIEW_ONCE = "view_once"; private static final String KEY_ADDRESS = "address"; @@ -391,7 +391,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple Uri uri = BlobProvider.getInstance() .forData(data) .withMimeType(MediaUtil.IMAGE_JPEG) - .createForSingleSessionOnDisk(this, e -> Log.w(TAG, "Failed to write to disk.", e)); + .createForSingleSessionOnDisk(this); return new Media(uri, MediaUtil.IMAGE_JPEG, System.currentTimeMillis(), @@ -527,14 +527,14 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple if (state == null) return; hud.setVisibility(state.isHudVisible() ? View.VISIBLE : View.GONE); - composeContainer.setVisibility(state.isComposeVisible() ? View.VISIBLE : (state.getRevealState() == RevealState.GONE ? View.GONE : View.INVISIBLE)); + composeContainer.setVisibility(state.isComposeVisible() ? View.VISIBLE : (state.getViewOnceState() == ViewOnceState.GONE ? View.GONE : View.INVISIBLE)); captionText.setVisibility(state.isCaptionVisible() ? View.VISIBLE : View.GONE); int captionBackground; if (state.getRailState() == MediaSendViewModel.RailState.VIEWABLE) { captionBackground = R.color.core_grey_90; - } else if (state.getRevealState() == RevealState.ENABLED) { + } else if (state.getViewOnceState() == ViewOnceState.ENABLED) { captionBackground = 0; } else { captionBackground = R.color.transparent_black_70; @@ -572,7 +572,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple break; } - switch (state.getRevealState()) { + switch (state.getViewOnceState()) { case ENABLED: revealButton.setVisibility(View.VISIBLE); revealButton.setImageResource(R.drawable.ic_view_once_32); @@ -815,7 +815,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple Uri uri = BlobProvider.getInstance() .forData(outputStream.toByteArray()) .withMimeType(MediaUtil.IMAGE_JPEG) - .createForSingleSessionOnDisk(context, e -> Log.w(TAG, "Failed to write to disk.", e)); + .createForSingleSessionOnDisk(context); Media updated = new Media(uri, MediaUtil.IMAGE_JPEG, media.getDate(), bitmap.getWidth(), bitmap.getHeight(), outputStream.size(), media.getBucketId(), media.getCaption()); @@ -859,9 +859,9 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple Intent intent = new Intent(); intent.putParcelableArrayListExtra(EXTRA_MEDIA, mediaList); - intent.putExtra(EXTRA_MESSAGE, viewModel.getRevealDuration() == 0 ? message : ""); + intent.putExtra(EXTRA_MESSAGE, viewModel.isViewOnce() ? "" : message); intent.putExtra(EXTRA_TRANSPORT, transport); - intent.putExtra(EXTRA_REVEAL_DURATION, viewModel.getRevealDuration()); + intent.putExtra(EXTRA_VIEW_ONCE, viewModel.isViewOnce()); setResult(RESULT_OK, intent); } else { @@ -885,7 +885,7 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple System.currentTimeMillis(), -1, recipient.getExpireMessages() * 1000, - viewModel.getRevealDuration(), + viewModel.isViewOnce(), ThreadDatabase.DistributionTypes.DEFAULT, null, Collections.emptyList(), diff --git a/src/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.java b/src/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.java index 6d642c0c9b..fc62144069 100644 --- a/src/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.java +++ b/src/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.java @@ -61,12 +61,12 @@ class MediaSendViewModel extends ViewModel { private boolean isSms; private Optional lastCameraCapture; - private boolean hudVisible; - private boolean composeVisible; - private boolean captionVisible; - private ButtonState buttonState; - private RailState railState; - private RevealState revealState; + private boolean hudVisible; + private boolean composeVisible; + private boolean captionVisible; + private ButtonState buttonState; + private RailState railState; + private ViewOnceState viewOnceState; private @Nullable Recipient recipient; @@ -86,7 +86,7 @@ class MediaSendViewModel extends ViewModel { this.body = ""; this.buttonState = ButtonState.GONE; this.railState = RailState.GONE; - this.revealState = RevealState.GONE; + this.viewOnceState = ViewOnceState.GONE; this.page = Page.UNKNOWN; position.setValue(-1); @@ -174,7 +174,7 @@ class MediaSendViewModel extends ViewModel { captionVisible = false; buttonState = ButtonState.COUNT; railState = RailState.VIEWABLE; - revealState = RevealState.GONE; + viewOnceState = ViewOnceState.GONE; hudState.setValue(buildHudState()); } @@ -182,19 +182,19 @@ class MediaSendViewModel extends ViewModel { void onImageEditorStarted() { page = Page.EDITOR; hudVisible = true; - composeVisible = revealState != RevealState.ENABLED; + composeVisible = viewOnceState != ViewOnceState.ENABLED; captionVisible = getSelectedMediaOrDefault().size() > 1 || (getSelectedMediaOrDefault().size() > 0 && getSelectedMediaOrDefault().get(0).getCaption().isPresent()); buttonState = (recipient != null) ? ButtonState.SEND : ButtonState.CONTINUE; - if (revealState == RevealState.GONE && revealSupported()) { + if (viewOnceState == ViewOnceState.GONE && viewOnceSupported()) { // TODO[reveal] -// revealState = TextSecurePreferences.isRevealableMessageEnabled(application) ? RevealState.ENABLED : RevealState.DISABLED; - revealState = RevealState.GONE; - } else if (!revealSupported()) { - revealState = RevealState.GONE; +// viewOnceState = TextSecurePreferences.isRevealableMessageEnabled(application) ? ViewOnceState.ENABLED : ViewOnceState.DISABLED; + viewOnceState = ViewOnceState.GONE; + } else if (!viewOnceSupported()) { + viewOnceState = ViewOnceState.GONE; } - railState = !isSms && revealState != RevealState.ENABLED ? RailState.INTERACTIVE : RailState.GONE; + railState = !isSms && viewOnceState != ViewOnceState.ENABLED ? RailState.INTERACTIVE : RailState.GONE; hudState.setValue(buildHudState()); } @@ -203,10 +203,10 @@ class MediaSendViewModel extends ViewModel { // TODO: Don't need this? Page previous = page; - page = Page.CAMERA; - hudVisible = false; - revealState = RevealState.GONE; - buttonState = ButtonState.COUNT; + page = Page.CAMERA; + hudVisible = false; + viewOnceState = ViewOnceState.GONE; + buttonState = ButtonState.COUNT; List selected = getSelectedMediaOrDefault(); @@ -225,7 +225,7 @@ class MediaSendViewModel extends ViewModel { composeVisible = false; captionVisible = false; buttonState = ButtonState.COUNT; - revealState = RevealState.GONE; + viewOnceState = ViewOnceState.GONE; railState = getSelectedMediaOrDefault().isEmpty() ? RailState.GONE : RailState.VIEWABLE; lastCameraCapture = Optional.absent(); @@ -239,7 +239,7 @@ class MediaSendViewModel extends ViewModel { composeVisible = false; captionVisible = false; buttonState = ButtonState.COUNT; - revealState = RevealState.GONE; + viewOnceState = ViewOnceState.GONE; railState = getSelectedMediaOrDefault().isEmpty() ? RailState.GONE : RailState.VIEWABLE; lastCameraCapture = Optional.absent(); @@ -255,9 +255,9 @@ class MediaSendViewModel extends ViewModel { void onRevealButtonToggled() { hudVisible = true; - revealState = revealState == RevealState.ENABLED ? RevealState.DISABLED : RevealState.ENABLED; - composeVisible = revealState != RevealState.ENABLED; - railState = revealState == RevealState.ENABLED || isSms ? RailState.GONE : RailState.INTERACTIVE; + viewOnceState = viewOnceState == ViewOnceState.ENABLED ? ViewOnceState.DISABLED : ViewOnceState.ENABLED; + composeVisible = viewOnceState != ViewOnceState.ENABLED; + railState = viewOnceState == ViewOnceState.ENABLED || isSms ? RailState.GONE : RailState.INTERACTIVE; captionVisible = false; List uncaptioned = Stream.of(getSelectedMediaOrDefault()) @@ -266,7 +266,7 @@ class MediaSendViewModel extends ViewModel { selectedMedia.setValue(uncaptioned); - TextSecurePreferences.setIsRevealableMessageEnabled(application, revealState == RevealState.ENABLED); + TextSecurePreferences.setIsRevealableMessageEnabled(application, viewOnceState == ViewOnceState.ENABLED); hudState.setValue(buildHudState()); } @@ -274,14 +274,14 @@ class MediaSendViewModel extends ViewModel { void onKeyboardHidden(boolean isSms) { if (page != Page.EDITOR) return; - composeVisible = (revealState != RevealState.ENABLED); + composeVisible = (viewOnceState != ViewOnceState.ENABLED); buttonState = (recipient != null) ? ButtonState.SEND : ButtonState.CONTINUE; if (isSms) { railState = RailState.GONE; captionVisible = false; } else { - railState = revealState != RevealState.ENABLED ? RailState.INTERACTIVE : RailState.GONE; + railState = viewOnceState != ViewOnceState.ENABLED ? RailState.INTERACTIVE : RailState.GONE; if (getSelectedMediaOrDefault().size() > 1 || (getSelectedMediaOrDefault().size() > 0 && getSelectedMediaOrDefault().get(0).getCaption().isPresent())) { captionVisible = true; @@ -296,18 +296,18 @@ class MediaSendViewModel extends ViewModel { if (isSms) { railState = RailState.GONE; - composeVisible = (revealState == RevealState.GONE); + composeVisible = (viewOnceState == ViewOnceState.GONE); captionVisible = false; buttonState = (recipient != null) ? ButtonState.SEND : ButtonState.CONTINUE; } else { if (isCaptionFocused) { - railState = revealState != RevealState.ENABLED ? RailState.INTERACTIVE : RailState.GONE; + railState = viewOnceState != ViewOnceState.ENABLED ? RailState.INTERACTIVE : RailState.GONE; composeVisible = false; captionVisible = true; buttonState = ButtonState.GONE; } else if (isComposeFocused) { - railState = revealState != RevealState.ENABLED ? RailState.INTERACTIVE : RailState.GONE; - composeVisible = (revealState != RevealState.ENABLED); + railState = viewOnceState != ViewOnceState.ENABLED ? RailState.INTERACTIVE : RailState.GONE; + composeVisible = (viewOnceState != ViewOnceState.ENABLED); captionVisible = false; buttonState = (recipient != null) ? ButtonState.SEND : ButtonState.CONTINUE; } @@ -357,7 +357,7 @@ class MediaSendViewModel extends ViewModel { } if (getSelectedMediaOrDefault().size() == 1) { - revealState = revealSupported() ? RevealState.DISABLED : RevealState.GONE; + viewOnceState = viewOnceSupported() ? ViewOnceState.DISABLED : ViewOnceState.GONE; } hudState.setValue(buildHudState()); @@ -448,10 +448,9 @@ class MediaSendViewModel extends ViewModel { return maxSelection; } - long getRevealDuration() { + boolean isViewOnce() { // TODO[reveal] -// return revealState == RevealState.ENABLED ? RevealableUtil.DURATION : 0; - return 0; + return false; } private @NonNull List getSelectedMediaOrDefault() { @@ -473,13 +472,13 @@ class MediaSendViewModel extends ViewModel { private HudState buildHudState() { // TODO[reveal] - RevealState updatedRevealState = RevealState.GONE; - List selectedMedia = getSelectedMediaOrDefault(); - int selectionCount = selectedMedia.size(); - ButtonState updatedButtonState = buttonState == ButtonState.COUNT && selectionCount == 0 ? ButtonState.GONE : buttonState; - boolean updatedCaptionVisible = captionVisible && (selectedMedia.size() > 1 || (selectedMedia.size() > 0 && selectedMedia.get(0).getCaption().isPresent())); + ViewOnceState updatedViewOnceState = ViewOnceState.GONE; + List selectedMedia = getSelectedMediaOrDefault(); + int selectionCount = selectedMedia.size(); + ButtonState updatedButtonState = buttonState == ButtonState.COUNT && selectionCount == 0 ? ButtonState.GONE : buttonState; + boolean updatedCaptionVisible = captionVisible && (selectedMedia.size() > 1 || (selectedMedia.size() > 0 && selectedMedia.get(0).getCaption().isPresent())); - return new HudState(hudVisible, composeVisible, updatedCaptionVisible, selectionCount, updatedButtonState, railState, updatedRevealState); + return new HudState(hudVisible, composeVisible, updatedCaptionVisible, selectionCount, updatedButtonState, railState, updatedViewOnceState); } private void clearPersistedMedia() { @@ -489,7 +488,7 @@ class MediaSendViewModel extends ViewModel { .forEach(uri -> BlobProvider.getInstance().delete(application.getApplicationContext(), uri)); } - private boolean revealSupported() { + private boolean viewOnceSupported() { return !isSms && (recipient == null || !recipient.isLocalNumber()) && mediaSupportsRevealableMessage(getSelectedMediaOrDefault()); } @@ -520,7 +519,7 @@ class MediaSendViewModel extends ViewModel { INTERACTIVE, VIEWABLE, GONE } - enum RevealState { + enum ViewOnceState { ENABLED, DISABLED, GONE } @@ -532,7 +531,7 @@ class MediaSendViewModel extends ViewModel { private final int selectionCount; private final ButtonState buttonState; private final RailState railState; - private final RevealState revealState; + private final ViewOnceState viewOnceState; HudState(boolean hudVisible, boolean composeVisible, @@ -540,7 +539,7 @@ class MediaSendViewModel extends ViewModel { int selectionCount, @NonNull ButtonState buttonState, @NonNull RailState railState, - @NonNull RevealState revealState) + @NonNull ViewOnceState viewOnceState) { this.hudVisible = hudVisible; this.composeVisible = composeVisible; @@ -548,7 +547,7 @@ class MediaSendViewModel extends ViewModel { this.selectionCount = selectionCount; this.buttonState = buttonState; this.railState = railState; - this.revealState = revealState; + this.viewOnceState = viewOnceState; } public boolean isHudVisible() { @@ -575,9 +574,8 @@ class MediaSendViewModel extends ViewModel { return hudVisible ? railState : RailState.GONE; } - public @NonNull - RevealState getRevealState() { - return hudVisible ? revealState : RevealState.GONE; + public @NonNull ViewOnceState getViewOnceState() { + return hudVisible ? viewOnceState : ViewOnceState.GONE; } } diff --git a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java index 5c85f0c072..2a623ca71c 100644 --- a/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java +++ b/src/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java @@ -24,9 +24,9 @@ public class IncomingMediaMessage { private final int subscriptionId; private final long expiresIn; private final boolean expirationUpdate; - private final long revealDuration; private final QuoteModel quote; private final boolean unidentified; + private final boolean viewOnce; private final List attachments = new LinkedList<>(); private final List sharedContacts = new LinkedList<>(); @@ -40,7 +40,7 @@ public class IncomingMediaMessage { int subscriptionId, long expiresIn, boolean expirationUpdate, - long revealDuration, + boolean viewOnce, boolean unidentified) { this.from = from; @@ -51,7 +51,7 @@ public class IncomingMediaMessage { this.subscriptionId = subscriptionId; this.expiresIn = expiresIn; this.expirationUpdate = expirationUpdate; - this.revealDuration = revealDuration; + this.viewOnce = viewOnce; this.quote = null; this.unidentified = unidentified; @@ -63,7 +63,7 @@ public class IncomingMediaMessage { int subscriptionId, long expiresIn, boolean expirationUpdate, - long revealDuration, + boolean viewOnce, boolean unidentified, Optional body, Optional group, @@ -80,7 +80,7 @@ public class IncomingMediaMessage { this.subscriptionId = subscriptionId; this.expiresIn = expiresIn; this.expirationUpdate = expirationUpdate; - this.revealDuration = revealDuration; + this.viewOnce = viewOnce; this.quote = quote.orNull(); this.unidentified = unidentified; @@ -132,8 +132,8 @@ public class IncomingMediaMessage { return expiresIn; } - public long getRevealDuration() { - return revealDuration; + public boolean isViewOnce() { + return viewOnce; } public boolean isGroupMessage() { diff --git a/src/org/thoughtcrime/securesms/mms/OutgoingExpirationUpdateMessage.java b/src/org/thoughtcrime/securesms/mms/OutgoingExpirationUpdateMessage.java index bd521a7310..fd86024544 100644 --- a/src/org/thoughtcrime/securesms/mms/OutgoingExpirationUpdateMessage.java +++ b/src/org/thoughtcrime/securesms/mms/OutgoingExpirationUpdateMessage.java @@ -11,7 +11,7 @@ public class OutgoingExpirationUpdateMessage extends OutgoingSecureMediaMessage public OutgoingExpirationUpdateMessage(Recipient recipient, long sentTimeMillis, long expiresIn) { super(recipient, "", new LinkedList(), sentTimeMillis, - ThreadDatabase.DistributionTypes.CONVERSATION, expiresIn, 0, null, Collections.emptyList(), + ThreadDatabase.DistributionTypes.CONVERSATION, expiresIn, false, null, Collections.emptyList(), Collections.emptyList()); } diff --git a/src/org/thoughtcrime/securesms/mms/OutgoingGroupMediaMessage.java b/src/org/thoughtcrime/securesms/mms/OutgoingGroupMediaMessage.java index 36d567acfa..2fb734f87c 100644 --- a/src/org/thoughtcrime/securesms/mms/OutgoingGroupMediaMessage.java +++ b/src/org/thoughtcrime/securesms/mms/OutgoingGroupMediaMessage.java @@ -24,14 +24,14 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage { @NonNull List avatar, long sentTimeMillis, long expiresIn, - long revealDuration, + boolean viewOnce, @Nullable QuoteModel quote, @NonNull List contacts, @NonNull List previews) throws IOException { super(recipient, encodedGroupContext, avatar, sentTimeMillis, - ThreadDatabase.DistributionTypes.CONVERSATION, expiresIn, revealDuration, quote, contacts, previews); + ThreadDatabase.DistributionTypes.CONVERSATION, expiresIn, viewOnce, quote, contacts, previews); this.group = GroupContext.parseFrom(Base64.decode(encodedGroupContext)); } @@ -41,7 +41,7 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage { @Nullable final Attachment avatar, long sentTimeMillis, long expireIn, - long revealDuration, + boolean viewOnce, @Nullable QuoteModel quote, @NonNull List contacts, @NonNull List previews) @@ -49,7 +49,7 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage { super(recipient, Base64.encodeBytes(group.toByteArray()), new LinkedList() {{if (avatar != null) add(avatar);}}, System.currentTimeMillis(), - ThreadDatabase.DistributionTypes.CONVERSATION, expireIn, revealDuration, quote, contacts, previews); + ThreadDatabase.DistributionTypes.CONVERSATION, expireIn, viewOnce, quote, contacts, previews); this.group = group; } diff --git a/src/org/thoughtcrime/securesms/mms/OutgoingMediaMessage.java b/src/org/thoughtcrime/securesms/mms/OutgoingMediaMessage.java index cc36527bc4..d9d02b62f8 100644 --- a/src/org/thoughtcrime/securesms/mms/OutgoingMediaMessage.java +++ b/src/org/thoughtcrime/securesms/mms/OutgoingMediaMessage.java @@ -23,7 +23,7 @@ public class OutgoingMediaMessage { private final int distributionType; private final int subscriptionId; private final long expiresIn; - private final long revealDuration; + private final boolean viewOnce; private final QuoteModel outgoingQuote; private final List networkFailures = new LinkedList<>(); @@ -33,7 +33,7 @@ public class OutgoingMediaMessage { public OutgoingMediaMessage(Recipient recipient, String message, List attachments, long sentTimeMillis, - int subscriptionId, long expiresIn, long revealDuration, + int subscriptionId, long expiresIn, boolean viewOnce, int distributionType, @Nullable QuoteModel outgoingQuote, @NonNull List contacts, @@ -48,7 +48,7 @@ public class OutgoingMediaMessage { this.attachments = attachments; this.subscriptionId = subscriptionId; this.expiresIn = expiresIn; - this.revealDuration = revealDuration; + this.viewOnce = viewOnce; this.outgoingQuote = outgoingQuote; this.contacts.addAll(contacts); @@ -59,7 +59,7 @@ public class OutgoingMediaMessage { public OutgoingMediaMessage(Recipient recipient, SlideDeck slideDeck, String message, long sentTimeMillis, int subscriptionId, long expiresIn, - long revealDuration, int distributionType, + boolean viewOnce, int distributionType, @Nullable QuoteModel outgoingQuote, @NonNull List contacts, @NonNull List linkPreviews) @@ -68,7 +68,7 @@ public class OutgoingMediaMessage { buildMessage(slideDeck, message), slideDeck.asAttachments(), sentTimeMillis, subscriptionId, - expiresIn, revealDuration, distributionType, outgoingQuote, + expiresIn, viewOnce, distributionType, outgoingQuote, contacts, linkPreviews, new LinkedList<>(), new LinkedList<>()); } @@ -80,7 +80,7 @@ public class OutgoingMediaMessage { this.sentTimeMillis = that.sentTimeMillis; this.subscriptionId = that.subscriptionId; this.expiresIn = that.expiresIn; - this.revealDuration = that.revealDuration; + this.viewOnce = that.viewOnce; this.outgoingQuote = that.outgoingQuote; this.identityKeyMismatches.addAll(that.identityKeyMismatches); @@ -129,8 +129,8 @@ public class OutgoingMediaMessage { return expiresIn; } - public long getRevealDuration() { - return revealDuration; + public boolean isViewOnce() { + return viewOnce; } public @Nullable QuoteModel getOutgoingQuote() { diff --git a/src/org/thoughtcrime/securesms/mms/OutgoingSecureMediaMessage.java b/src/org/thoughtcrime/securesms/mms/OutgoingSecureMediaMessage.java index 839b26ce44..2aec2a3400 100644 --- a/src/org/thoughtcrime/securesms/mms/OutgoingSecureMediaMessage.java +++ b/src/org/thoughtcrime/securesms/mms/OutgoingSecureMediaMessage.java @@ -18,12 +18,12 @@ public class OutgoingSecureMediaMessage extends OutgoingMediaMessage { long sentTimeMillis, int distributionType, long expiresIn, - long revealDuration, + boolean viewOnce, @Nullable QuoteModel quote, @NonNull List contacts, @NonNull List previews) { - super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, revealDuration, distributionType, quote, contacts, previews, Collections.emptyList(), Collections.emptyList()); + super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, viewOnce, distributionType, quote, contacts, previews, Collections.emptyList(), Collections.emptyList()); } public OutgoingSecureMediaMessage(OutgoingMediaMessage base) { diff --git a/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java b/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java index 09c9ed8c69..ab568b80b8 100644 --- a/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java +++ b/src/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java @@ -76,7 +76,7 @@ public class AndroidAutoReplyReceiver extends BroadcastReceiver { if (recipient.isGroupRecipient()) { Log.w("AndroidAutoReplyReceiver", "GroupRecipient, Sending media message"); - OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0, 0, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, false, 0, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); replyThreadId = MessageSender.send(context, reply, threadId, false, null); } else { Log.w("AndroidAutoReplyReceiver", "Sending regular message "); diff --git a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java index 5879eebd2d..c8fef46bca 100644 --- a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java +++ b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java @@ -465,7 +465,7 @@ public class MessageNotifier { } else if (record.isMms() && ((MmsMessageRecord) record).getSlideDeck().getStickerSlide() != null) { body = SpanUtil.italic(context.getString(R.string.MessageNotifier_sticker)); slideDeck = ((MmsMessageRecord) record).getSlideDeck(); - } else if (record.isMms() && ((MmsMessageRecord) record).getRevealDuration() > 0) { + } else if (record.isMms() && ((MmsMessageRecord) record).isViewOnce()) { body = SpanUtil.italic(context.getString(R.string.MessageNotifier_disappearing_photo)); } else if (record.isMms() && TextUtils.isEmpty(body) && !((MmsMessageRecord) record).getSlideDeck().getSlides().isEmpty()) { body = SpanUtil.italic(context.getString(R.string.MessageNotifier_media_message)); diff --git a/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java b/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java index 7276d03b02..96a3322592 100644 --- a/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java +++ b/src/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java @@ -76,7 +76,7 @@ public class RemoteReplyReceiver extends BroadcastReceiver { switch (replyMethod) { case GroupMessage: { - OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, 0, 0, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient, responseText.toString(), new LinkedList<>(), System.currentTimeMillis(), subscriptionId, expiresIn, false, 0, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); threadId = MessageSender.send(context, reply, -1, false, null); break; } diff --git a/src/org/thoughtcrime/securesms/providers/BlobProvider.java b/src/org/thoughtcrime/securesms/providers/BlobProvider.java index 0cdb50da9a..41f76b28d7 100644 --- a/src/org/thoughtcrime/securesms/providers/BlobProvider.java +++ b/src/org/thoughtcrime/securesms/providers/BlobProvider.java @@ -25,6 +25,8 @@ import java.io.OutputStream; import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; /** * Allows for the creation and retrieval of blobs. @@ -173,7 +175,34 @@ public class BlobProvider { } @WorkerThread - private synchronized @NonNull Uri writeBlobSpecToDisk(@NonNull Context context, @NonNull BlobSpec blobSpec, @Nullable ErrorListener errorListener) throws IOException { + private synchronized @NonNull Uri writeBlobSpecToDisk(@NonNull Context context, @NonNull BlobSpec blobSpec) + throws IOException + { + CountDownLatch latch = new CountDownLatch(1); + AtomicReference exception = new AtomicReference<>(null); + Uri uri = writeBlobSpecToDiskAsync(context, blobSpec, latch::countDown, exception::set); + + try { + latch.await(); + } catch (InterruptedException e) { + throw new IOException(e); + } + + if (exception.get() != null) { + throw exception.get(); + } + + return uri; + } + + + @WorkerThread + private synchronized @NonNull Uri writeBlobSpecToDiskAsync(@NonNull Context context, + @NonNull BlobSpec blobSpec, + @Nullable SuccessListener successListener, + @Nullable ErrorListener errorListener) + throws IOException + { AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(); String directory = getDirectory(blobSpec.getStorageType()); File outputFile = new File(getOrCreateCacheDirectory(context, directory), buildFileName(blobSpec.id)); @@ -182,6 +211,10 @@ public class BlobProvider { SignalExecutors.UNBOUNDED.execute(() -> { try { Util.copy(blobSpec.getData(), outputStream); + + if (successListener != null) { + successListener.onSuccess(); + } } catch (IOException e) { if (errorListener != null) { errorListener.onError(e); @@ -258,8 +291,23 @@ public class BlobProvider { * period from one {@link Application#onCreate()} to the next. */ @WorkerThread - public Uri createForSingleSessionOnDisk(@NonNull Context context, @Nullable ErrorListener errorListener) throws IOException { - return writeBlobSpecToDisk(context, buildBlobSpec(StorageType.SINGLE_SESSION_DISK), errorListener); + public Uri createForSingleSessionOnDisk(@NonNull Context context) throws IOException { + return writeBlobSpecToDisk(context, buildBlobSpec(StorageType.SINGLE_SESSION_DISK)); + } + + /** + * Create a blob that will exist for a single app session. An app session is defined as the + * period from one {@link Application#onCreate()} to the next. The file will be created on disk + * synchronously, but the data will copied asynchronously. This is helpful when the copy is + * long-running, such as in the case of recording a voice note. + */ + @WorkerThread + public Uri createForSingleSessionOnDiskAsync(@NonNull Context context, + @Nullable SuccessListener successListener, + @Nullable ErrorListener errorListener) + throws IOException + { + return writeBlobSpecToDiskAsync(context, buildBlobSpec(StorageType.SINGLE_SESSION_DISK), successListener, errorListener); } /** @@ -267,8 +315,25 @@ public class BlobProvider { * eventually call {@link BlobProvider#delete(Context, Uri)} when the blob is no longer in use. */ @WorkerThread - public Uri createForMultipleSessionsOnDisk(@NonNull Context context, @Nullable ErrorListener errorListener) throws IOException { - return writeBlobSpecToDisk(context, buildBlobSpec(StorageType.MULTI_SESSION_DISK), errorListener); + public Uri createForMultipleSessionsOnDisk(@NonNull Context context) throws IOException { + return writeBlobSpecToDisk(context, buildBlobSpec(StorageType.MULTI_SESSION_DISK)); + } + + /** + * Create a blob that will exist for multiple app sessions. The file will be created on disk + * synchronously, but the data will copied asynchronously. This is helpful when the copy is + * long-running, such as in the case of recording a voice note. + * + * It is the caller's responsibility to eventually call {@link BlobProvider#delete(Context, Uri)} + * when the blob is no longer in use. + */ + @WorkerThread + public Uri createForMultipleSessionsOnDiskAsync(@NonNull Context context, + @Nullable SuccessListener successListener, + @Nullable ErrorListener errorListener) + throws IOException + { + return writeBlobSpecToDiskAsync(context, buildBlobSpec(StorageType.MULTI_SESSION_DISK), successListener, errorListener); } } @@ -311,6 +376,11 @@ public class BlobProvider { } } + public interface SuccessListener { + @WorkerThread + void onSuccess(); + } + public interface ErrorListener { @WorkerThread void onError(IOException e); diff --git a/src/org/thoughtcrime/securesms/revealable/RevealExpirationInfo.java b/src/org/thoughtcrime/securesms/revealable/RevealExpirationInfo.java deleted file mode 100644 index eb5e07f097..0000000000 --- a/src/org/thoughtcrime/securesms/revealable/RevealExpirationInfo.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.thoughtcrime.securesms.revealable; - -public class RevealExpirationInfo { - - private final long messageId; - private final long receiveTime; - private final long revealStartTime; - private final long revealDuration; - - public RevealExpirationInfo(long messageId, long receiveTime, long revealStartTime, long revealDuration) { - this.messageId = messageId; - this.receiveTime = receiveTime; - this.revealStartTime = revealStartTime; - this.revealDuration = revealDuration; - } - - public long getMessageId() { - return messageId; - } - - public long getReceiveTime() { - return receiveTime; - } - - public long getRevealStartTime() { - return revealStartTime; - } - - public long getRevealDuration() { - return revealDuration; - } -} diff --git a/src/org/thoughtcrime/securesms/revealable/RevealableMessageActivity.java b/src/org/thoughtcrime/securesms/revealable/RevealableMessageActivity.java deleted file mode 100644 index e05b5701a7..0000000000 --- a/src/org/thoughtcrime/securesms/revealable/RevealableMessageActivity.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.thoughtcrime.securesms.revealable; - -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.widget.ImageView; - -import androidx.annotation.NonNull; -import androidx.lifecycle.Observer; -import androidx.lifecycle.ViewModelProviders; - -import com.pnikosis.materialishprogress.ProgressWheel; - -import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity; -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.logging.Log; -import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; -import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.util.Util; - -public class RevealableMessageActivity extends PassphraseRequiredActionBarActivity { - - private static final String TAG = Log.tag(RevealableMessageActivity.class); - - private static final String KEY_MESSAGE_ID = "message_id"; - - private ImageView image; - private View closeButton; - private RevealableMessageViewModel viewModel; - - public static Intent getIntent(@NonNull Context context, long messageId) { - Intent intent = new Intent(context, RevealableMessageActivity.class); - intent.putExtra(KEY_MESSAGE_ID, messageId); - return intent; - } - - @Override - protected void onCreate(Bundle savedInstanceState, boolean ready) { - super.onCreate(savedInstanceState, ready); - setContentView(R.layout.revealable_message_activity); - - this.image = findViewById(R.id.reveal_image); - this.closeButton = findViewById(R.id.reveal_close_button); - - image.setOnClickListener(v -> finish()); - closeButton.setOnClickListener(v -> finish()); - - initViewModel(getIntent().getLongExtra(KEY_MESSAGE_ID, -1)); - } - - private void initViewModel(long messageId) { - RevealableMessageRepository repository = new RevealableMessageRepository(this); - viewModel = ViewModelProviders.of(this, new RevealableMessageViewModel.Factory(getApplication(), messageId, repository)) - .get(RevealableMessageViewModel.class); - - viewModel.getMessage().observe(this, (message) -> { - if (message == null) return; - - if (message.isPresent()) { - //noinspection ConstantConditions - GlideApp.with(this) - .load(new DecryptableUri(message.get().getSlideDeck().getThumbnailSlide().getUri())) - .into(image); - } else { - image.setImageDrawable(null); - finish(); - } - }); - } -} diff --git a/src/org/thoughtcrime/securesms/revealable/RevealableUtil.java b/src/org/thoughtcrime/securesms/revealable/RevealableUtil.java deleted file mode 100644 index 6ea1955c47..0000000000 --- a/src/org/thoughtcrime/securesms/revealable/RevealableUtil.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.thoughtcrime.securesms.revealable; - -import androidx.annotation.Nullable; - -import org.thoughtcrime.securesms.attachments.Attachment; -import org.thoughtcrime.securesms.database.AttachmentDatabase; -import org.thoughtcrime.securesms.database.model.MessageRecord; -import org.thoughtcrime.securesms.database.model.MmsMessageRecord; - -import java.util.concurrent.TimeUnit; - -public class RevealableUtil { - - public static final long MAX_LIFESPAN = TimeUnit.DAYS.toMillis(30); - public static final long DURATION = TimeUnit.SECONDS.toMillis(5); - - public static boolean isViewable(@Nullable MmsMessageRecord message) { - if (message.getRevealDuration() == 0) { - return true; - } else if (message.getSlideDeck().getThumbnailSlide() == null) { - return false; - } else if (message.getSlideDeck().getThumbnailSlide().getUri() == null) { - return false; - } else if (message.isOutgoing() && message.getSlideDeck().getThumbnailSlide().getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_STARTED) { - return true; - } else if (message.getSlideDeck().getThumbnailSlide().getTransferState() != AttachmentDatabase.TRANSFER_PROGRESS_DONE) { - return false; - } else if (isRevealExpired(message)) { - return false; - } else { - return true; - } - } - - public static boolean isRevealExpired(@Nullable MmsMessageRecord message) { - if (message == null) { - return false; - } else if (message.getRevealDuration() == 0) { - return false; - } else if (message.getDateReceived() + MAX_LIFESPAN < System.currentTimeMillis()) { - return true; - } else if (message.getRevealStartTime() == 0) { - return false; - } else if (message.getRevealStartTime() + message.getRevealDuration() < System.currentTimeMillis()) { - return true; - } else { - return false; - } - } -} diff --git a/src/org/thoughtcrime/securesms/revealable/ViewOnceExpirationInfo.java b/src/org/thoughtcrime/securesms/revealable/ViewOnceExpirationInfo.java new file mode 100644 index 0000000000..26722b0c30 --- /dev/null +++ b/src/org/thoughtcrime/securesms/revealable/ViewOnceExpirationInfo.java @@ -0,0 +1,20 @@ +package org.thoughtcrime.securesms.revealable; + +public class ViewOnceExpirationInfo { + + private final long messageId; + private final long receiveTime; + + public ViewOnceExpirationInfo(long messageId, long receiveTime) { + this.messageId = messageId; + this.receiveTime = receiveTime; + } + + public long getMessageId() { + return messageId; + } + + public long getReceiveTime() { + return receiveTime; + } +} diff --git a/src/org/thoughtcrime/securesms/revealable/ViewOnceMessageActivity.java b/src/org/thoughtcrime/securesms/revealable/ViewOnceMessageActivity.java new file mode 100644 index 0000000000..1f7cf5d8a5 --- /dev/null +++ b/src/org/thoughtcrime/securesms/revealable/ViewOnceMessageActivity.java @@ -0,0 +1,81 @@ +package org.thoughtcrime.securesms.revealable; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; +import android.widget.ImageView; + +import androidx.annotation.NonNull; +import androidx.lifecycle.ViewModelProviders; + +import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity; +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; +import org.thoughtcrime.securesms.mms.GlideApp; +import org.thoughtcrime.securesms.providers.BlobProvider; + +public class ViewOnceMessageActivity extends PassphraseRequiredActionBarActivity { + + private static final String TAG = Log.tag(ViewOnceMessageActivity.class); + + private static final String KEY_MESSAGE_ID = "message_id"; + private static final String KEY_URI = "uri"; + + private ImageView image; + private View closeButton; + private ViewOnceMessageViewModel viewModel; + private Uri uri; + + public static Intent getIntent(@NonNull Context context, long messageId, @NonNull Uri uri) { + Intent intent = new Intent(context, ViewOnceMessageActivity.class); + intent.putExtra(KEY_MESSAGE_ID, messageId); + intent.putExtra(KEY_URI, uri); + return intent; + } + + @Override + protected void onCreate(Bundle savedInstanceState, boolean ready) { + super.onCreate(savedInstanceState, ready); + setContentView(R.layout.view_once_message_activity); + + this.image = findViewById(R.id.view_once_image); + this.closeButton = findViewById(R.id.view_once_close_button); + this.uri = getIntent().getParcelableExtra(KEY_URI); + + image.setOnClickListener(v -> finish()); + closeButton.setOnClickListener(v -> finish()); + + + initViewModel(getIntent().getLongExtra(KEY_MESSAGE_ID, -1), uri); + } + + @Override + protected void onStop() { + super.onStop(); + BlobProvider.getInstance().delete(this, uri); + finish(); + } + + private void initViewModel(long messageId, @NonNull Uri uri) { + ViewOnceMessageRepository repository = new ViewOnceMessageRepository(this); + + viewModel = ViewModelProviders.of(this, new ViewOnceMessageViewModel.Factory(getApplication(), messageId, repository)) + .get(ViewOnceMessageViewModel.class); + + viewModel.getMessage().observe(this, (message) -> { + if (message == null) return; + + if (message.isPresent()) { + GlideApp.with(this) + .load(new DecryptableUri(uri)) + .into(image); + } else { + image.setImageDrawable(null); + finish(); + } + }); + } +} diff --git a/src/org/thoughtcrime/securesms/revealable/RevealableMessageManager.java b/src/org/thoughtcrime/securesms/revealable/ViewOnceMessageManager.java similarity index 59% rename from src/org/thoughtcrime/securesms/revealable/RevealableMessageManager.java rename to src/org/thoughtcrime/securesms/revealable/ViewOnceMessageManager.java index cda737ebb3..7e9550e31e 100644 --- a/src/org/thoughtcrime/securesms/revealable/RevealableMessageManager.java +++ b/src/org/thoughtcrime/securesms/revealable/ViewOnceMessageManager.java @@ -20,14 +20,14 @@ import org.thoughtcrime.securesms.service.TimedEventManager; /** * Manages clearing removable message content after they're opened. */ -public class RevealableMessageManager extends TimedEventManager { +public class ViewOnceMessageManager extends TimedEventManager { - private static final String TAG = Log.tag(RevealableMessageManager.class); + private static final String TAG = Log.tag(ViewOnceMessageManager.class); private final MmsDatabase mmsDatabase; private final AttachmentDatabase attachmentDatabase; - public RevealableMessageManager(@NonNull Application application) { + public ViewOnceMessageManager(@NonNull Application application) { super(application, "RevealableMessageManager"); this.mmsDatabase = DatabaseFactory.getMmsDatabase(application); @@ -38,8 +38,8 @@ public class RevealableMessageManager extends TimedEventManager> message; private final ContentObserver observer; - private RevealableMessageViewModel(@NonNull Application application, - long messageId, - @NonNull RevealableMessageRepository repository) + private ViewOnceMessageViewModel(@NonNull Application application, + long messageId, + @NonNull ViewOnceMessageRepository repository) { this.application = application; this.repository = repository; @@ -74,13 +74,13 @@ class RevealableMessageViewModel extends ViewModel { static class Factory extends ViewModelProvider.NewInstanceFactory { - private final Application application; - private final long messageId; - private final RevealableMessageRepository repository; + private final Application application; + private final long messageId; + private final ViewOnceMessageRepository repository; Factory(@NonNull Application application, long messageId, - @NonNull RevealableMessageRepository repository) + @NonNull ViewOnceMessageRepository repository) { this.application = application; this.messageId = messageId; @@ -90,7 +90,7 @@ class RevealableMessageViewModel extends ViewModel { @Override public @NonNull T create(@NonNull Class modelClass) { //noinspection ConstantConditions - return modelClass.cast(new RevealableMessageViewModel(application, messageId, repository)); + return modelClass.cast(new ViewOnceMessageViewModel(application, messageId, repository)); } } } diff --git a/src/org/thoughtcrime/securesms/revealable/ViewOnceUtil.java b/src/org/thoughtcrime/securesms/revealable/ViewOnceUtil.java new file mode 100644 index 0000000000..6db290780c --- /dev/null +++ b/src/org/thoughtcrime/securesms/revealable/ViewOnceUtil.java @@ -0,0 +1,69 @@ +package org.thoughtcrime.securesms.revealable; + +import androidx.annotation.NonNull; + +import org.thoughtcrime.securesms.database.AttachmentDatabase; +import org.thoughtcrime.securesms.database.model.MmsMessageRecord; + +import java.util.concurrent.TimeUnit; + +public class ViewOnceUtil { + + public static final long MAX_LIFESPAN = TimeUnit.DAYS.toMillis(30); + + public static boolean isViewable(@NonNull MmsMessageRecord message) { + if (!message.isViewOnce()) { + return true; + } + + if (message.isOutgoing()) { + return false; + } + + if (message.getSlideDeck().getThumbnailSlide() == null) { + return false; + } + + if (message.getSlideDeck().getThumbnailSlide().getUri() == null) { + return false; + } + + if (message.getSlideDeck().getThumbnailSlide().getTransferState() != AttachmentDatabase.TRANSFER_PROGRESS_DONE) { + return false; + } + + if (isViewed(message)) { + return false; + } + + return true; + } + + public static boolean isViewed(@NonNull MmsMessageRecord message) { + if (!message.isViewOnce()) { + return false; + } + + if (message.getDateReceived() + MAX_LIFESPAN <= System.currentTimeMillis()) { + return true; + } + + if (message.getSlideDeck().getThumbnailSlide() != null && message.getSlideDeck().getThumbnailSlide().getTransferState() != AttachmentDatabase.TRANSFER_PROGRESS_DONE) { + return false; + } + + if (message.getSlideDeck().getThumbnailSlide() == null) { + return true; + } + + if (message.getSlideDeck().getThumbnailSlide().getUri() == null) { + return true; + } + + if (message.isOutgoing()) { + return true; + } + + return false; + } +} diff --git a/src/org/thoughtcrime/securesms/sms/IncomingJoinedMessage.java b/src/org/thoughtcrime/securesms/sms/IncomingJoinedMessage.java index 658caddb1a..8ce4bec8b4 100644 --- a/src/org/thoughtcrime/securesms/sms/IncomingJoinedMessage.java +++ b/src/org/thoughtcrime/securesms/sms/IncomingJoinedMessage.java @@ -7,7 +7,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceGroup; public class IncomingJoinedMessage extends IncomingTextMessage { public IncomingJoinedMessage(Address sender) { - super(sender, 1, System.currentTimeMillis(), null, Optional.absent(), 0, 0, false); + super(sender, 1, System.currentTimeMillis(), null, Optional.absent(), 0, false); } @Override diff --git a/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java b/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java index 4ebddcd739..3d2e97d789 100644 --- a/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java +++ b/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java @@ -42,7 +42,6 @@ public class IncomingTextMessage implements Parcelable { private final boolean push; private final int subscriptionId; private final long expiresInMillis; - private final long revealDuration; private final boolean unidentified; public IncomingTextMessage(@NonNull Context context, @NonNull SmsMessage message, int subscriptionId) { @@ -56,7 +55,6 @@ public class IncomingTextMessage implements Parcelable { this.sentTimestampMillis = message.getTimestampMillis(); this.subscriptionId = subscriptionId; this.expiresInMillis = 0; - this.revealDuration = 0; this.groupId = null; this.push = false; this.unidentified = false; @@ -64,7 +62,7 @@ public class IncomingTextMessage implements Parcelable { public IncomingTextMessage(Address sender, int senderDeviceId, long sentTimestampMillis, String encodedBody, Optional group, - long expiresInMillis, long revealDuration, boolean unidentified) + long expiresInMillis, boolean unidentified) { this.message = encodedBody; this.sender = sender; @@ -77,7 +75,6 @@ public class IncomingTextMessage implements Parcelable { this.push = true; this.subscriptionId = -1; this.expiresInMillis = expiresInMillis; - this.revealDuration = revealDuration; this.unidentified = unidentified; if (group.isPresent()) { @@ -100,7 +97,6 @@ public class IncomingTextMessage implements Parcelable { this.push = (in.readInt() == 1); this.subscriptionId = in.readInt(); this.expiresInMillis = in.readLong(); - this.revealDuration = in.readLong(); this.unidentified = in.readInt() == 1; } @@ -117,7 +113,6 @@ public class IncomingTextMessage implements Parcelable { this.push = base.isPush(); this.subscriptionId = base.getSubscriptionId(); this.expiresInMillis = base.getExpiresIn(); - this.revealDuration = base.getRevealDuration(); this.unidentified = base.isUnidentified(); } @@ -140,7 +135,6 @@ public class IncomingTextMessage implements Parcelable { this.push = fragments.get(0).isPush(); this.subscriptionId = fragments.get(0).getSubscriptionId(); this.expiresInMillis = fragments.get(0).getExpiresIn(); - this.revealDuration = fragments.get(0).getRevealDuration(); this.unidentified = fragments.get(0).isUnidentified(); } @@ -158,7 +152,6 @@ public class IncomingTextMessage implements Parcelable { this.push = true; this.subscriptionId = -1; this.expiresInMillis = 0; - this.revealDuration = 0; this.unidentified = false; } @@ -170,10 +163,6 @@ public class IncomingTextMessage implements Parcelable { return expiresInMillis; } - public long getRevealDuration() { - return revealDuration; - } - public long getSentTimestampMillis() { return sentTimestampMillis; } @@ -281,7 +270,6 @@ public class IncomingTextMessage implements Parcelable { out.writeInt(push ? 1 : 0); out.writeInt(subscriptionId); out.writeLong(expiresInMillis); - out.writeLong(revealDuration); out.writeInt(unidentified ? 1 : 0); } } diff --git a/src/org/thoughtcrime/securesms/util/GroupUtil.java b/src/org/thoughtcrime/securesms/util/GroupUtil.java index 71f8baf138..d64de8220d 100644 --- a/src/org/thoughtcrime/securesms/util/GroupUtil.java +++ b/src/org/thoughtcrime/securesms/util/GroupUtil.java @@ -73,7 +73,7 @@ public class GroupUtil { .setType(GroupContext.Type.QUIT) .build(); - return Optional.of(new OutgoingGroupMediaMessage(groupRecipient, groupContext, null, System.currentTimeMillis(), 0, 0, null, Collections.emptyList(), Collections.emptyList())); + return Optional.of(new OutgoingGroupMediaMessage(groupRecipient, groupContext, null, System.currentTimeMillis(), 0, false, null, Collections.emptyList(), Collections.emptyList())); } diff --git a/src/org/thoughtcrime/securesms/util/IdentityUtil.java b/src/org/thoughtcrime/securesms/util/IdentityUtil.java index 6d00ed29ee..d6d66ce9e5 100644 --- a/src/org/thoughtcrime/securesms/util/IdentityUtil.java +++ b/src/org/thoughtcrime/securesms/util/IdentityUtil.java @@ -78,7 +78,7 @@ public class IdentityUtil { SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId()); if (remote) { - IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0, 0, false); + IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0, false); if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming); else incoming = new IncomingIdentityDefaultMessage(incoming); @@ -98,7 +98,7 @@ public class IdentityUtil { } if (remote) { - IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0, 0, false); + IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0, false); if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming); else incoming = new IncomingIdentityDefaultMessage(incoming); @@ -128,14 +128,14 @@ public class IdentityUtil { while ((groupRecord = reader.getNext()) != null) { if (groupRecord.getMembers().contains(recipient.getAddress()) && groupRecord.isActive()) { SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId()); - IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0, 0, false); + IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0, false); IncomingIdentityUpdateMessage groupUpdate = new IncomingIdentityUpdateMessage(incoming); smsDatabase.insertMessageInbox(groupUpdate); } } - IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0, 0, false); + IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0, false); IncomingIdentityUpdateMessage individualUpdate = new IncomingIdentityUpdateMessage(incoming); Optional insertResult = smsDatabase.insertMessageInbox(individualUpdate); diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java index c840ccbcb8..b761fb19c1 100644 --- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -183,7 +183,7 @@ public class TextSecurePreferences { private static final String MEDIA_KEYBOARD_MODE = "pref_media_keyboard_mode"; - private static final String REVEALABLE_MESSAGE_DEFAULT = "pref_revealable_message_default"; + private static final String VIEW_ONCE_DEFAULT = "pref_revealable_message_default"; private static final String SEEN_CAMERA_FIRST_TOOLTIP = "pref_seen_camera_first_tooltip"; @@ -1103,11 +1103,11 @@ public class TextSecurePreferences { } public static void setIsRevealableMessageEnabled(Context context, boolean value) { - setBooleanPreference(context, REVEALABLE_MESSAGE_DEFAULT, value); + setBooleanPreference(context, VIEW_ONCE_DEFAULT, value); } public static boolean isRevealableMessageEnabled(Context context) { - return getBooleanPreference(context, REVEALABLE_MESSAGE_DEFAULT, false); + return getBooleanPreference(context, VIEW_ONCE_DEFAULT, false); } public static void setHasSeenCameraFirstTooltip(Context context, boolean value) {