mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-21 18:26:57 +00:00
Add support for sending and syncing viewed receipts behind a feature flag.
This commit is contained in:
@@ -99,7 +99,7 @@ public class ConversationItemFooter extends LinearLayout {
|
||||
presentTimer(messageRecord);
|
||||
presentInsecureIndicator(messageRecord);
|
||||
presentDeliveryStatus(messageRecord);
|
||||
hideAudioDurationViews();
|
||||
presentAudioDuration(messageRecord);
|
||||
}
|
||||
|
||||
public void setAudioDuration(long totalDurationMillis, long currentPostionMillis) {
|
||||
@@ -259,6 +259,12 @@ public class ConversationItemFooter extends LinearLayout {
|
||||
moveAudioViewsForIncoming();
|
||||
}
|
||||
showAudioDurationViews();
|
||||
|
||||
if (messageRecord.getViewedReceiptCount() > 0) {
|
||||
revealDot.setProgress(1f);
|
||||
} else {
|
||||
revealDot.setProgress(0f);
|
||||
}
|
||||
} else {
|
||||
hideAudioDurationViews();
|
||||
}
|
||||
@@ -295,7 +301,7 @@ public class ConversationItemFooter extends LinearLayout {
|
||||
|
||||
private void showAudioDurationViews() {
|
||||
audioSpace.setVisibility(View.VISIBLE);
|
||||
audioDuration.setVisibility(View.VISIBLE);
|
||||
audioDuration.setVisibility(View.GONE);
|
||||
|
||||
if (FeatureFlags.viewedReceipts()) {
|
||||
revealDot.setVisibility(View.VISIBLE);
|
||||
|
||||
@@ -33,7 +33,17 @@ import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.ui.PlayerNotificationManager;
|
||||
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MessageDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceViewedUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.SendViewedReceiptJob;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.video.exo.AttachmentMediaSourceFactory;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -48,10 +58,10 @@ public class VoiceNotePlaybackService extends MediaBrowserServiceCompat {
|
||||
private static final String EMPTY_ROOT_ID = "empty-root-id";
|
||||
private static final int LOAD_MORE_THRESHOLD = 2;
|
||||
|
||||
private static final long SUPPORTED_ACTIONS = PlaybackStateCompat.ACTION_PLAY |
|
||||
PlaybackStateCompat.ACTION_PAUSE |
|
||||
PlaybackStateCompat.ACTION_SEEK_TO |
|
||||
PlaybackStateCompat.ACTION_STOP |
|
||||
private static final long SUPPORTED_ACTIONS = PlaybackStateCompat.ACTION_PLAY |
|
||||
PlaybackStateCompat.ACTION_PAUSE |
|
||||
PlaybackStateCompat.ACTION_SEEK_TO |
|
||||
PlaybackStateCompat.ACTION_STOP |
|
||||
PlaybackStateCompat.ACTION_PLAY_PAUSE;
|
||||
|
||||
private MediaSessionCompat mediaSession;
|
||||
@@ -152,6 +162,7 @@ public class VoiceNotePlaybackService extends MediaBrowserServiceCompat {
|
||||
becomingNoisyReceiver.unregister();
|
||||
voiceNoteProximityManager.onPlayerEnded();
|
||||
} else {
|
||||
sendViewedReceiptForCurrentWindowIndex();
|
||||
becomingNoisyReceiver.register();
|
||||
voiceNoteProximityManager.onPlayerReady();
|
||||
}
|
||||
@@ -172,11 +183,12 @@ public class VoiceNotePlaybackService extends MediaBrowserServiceCompat {
|
||||
|
||||
if (reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION) {
|
||||
MediaDescriptionCompat mediaDescriptionCompat = queueDataAdapter.getMediaDescription(currentWindowIndex);
|
||||
sendViewedReceiptForCurrentWindowIndex();
|
||||
Log.d(TAG, "onPositionDiscontinuity: current window uri: " + mediaDescriptionCompat.getMediaUri());
|
||||
}
|
||||
|
||||
boolean isWithinThreshold = currentWindowIndex < LOAD_MORE_THRESHOLD ||
|
||||
currentWindowIndex + LOAD_MORE_THRESHOLD >= queueDataAdapter.size();
|
||||
boolean isWithinThreshold = currentWindowIndex < LOAD_MORE_THRESHOLD ||
|
||||
currentWindowIndex + LOAD_MORE_THRESHOLD >= queueDataAdapter.size();
|
||||
|
||||
if (isWithinThreshold && currentWindowIndex % 2 == 0) {
|
||||
voiceNotePlaybackPreparer.loadMoreVoiceNotes();
|
||||
@@ -190,6 +202,36 @@ public class VoiceNotePlaybackService extends MediaBrowserServiceCompat {
|
||||
}
|
||||
}
|
||||
|
||||
private void sendViewedReceiptForCurrentWindowIndex() {
|
||||
if (player.getPlaybackState() == Player.STATE_READY &&
|
||||
player.getPlayWhenReady() &&
|
||||
player.getCurrentWindowIndex() != C.INDEX_UNSET &&
|
||||
FeatureFlags.sendViewedReceipts()) {
|
||||
|
||||
final MediaDescriptionCompat descriptionCompat = queueDataAdapter.getMediaDescription(player.getCurrentWindowIndex());
|
||||
|
||||
if (!descriptionCompat.getMediaUri().getScheme().equals("content")) {
|
||||
return;
|
||||
}
|
||||
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
Bundle extras = descriptionCompat.getExtras();
|
||||
long messageId = extras.getLong(VoiceNoteMediaDescriptionCompatFactory.EXTRA_MESSAGE_ID);
|
||||
RecipientId recipientId = RecipientId.from(extras.getString(VoiceNoteMediaDescriptionCompatFactory.EXTRA_THREAD_RECIPIENT_ID));
|
||||
MessageDatabase messageDatabase = DatabaseFactory.getMmsDatabase(this);
|
||||
|
||||
MessageDatabase.MarkedMessageInfo markedMessageInfo = messageDatabase.setIncomingMessageViewed(messageId);
|
||||
|
||||
if (markedMessageInfo != null) {
|
||||
ApplicationDependencies.getJobManager().add(new SendViewedReceiptJob(markedMessageInfo.getThreadId(),
|
||||
recipientId,
|
||||
markedMessageInfo.getSyncMessageId().getTimetamp()));
|
||||
MultiDeviceViewedUpdateJob.enqueue(Collections.singletonList(markedMessageInfo.getSyncMessageId()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class VoiceNoteNotificationManagerListener implements PlayerNotificationManager.NotificationListener {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -117,6 +117,7 @@ import org.thoughtcrime.securesms.revealable.ViewOnceUtil;
|
||||
import org.thoughtcrime.securesms.stickers.StickerUrl;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.InterceptableLongClickCopyLinkSpan;
|
||||
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
|
||||
import org.thoughtcrime.securesms.util.SearchUtil;
|
||||
@@ -1164,7 +1165,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
|
||||
boolean differentTimestamps = next.isPresent() && !DateUtils.isSameExtendedRelativeTimestamp(context, locale, next.get().getTimestamp(), current.getTimestamp());
|
||||
|
||||
if (current.getExpiresIn() > 0 || !current.isSecure() || current.isPending() || current.isPendingInsecureSmsFallback() ||
|
||||
if (forceFooter(messageRecord) || current.getExpiresIn() > 0 || !current.isSecure() || current.isPending() || current.isPendingInsecureSmsFallback() ||
|
||||
current.isFailed() || differentTimestamps || isEndOfMessageCluster(current, next, isGroupThread))
|
||||
{
|
||||
ConversationItemFooter activeFooter = getActiveFooter(current);
|
||||
@@ -1189,6 +1190,10 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
}
|
||||
}
|
||||
|
||||
private boolean forceFooter(@NonNull MessageRecord messageRecord) {
|
||||
return FeatureFlags.viewedReceipts() && hasAudio(messageRecord) && messageRecord.getViewedReceiptCount() == 0;
|
||||
}
|
||||
|
||||
private ConversationItemFooter getActiveFooter(@NonNull MessageRecord messageRecord) {
|
||||
if (hasNoBubble(messageRecord)) {
|
||||
return stickerFooter;
|
||||
|
||||
@@ -128,6 +128,7 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns
|
||||
public abstract Pair<Long, Long> updateBundleMessageBody(long messageId, String body);
|
||||
public abstract @NonNull List<MarkedMessageInfo> getViewedIncomingMessages(long threadId);
|
||||
public abstract @Nullable MarkedMessageInfo setIncomingMessageViewed(long messageId);
|
||||
public abstract @NonNull List<MarkedMessageInfo> setIncomingMessagesViewed(@NonNull List<Long> messageIds);
|
||||
|
||||
public abstract void addFailures(long messageId, List<NetworkFailure> failure);
|
||||
public abstract void removeFailure(long messageId, NetworkFailure failure);
|
||||
|
||||
@@ -412,39 +412,56 @@ public class MmsDatabase extends MessageDatabase {
|
||||
|
||||
@Override
|
||||
public @Nullable MarkedMessageInfo setIncomingMessageViewed(long messageId) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
String[] columns = new String[]{ID, RECIPIENT_ID, DATE_SENT, MESSAGE_BOX, THREAD_ID};
|
||||
String where = ID_WHERE + " AND " + VIEWED_RECEIPT_COUNT + " = 0";
|
||||
String[] args = SqlUtil.buildArgs(messageId);
|
||||
List<MarkedMessageInfo> results = setIncomingMessagesViewed(Collections.singletonList(messageId));
|
||||
|
||||
if (results.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
return results.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull List<MarkedMessageInfo> setIncomingMessagesViewed(@NonNull List<Long> messageIds) {
|
||||
if (messageIds.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
String[] columns = new String[]{ID, RECIPIENT_ID, DATE_SENT, MESSAGE_BOX, THREAD_ID};
|
||||
String where = ID + " IN (" + Util.join(messageIds, ",") + ") AND " + VIEWED_RECEIPT_COUNT + " = 0";
|
||||
List<MarkedMessageInfo> results = new LinkedList<>();
|
||||
|
||||
database.beginTransaction();
|
||||
try (Cursor cursor = database.query(TABLE_NAME, columns, where, args, null, null, null)) {
|
||||
if (cursor == null || !cursor.moveToFirst()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
long type = CursorUtil.requireLong(cursor, MESSAGE_BOX);
|
||||
if (Types.isSecureType(type) && Types.isInboxType(type)) {
|
||||
long threadId = CursorUtil.requireLong(cursor, THREAD_ID);
|
||||
RecipientId recipientId = RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID));
|
||||
long dateSent = CursorUtil.requireLong(cursor, DATE_SENT);
|
||||
SyncMessageId syncMessageId = new SyncMessageId(recipientId, dateSent);
|
||||
|
||||
MarkedMessageInfo result = new MarkedMessageInfo(threadId, syncMessageId, null);
|
||||
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(VIEWED_RECEIPT_COUNT, 1);
|
||||
|
||||
database.update(TABLE_NAME, contentValues, where, args);
|
||||
database.setTransactionSuccessful();
|
||||
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
try (Cursor cursor = database.query(TABLE_NAME, columns, where, null, null, null, null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
long type = CursorUtil.requireLong(cursor, MESSAGE_BOX);
|
||||
if (Types.isSecureType(type) && Types.isInboxType(type)) {
|
||||
long threadId = CursorUtil.requireLong(cursor, THREAD_ID);
|
||||
RecipientId recipientId = RecipientId.from(CursorUtil.requireLong(cursor, RECIPIENT_ID));
|
||||
long dateSent = CursorUtil.requireLong(cursor, DATE_SENT);
|
||||
SyncMessageId syncMessageId = new SyncMessageId(recipientId, dateSent);
|
||||
|
||||
results.add(new MarkedMessageInfo(threadId, syncMessageId, null));
|
||||
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(VIEWED_RECEIPT_COUNT, 1);
|
||||
|
||||
database.update(TABLE_NAME, contentValues, ID_WHERE, SqlUtil.buildArgs(CursorUtil.requireLong(cursor, ID)));
|
||||
}
|
||||
}
|
||||
database.setTransactionSuccessful();
|
||||
} finally {
|
||||
database.endTransaction();
|
||||
}
|
||||
|
||||
Set<Long> threadsUpdated = Stream.of(results)
|
||||
.map(MarkedMessageInfo::getThreadId)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
notifyConversationListeners(threadsUpdated);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -635,6 +635,11 @@ public class SmsDatabase extends MessageDatabase {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull List<MarkedMessageInfo> setIncomingMessagesViewed(@NonNull List<Long> messageIds) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private Pair<Long, Long> updateMessageBodyAndType(long messageId, String body, long maskOff, long maskOn) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
db.execSQL("UPDATE " + TABLE_NAME + " SET " + BODY + " = ?, " +
|
||||
|
||||
@@ -104,6 +104,7 @@ public final class JobManagerFactories {
|
||||
put(MultiDeviceStorageSyncRequestJob.KEY, new MultiDeviceStorageSyncRequestJob.Factory());
|
||||
put(MultiDeviceVerifiedUpdateJob.KEY, new MultiDeviceVerifiedUpdateJob.Factory());
|
||||
put(MultiDeviceViewOnceOpenJob.KEY, new MultiDeviceViewOnceOpenJob.Factory());
|
||||
put(MultiDeviceViewedUpdateJob.KEY, new MultiDeviceViewedUpdateJob.Factory());
|
||||
put(ProfileKeySendJob.KEY, new ProfileKeySendJob.Factory());
|
||||
put(PushDecryptMessageJob.KEY, new PushDecryptMessageJob.Factory());
|
||||
put(PushDecryptDrainedJob.KEY, new PushDecryptDrainedJob.Factory());
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.MessageDatabase.SyncMessageId;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.ViewedMessage;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class MultiDeviceViewedUpdateJob extends BaseJob {
|
||||
|
||||
public static final String KEY = "MultiDeviceViewedUpdateJob";
|
||||
|
||||
private static final String TAG = Log.tag(MultiDeviceViewedUpdateJob.class);
|
||||
|
||||
private static final String KEY_MESSAGE_IDS = "message_ids";
|
||||
|
||||
private List<SerializableSyncMessageId> messageIds;
|
||||
|
||||
private MultiDeviceViewedUpdateJob(List<SyncMessageId> messageIds) {
|
||||
this(new Parameters.Builder()
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.build(),
|
||||
SendReadReceiptJob.ensureSize(messageIds, SendReadReceiptJob.MAX_TIMESTAMPS));
|
||||
}
|
||||
|
||||
private MultiDeviceViewedUpdateJob(@NonNull Parameters parameters, @NonNull List<SyncMessageId> messageIds) {
|
||||
super(parameters);
|
||||
|
||||
this.messageIds = new LinkedList<>();
|
||||
|
||||
for (SyncMessageId messageId : messageIds) {
|
||||
this.messageIds.add(new SerializableSyncMessageId(messageId.getRecipientId().serialize(), messageId.getTimetamp()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues all the necessary jobs for read receipts, ensuring that they're all within the
|
||||
* maximum size.
|
||||
*/
|
||||
public static void enqueue(@NonNull List<SyncMessageId> messageIds) {
|
||||
JobManager jobManager = ApplicationDependencies.getJobManager();
|
||||
List<List<SyncMessageId>> messageIdChunks = Util.chunk(messageIds, SendReadReceiptJob.MAX_TIMESTAMPS);
|
||||
|
||||
if (messageIdChunks.size() > 1) {
|
||||
Log.w(TAG, "Large receipt count! Had to break into multiple chunks. Total count: " + messageIds.size());
|
||||
}
|
||||
|
||||
for (List<SyncMessageId> chunk : messageIdChunks) {
|
||||
jobManager.add(new MultiDeviceViewedUpdateJob(chunk));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Data serialize() {
|
||||
String[] ids = new String[messageIds.size()];
|
||||
|
||||
for (int i = 0; i < ids.length; i++) {
|
||||
try {
|
||||
ids[i] = JsonUtils.toJson(messageIds.get(i));
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
return new Data.Builder().putStringArray(KEY_MESSAGE_IDS, ids).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRun() throws IOException, UntrustedIdentityException {
|
||||
if (!TextSecurePreferences.isMultiDevice(context)) {
|
||||
Log.i(TAG, "Not multi device...");
|
||||
return;
|
||||
}
|
||||
|
||||
List<ViewedMessage> viewedMessages = new LinkedList<>();
|
||||
|
||||
for (SerializableSyncMessageId messageId : messageIds) {
|
||||
Recipient recipient = Recipient.resolved(RecipientId.from(messageId.recipientId));
|
||||
if (!recipient.isGroup()) {
|
||||
viewedMessages.add(new ViewedMessage(RecipientUtil.toSignalServiceAddress(context, recipient), messageId.timestamp));
|
||||
}
|
||||
}
|
||||
|
||||
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
|
||||
messageSender.sendMessage(SignalServiceSyncMessage.forViewed(viewedMessages), UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onShouldRetry(@NonNull Exception exception) {
|
||||
return exception instanceof PushNetworkException;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
|
||||
}
|
||||
|
||||
private static class SerializableSyncMessageId implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@JsonProperty
|
||||
private final String recipientId;
|
||||
|
||||
@JsonProperty
|
||||
private final long timestamp;
|
||||
|
||||
private SerializableSyncMessageId(@JsonProperty("recipientId") String recipientId, @JsonProperty("timestamp") long timestamp) {
|
||||
this.recipientId = recipientId;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Factory implements Job.Factory<MultiDeviceViewedUpdateJob> {
|
||||
@Override
|
||||
public @NonNull MultiDeviceViewedUpdateJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
List<SyncMessageId> ids = Stream.of(data.getStringArray(KEY_MESSAGE_IDS))
|
||||
.map(id -> {
|
||||
try {
|
||||
return JsonUtils.fromJson(id, SerializableSyncMessageId.class);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
})
|
||||
.map(id -> new SyncMessageId(RecipientId.from(id.recipientId), id.timestamp))
|
||||
.toList();
|
||||
|
||||
return new MultiDeviceViewedUpdateJob(parameters, ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ public class SendViewedReceiptJob extends BaseJob {
|
||||
.build(),
|
||||
threadId,
|
||||
recipientId,
|
||||
syncTimestamps,
|
||||
SendReadReceiptJob.ensureSize(syncTimestamps, SendReadReceiptJob.MAX_TIMESTAMPS),
|
||||
System.currentTimeMillis());
|
||||
}
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.Hex;
|
||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.MessageRecordUtil;
|
||||
import org.thoughtcrime.securesms.util.RemoteDeleteUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
@@ -133,6 +134,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSy
|
||||
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.multidevice.ViewedMessage;
|
||||
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
||||
import org.whispersystems.signalservice.api.payments.Money;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
@@ -147,6 +149,7 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -268,6 +271,7 @@ public final class MessageContentProcessor {
|
||||
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.getViewed().isPresent()) handleSynchronizeViewedMessage(syncMessage.getViewed().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());
|
||||
@@ -1019,6 +1023,24 @@ public final class MessageContentProcessor {
|
||||
messageNotifier.updateNotification(context);
|
||||
}
|
||||
|
||||
private void handleSynchronizeViewedMessage(@NonNull List<ViewedMessage> viewedMessages, long envelopeTimestamp) {
|
||||
List<Long> toMarkViewed = Stream.of(viewedMessages)
|
||||
.map(message -> {
|
||||
RecipientId author = Recipient.externalPush(context, message.getSender()).getId();
|
||||
return DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(message.getTimestamp(), author);
|
||||
})
|
||||
.filter(message -> message != null && message.isMms())
|
||||
.map(MessageRecord::getId)
|
||||
.toList();
|
||||
|
||||
DatabaseFactory.getMmsDatabase(context).setIncomingMessagesViewed(toMarkViewed);
|
||||
|
||||
MessageNotifier messageNotifier = ApplicationDependencies.getMessageNotifier();
|
||||
messageNotifier.setLastDesktopActivityTimestamp(envelopeTimestamp);
|
||||
messageNotifier.cancelDelayedNotifications();
|
||||
messageNotifier.updateNotification(context);
|
||||
}
|
||||
|
||||
private void handleSynchronizeViewOnceOpenMessage(@NonNull ViewOnceOpenMessage openMessage, long envelopeTimestamp) {
|
||||
log(String.valueOf(envelopeTimestamp), "Handling a view-once open for message: " + openMessage.getTimestamp());
|
||||
|
||||
|
||||
@@ -11,9 +11,13 @@ import org.thoughtcrime.securesms.database.MessageDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceViewedUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.SendViewedReceiptJob;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
class ViewOnceMessageRepository {
|
||||
|
||||
private static final String TAG = Log.tag(ViewOnceMessageRepository.class);
|
||||
@@ -28,12 +32,17 @@ class ViewOnceMessageRepository {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
try (MmsDatabase.Reader reader = MmsDatabase.readerFor(mmsDatabase.getMessageCursor(messageId))) {
|
||||
MmsMessageRecord record = (MmsMessageRecord) reader.getNext();
|
||||
MessageDatabase.MarkedMessageInfo info = mmsDatabase.setIncomingMessageViewed(record.getId());
|
||||
if (info != null) {
|
||||
ApplicationDependencies.getJobManager().add(new SendViewedReceiptJob(record.getThreadId(),
|
||||
info.getSyncMessageId().getRecipientId(),
|
||||
info.getSyncMessageId().getTimetamp()));
|
||||
|
||||
if (FeatureFlags.sendViewedReceipts()) {
|
||||
MessageDatabase.MarkedMessageInfo info = mmsDatabase.setIncomingMessageViewed(record.getId());
|
||||
if (info != null) {
|
||||
ApplicationDependencies.getJobManager().add(new SendViewedReceiptJob(record.getThreadId(),
|
||||
info.getSyncMessageId().getRecipientId(),
|
||||
info.getSyncMessageId().getTimetamp()));
|
||||
MultiDeviceViewedUpdateJob.enqueue(Collections.singletonList(info.getSyncMessageId()));
|
||||
}
|
||||
}
|
||||
|
||||
callback.onComplete(Optional.fromNullable(record));
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user