diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java index 39e817037b..762d88b81c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java @@ -366,6 +366,8 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { * Send a MediaSaved notification to the recipient */ private void sendMediaSavedNotificationIfNeeded() { + // we don't send media saved notification for groups + if (recipient.isGroupRecipient()) return; DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis())); MessageSender.send(message, recipient.getAddress()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java index 39a67d762d..8bc53250a4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java @@ -368,6 +368,8 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im * Send a MediaSaved notification to the recipient */ private void sendMediaSavedNotificationIfNeeded() { + // we don't send media saved notification for groups + if (conversationRecipient.isGroupRecipient()) return; DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis())); MessageSender.send(message, conversationRecipient.getAddress()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index b833ad6c31..d69574c4d1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -764,6 +764,8 @@ public class ConversationFragment extends Fragment * Send a MediaSaved notification to the recipient */ private void sendMediaSavedNotificationIfNeeded() { + // we don't send media saved notification for groups + if (recipient.isGroupRecipient()) return; DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis())); MessageSender.send(message, recipient.getAddress()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java index 6b76c0572a..0bdb8ba3cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java @@ -15,6 +15,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage; import org.thoughtcrime.securesms.BindableConversationItem; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt; @@ -106,6 +107,8 @@ public class ConversationUpdateItem extends LinearLayout else if (messageRecord.isCallLog()) setCallRecord(messageRecord); else if (messageRecord.isJoined()) setJoinedRecord(messageRecord); else if (messageRecord.isExpirationTimerUpdate()) setTimerRecord(messageRecord); + else if (messageRecord.isScreenshotExtraction()) setDataExtractionRecord(messageRecord, DataExtractionNotificationInfoMessage.Kind.SCREENSHOT); + else if (messageRecord.isMediaSavedExtraction()) setDataExtractionRecord(messageRecord, DataExtractionNotificationInfoMessage.Kind.MEDIASAVED); else if (messageRecord.isEndSession()) setEndSessionRecord(messageRecord); else if (messageRecord.isIdentityUpdate()) setIdentityRecord(messageRecord); else if (messageRecord.isIdentityVerified() || @@ -149,6 +152,22 @@ public class ConversationUpdateItem extends LinearLayout date.setVisibility(GONE); } + private void setDataExtractionRecord(final MessageRecord messageRecord, DataExtractionNotificationInfoMessage.Kind kind) { + @ColorInt int color = GeneralUtilitiesKt.getColorWithID(getResources(), R.color.text, getContext().getTheme()); + if (kind == DataExtractionNotificationInfoMessage.Kind.SCREENSHOT) { + icon.setImageResource(R.drawable.quick_camera_dark); + } else if (kind == DataExtractionNotificationInfoMessage.Kind.MEDIASAVED) { + icon.setImageResource(R.drawable.ic_file_download_white_36dp); + } + icon.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + + body.setText(messageRecord.getDisplayBody(getContext())); + + title.setVisibility(VISIBLE); + body.setVisibility(VISIBLE); + date.setVisibility(GONE); + } + private void setIdentityRecord(final MessageRecord messageRecord) { icon.setImageResource(R.drawable.ic_security_white_24dp); icon.setColorFilter(new PorterDuffColorFilter(Color.parseColor("#757575"), PorterDuff.Mode.MULTIPLY)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index 62de9499a1..12e5ba2d95 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -709,6 +709,14 @@ public class MmsDatabase extends MessagingDatabase { type |= Types.EXPIRATION_TIMER_UPDATE_BIT; } + if (retrieved.isScreenshotDataExtraction()) { + type |= Types.SCREENSHOT_EXTRACTION_BIT; + } + + if (retrieved.isMediaSavedDataExtraction()) { + type |= Types.MEDIA_SAVED_EXTRACTION_BIT; + } + return insertMessageInbox(retrieved, "", threadId, type, serverTimestamp); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java index 36b86023e6..aefd1af808 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -71,6 +71,10 @@ public interface MmsSmsColumns { protected static final long GROUP_QUIT_BIT = 0x20000; protected static final long EXPIRATION_TIMER_UPDATE_BIT = 0x40000; + // Data Extraction Information + protected static final long MEDIA_SAVED_EXTRACTION_BIT = 0x01000; + protected static final long SCREENSHOT_EXTRACTION_BIT = 0x02000; + // Encrypted Storage Information XXX public static final long ENCRYPTION_MASK = 0xFF000000; // public static final long ENCRYPTION_SYMMETRIC_BIT = 0x80000000; Deprecated @@ -197,6 +201,14 @@ public interface MmsSmsColumns { return (type & EXPIRATION_TIMER_UPDATE_BIT) != 0; } + public static boolean isMediaSavedExtraction(long type) { + return (type & MEDIA_SAVED_EXTRACTION_BIT) != 0; + } + + public static boolean isScreenshotExtraction(long type) { + return (type & SCREENSHOT_EXTRACTION_BIT) != 0; + } + public static boolean isIncomingCall(long type) { return type == INCOMING_CALL_TYPE; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 57aad5b10d..7722840a49 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -581,22 +581,18 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } // Data Extraction Notification - override fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, groupID: String?, sentTimestamp: Long) { + override fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long) { val database = DatabaseFactory.getMmsDatabase(context) val address = fromSerialized(senderPublicKey) val recipient = Recipient.from(context, address, false) if (recipient.isBlocked) return - var groupInfo = Optional.absent() - if (groupID != null) { - groupInfo = Optional.of(SignalServiceGroup(groupID.toByteArray(), SignalServiceGroup.GroupType.SIGNAL)) - } val mediaMessage = IncomingMediaMessage(address, sentTimestamp, -1, 0, false, false, Optional.absent(), - groupInfo, + Optional.absent(), Optional.absent(), Optional.absent(), Optional.absent(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java index ab40f3381c..28eae16f16 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java @@ -125,6 +125,20 @@ public abstract class DisplayRecord { return SmsDatabase.Types.isExpirationTimerUpdate(type); } + // Data extraction + + public boolean isMediaSavedExtraction() { + return MmsSmsColumns.Types.isMediaSavedExtraction(type); + } + + public boolean isScreenshotExtraction() { + return MmsSmsColumns.Types.isScreenshotExtraction(type); + } + + public boolean isDataExtraction() { + return isMediaSavedExtraction() || isScreenshotExtraction(); + } + public boolean isCallLog() { return SmsDatabase.Types.isCallLog(type); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index b41e4ae78e..64229e95b5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -98,6 +98,9 @@ public abstract class MessageRecord extends DisplayRecord { return new SpannableString(context.getString(R.string.MessageRecord_left_group)); } else if (isGroupQuit()) { return new SpannableString(context.getString(R.string.ConversationItem_group_action_left, getIndividualRecipient().toShortString())); + } else if (isDataExtraction()) { + if (isMediaSavedExtraction()) return new SpannableString(context.getString(R.string.MessageRecord_media_saved_by_s, getIndividualRecipient().toShortString())); + if (isScreenshotExtraction()) return new SpannableString(context.getString(R.string.MessageRecord_s_took_a_screenshot, getIndividualRecipient().toShortString())); } else if (isIncomingCall()) { return new SpannableString(context.getString(R.string.MessageRecord_s_called_you, getIndividualRecipient().toShortString())); } else if (isOutgoingCall()) { @@ -175,7 +178,7 @@ public abstract class MessageRecord extends DisplayRecord { } public boolean isUpdate() { - return isGroupAction() || isJoined() || isExpirationTimerUpdate() || isCallLog() || + return isGroupAction() || isJoined() || isExpirationTimerUpdate() || isCallLog() || isDataExtraction() || isEndSession() || isIdentityUpdate() || isIdentityVerified() || isIdentityDefault() || isLokiSessionRestoreSent() || isLokiSessionRestoreDone(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java index e2a286c854..42c8a20ede 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -103,12 +103,16 @@ public class ThreadRecord extends DisplayRecord { } else if (SmsDatabase.Types.isJoinedType(type)) { return emphasisAdded(context.getString(R.string.ThreadRecord_s_is_on_signal, getRecipient().toShortString())); } else if (SmsDatabase.Types.isExpirationTimerUpdate(type)) { - int seconds = (int)(getExpiresIn() / 1000); + int seconds = (int) (getExpiresIn() / 1000); if (seconds <= 0) { return emphasisAdded(context.getString(R.string.ThreadRecord_disappearing_messages_disabled)); } String time = ExpirationUtil.getExpirationDisplayValue(context, seconds); return emphasisAdded(context.getString(R.string.ThreadRecord_disappearing_message_time_updated_to_s, time)); + } else if (MmsSmsColumns.Types.isMediaSavedExtraction(type)) { + return emphasisAdded(context.getString(R.string.ThreadRecord_media_saved_by_s, getRecipient().toShortString())); + } else if (MmsSmsColumns.Types.isScreenshotExtraction(type)) { + return emphasisAdded(context.getString(R.string.ThreadRecord_s_took_a_screenshot, getRecipient().toShortString())); } else if (SmsDatabase.Types.isIdentityUpdate(type)) { if (getRecipient().isGroupRecipient()) return emphasisAdded(context.getString(R.string.ThreadRecord_safety_number_changed)); else return emphasisAdded(context.getString(R.string.ThreadRecord_your_safety_number_with_s_has_changed, getRecipient().toShortString())); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 166f8c42d1..3b0693645a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -505,9 +505,7 @@ %1$s disabled disappearing messages. You set the disappearing message timer to %1$s %1$s set the disappearing message timer to %2$s - You took a screenshot. %1$s took a screenshot. - Media saved by you. Media saved by %1$s. Your safety number with %s has changed. You marked your safety number with %s verified @@ -707,6 +705,8 @@ %s is on Session! Disappearing messages disabled Disappearing message time set to %s + %s took a screenshot. + Media saved by %s. Safety number changed Your safety number with %s has changed. You marked verified diff --git a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt index 3327952304..b390637ca2 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt @@ -161,5 +161,5 @@ interface StorageProtocol { fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List, groupPublicKey: String?, openGroupID: String?, attachments: List): Long? // Data Extraction Notification - fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, groupID: String?, sentTimestamp: Long) + fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java index ad110964a0..53e48a5897 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java @@ -121,6 +121,20 @@ public class IncomingMediaMessage { return groupId != null; } + public boolean isScreenshotDataExtraction() { + if (dataExtractionNotification == null) return false; + else { + return dataExtractionNotification.getKind() == DataExtractionNotificationInfoMessage.Kind.SCREENSHOT; + } + } + + public boolean isMediaSavedDataExtraction() { + if (dataExtractionNotification == null) return false; + else { + return dataExtractionNotification.getKind() == DataExtractionNotificationInfoMessage.Kind.MEDIASAVED; + } + } + public QuoteModel getQuote() { return quote; } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt index 2c0e2269c9..190a55195f 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt @@ -96,6 +96,9 @@ private fun MessageReceiver.handleExpirationTimerUpdate(message: ExpirationTimer // Data Extraction Notification handling private fun MessageReceiver.handleDataExtractionNotification(message: DataExtractionNotification) { + // we don't handle data extraction messages for groups (they shouldn't be sent, but in case we filter them here too) + if (message.groupPublicKey != null) return + val storage = MessagingConfiguration.shared.storage val senderPublicKey = message.sender!! val notification: DataExtractionNotificationInfoMessage = when(message.kind) { @@ -103,7 +106,7 @@ private fun MessageReceiver.handleDataExtractionNotification(message: DataExtrac is DataExtractionNotification.Kind.MediaSaved -> DataExtractionNotificationInfoMessage(DataExtractionNotificationInfoMessage.Kind.MEDIASAVED) else -> return } - storage.insertDataExtractionNotificationMessage(senderPublicKey, notification, message.groupPublicKey, message.sentTimestamp!!) + storage.insertDataExtractionNotificationMessage(senderPublicKey, notification, message.sentTimestamp!!) } // Configuration message handling @@ -170,7 +173,8 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS } } // Get or create thread - val threadID = storage.getOrCreateThreadIdFor(message.syncTarget ?: message.sender!!, message.groupPublicKey, openGroupID) + val threadID = storage.getOrCreateThreadIdFor(message.syncTarget + ?: message.sender!!, message.groupPublicKey, openGroupID) // Parse quote if needed var quoteModel: QuoteModel? = null if (message.quote != null && proto.dataMessage.hasQuote()) { @@ -259,7 +263,7 @@ private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPubli storage.updateMembers(groupID, members.map { Address.fromSerialized(it) }) } else { storage.createGroup(groupID, name, LinkedList(members.map { Address.fromSerialized(it) }), - null, null, LinkedList(admins.map { Address.fromSerialized(it) }), formationTimestamp) + null, null, LinkedList(admins.map { Address.fromSerialized(it) }), formationTimestamp) // Notify the user storage.insertIncomingInfoMessage(context, sender, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins, sentTimestamp) }