Add new VIEWED item in RecieptMessage enumeration.

Also includes necessary Database changes for supporting this as well as View-Once receipt support.
fork-5.53.8
Alex Hart 2020-11-20 09:16:37 -04:00
rodzic 7bb1262571
commit ce44e3949c
26 zmienionych plików z 432 dodań i 33 usunięć

Wyświetl plik

@ -34,6 +34,7 @@ public class GroupReceiptDatabase extends Database {
public static final int STATUS_UNDELIVERED = 0;
public static final int STATUS_DELIVERED = 1;
public static final int STATUS_READ = 2;
public static final int STATUS_VIEWED = 3;
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
MMS_ID + " INTEGER, " + RECIPIENT_ID + " INTEGER, " + STATUS + " INTEGER, " + TIMESTAMP + " INTEGER, " + UNIDENTIFIED + " INTEGER DEFAULT 0);";

Wyświetl plik

@ -117,12 +117,14 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns
public abstract void markDownloadState(long messageId, long state);
public abstract void markIncomingNotificationReceived(long threadId);
public abstract boolean incrementReceiptCount(SyncMessageId messageId, long timestamp, boolean deliveryReceipt);
public abstract boolean incrementReceiptCount(SyncMessageId messageId, long timestamp, @NonNull ReceiptType receiptType);
public abstract List<Pair<Long, Long>> setTimestampRead(SyncMessageId messageId, long proposedExpireStarted);
public abstract List<MarkedMessageInfo> setEntireThreadRead(long threadId);
public abstract List<MarkedMessageInfo> setMessagesReadSince(long threadId, long timestamp);
public abstract List<MarkedMessageInfo> setAllMessagesRead();
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 void addFailures(long messageId, List<NetworkFailure> failure);
public abstract void removeFailure(long messageId, NetworkFailure failure);
@ -555,6 +557,28 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns
return -1;
}
protected enum ReceiptType {
READ(READ_RECEIPT_COUNT, GroupReceiptDatabase.STATUS_READ),
DELIVERY(DELIVERY_RECEIPT_COUNT, GroupReceiptDatabase.STATUS_DELIVERED),
VIEWED(VIEWED_RECEIPT_COUNT, GroupReceiptDatabase.STATUS_VIEWED);
private final String columnName;
private final int groupStatus;
ReceiptType(String columnName, int groupStatus) {
this.columnName = columnName;
this.groupStatus = groupStatus;
}
public String getColumnName() {
return columnName;
}
public int getGroupStatus() {
return groupStatus;
}
}
public static class SyncMessageId {
private final RecipientId recipientId;

Wyświetl plik

@ -185,7 +185,8 @@ public class MmsDatabase extends MessageDatabase {
REACTIONS_LAST_SEEN + " INTEGER DEFAULT -1, " +
REMOTE_DELETED + " INTEGER DEFAULT 0, " +
MENTIONS_SELF + " INTEGER DEFAULT 0, " +
NOTIFIED_TIMESTAMP + " INTEGER DEFAULT 0);";
NOTIFIED_TIMESTAMP + " INTEGER DEFAULT 0, " +
VIEWED_RECEIPT_COUNT + " INTEGER DEFAULT 0);";
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS mms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
@ -210,7 +211,7 @@ public class MmsDatabase extends MessageDatabase {
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, QUOTE_MENTIONS,
SHARED_CONTACTS, LINK_PREVIEWS, UNIDENTIFIED, VIEW_ONCE, REACTIONS, REACTIONS_UNREAD, REACTIONS_LAST_SEEN,
REMOTE_DELETED, MENTIONS_SELF, NOTIFIED_TIMESTAMP,
REMOTE_DELETED, MENTIONS_SELF, NOTIFIED_TIMESTAMP, VIEWED_RECEIPT_COUNT,
"json_group_array(json_object(" +
"'" + AttachmentDatabase.ROW_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + ", " +
"'" + AttachmentDatabase.UNIQUE_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UNIQUE_ID + ", " +
@ -383,6 +384,69 @@ public class MmsDatabase extends MessageDatabase {
throw new UnsupportedOperationException();
}
@Override
public @NonNull List<MarkedMessageInfo> getViewedIncomingMessages(long threadId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
String[] columns = new String[]{ID, RECIPIENT_ID, DATE_SENT, MESSAGE_BOX, THREAD_ID};
String where = THREAD_ID + " = ? AND " + VIEWED_RECEIPT_COUNT + " > 0 AND " + MESSAGE_BOX + " & " + Types.BASE_INBOX_TYPE + " = " + Types.BASE_INBOX_TYPE;
String[] args = SqlUtil.buildArgs(threadId);
try (Cursor cursor = db.query(getTableName(), columns, where, args, null, null, null, null)) {
if (cursor == null) {
return Collections.emptyList();
}
List<MarkedMessageInfo> results = new ArrayList<>(cursor.getCount());
while (cursor.moveToNext()) {
RecipientId recipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndex(RECIPIENT_ID)));
long dateSent = cursor.getLong(cursor.getColumnIndex(DATE_SENT));
SyncMessageId syncMessageId = new SyncMessageId(recipientId, dateSent);
results.add(new MarkedMessageInfo(threadId, syncMessageId, null));
}
return results;
}
}
@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);
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 = cursor.getLong(cursor.getColumnIndex(THREAD_ID));
RecipientId recipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndex(RECIPIENT_ID)));
long dateSent = cursor.getLong(cursor.getColumnIndex(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;
}
} finally {
database.endTransaction();
}
}
@Override
public @NonNull Pair<Long, Long> insertReceivedCall(@NonNull RecipientId address, boolean isVideoOffer) {
throw new UnsupportedOperationException();
@ -540,23 +604,23 @@ public class MmsDatabase extends MessageDatabase {
}
@Override
public boolean incrementReceiptCount(SyncMessageId messageId, long timestamp, boolean deliveryReceipt) {
public boolean incrementReceiptCount(SyncMessageId messageId, long timestamp, @NonNull ReceiptType receiptType) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
boolean found = false;
try (Cursor cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID, MESSAGE_BOX, RECIPIENT_ID, DELIVERY_RECEIPT_COUNT, READ_RECEIPT_COUNT},
try (Cursor cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID, MESSAGE_BOX, RECIPIENT_ID, receiptType.getColumnName()},
DATE_SENT + " = ?", new String[] {String.valueOf(messageId.getTimetamp())},
null, null, null, null)) {
while (cursor.moveToNext()) {
if (Types.isOutgoingMessageType(cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX)))) {
RecipientId theirRecipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT_ID)));
RecipientId ourRecipientId = messageId.getRecipientId();
String columnName = deliveryReceipt ? DELIVERY_RECEIPT_COUNT : READ_RECEIPT_COUNT;
String columnName = receiptType.getColumnName();
if (ourRecipientId.equals(theirRecipientId) || Recipient.resolved(theirRecipientId).isGroup()) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
int status = deliveryReceipt ? GroupReceiptDatabase.STATUS_DELIVERED : GroupReceiptDatabase.STATUS_READ;
int status = receiptType.getGroupStatus();
boolean isFirstIncrement = cursor.getLong(cursor.getColumnIndexOrThrow(columnName)) == 0;
found = true;
@ -577,7 +641,7 @@ public class MmsDatabase extends MessageDatabase {
}
}
if (!found && deliveryReceipt) {
if (!found && receiptType == ReceiptType.DELIVERY) {
earlyDeliveryReceiptCache.increment(messageId.getTimetamp(), messageId.getRecipientId());
return true;
}
@ -1808,6 +1872,7 @@ public class MmsDatabase extends MessageDatabase {
Collections.emptyList(),
false,
false,
0,
0);
}
}
@ -1859,6 +1924,7 @@ public class MmsDatabase extends MessageDatabase {
int deliveryReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.DELIVERY_RECEIPT_COUNT));
int readReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.READ_RECEIPT_COUNT));
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.SUBSCRIPTION_ID));
int viewedReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsColumns.VIEWED_RECEIPT_COUNT));
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
readReceiptCount = 0;
@ -1880,7 +1946,7 @@ public class MmsDatabase extends MessageDatabase {
addressDeviceId, dateSent, dateReceived, deliveryReceiptCount, threadId,
contentLocationBytes, messageSize, expiry, status,
transactionIdBytes, mailbox, subscriptionId, slideDeck,
readReceiptCount);
readReceiptCount, viewedReceiptCount);
}
private MediaMmsMessageRecord getMediaMmsMessageRecord(Cursor cursor) {
@ -1907,9 +1973,11 @@ public class MmsDatabase extends MessageDatabase {
List<ReactionRecord> reactions = parseReactions(cursor);
boolean mentionsSelf = CursorUtil.requireBoolean(cursor, MENTIONS_SELF);
long notifiedTimestamp = CursorUtil.requireLong(cursor, NOTIFIED_TIMESTAMP);
int viewedReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsColumns.VIEWED_RECEIPT_COUNT));
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
readReceiptCount = 0;
readReceiptCount = 0;
viewedReceiptCount = 0;
}
Recipient recipient = Recipient.live(RecipientId.from(recipientId)).get();
@ -1928,7 +1996,7 @@ public class MmsDatabase extends MessageDatabase {
threadId, body, slideDeck, partCount, box, mismatches,
networkFailures, subscriptionId, expiresIn, expireStarted,
isViewOnce, readReceiptCount, quote, contacts, previews, unidentified, reactions,
remoteDelete, mentionsSelf, notifiedTimestamp);
remoteDelete, mentionsSelf, notifiedTimestamp, viewedReceiptCount);
}
private List<IdentityKeyMismatch> getMismatchedIdentities(String document) {

Wyświetl plik

@ -14,6 +14,7 @@ public interface MmsSmsColumns {
public static final String ADDRESS_DEVICE_ID = "address_device_id";
public static final String DELIVERY_RECEIPT_COUNT = "delivery_receipt_count";
public static final String READ_RECEIPT_COUNT = "read_receipt_count";
public static final String VIEWED_RECEIPT_COUNT = "viewed_receipt_count";
public static final String MISMATCHED_IDENTITIES = "mismatched_identities";
public static final String UNIQUE_ROW_ID = "unique_row_id";
public static final String SUBSCRIPTION_ID = "subscription_id";

Wyświetl plik

@ -101,7 +101,8 @@ public class MmsSmsDatabase extends Database {
MmsSmsColumns.REACTIONS_LAST_SEEN,
MmsSmsColumns.REMOTE_DELETED,
MmsDatabase.MENTIONS_SELF,
MmsSmsColumns.NOTIFIED_TIMESTAMP};
MmsSmsColumns.NOTIFIED_TIMESTAMP,
MmsSmsColumns.VIEWED_RECEIPT_COUNT};
public MmsSmsDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper);
@ -339,8 +340,8 @@ public class MmsSmsDatabase extends Database {
db.beginTransaction();
try {
DatabaseFactory.getSmsDatabase(context).incrementReceiptCount(syncMessageId, timestamp, true);
DatabaseFactory.getMmsDatabase(context).incrementReceiptCount(syncMessageId, timestamp, true);
DatabaseFactory.getSmsDatabase(context).incrementReceiptCount(syncMessageId, timestamp, MessageDatabase.ReceiptType.DELIVERY);
DatabaseFactory.getMmsDatabase(context).incrementReceiptCount(syncMessageId, timestamp, MessageDatabase.ReceiptType.DELIVERY);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
@ -379,8 +380,8 @@ public class MmsSmsDatabase extends Database {
try {
boolean handled = false;
handled |= DatabaseFactory.getSmsDatabase(context).incrementReceiptCount(syncMessageId, timestamp, false);
handled |= DatabaseFactory.getMmsDatabase(context).incrementReceiptCount(syncMessageId, timestamp, false);
handled |= DatabaseFactory.getSmsDatabase(context).incrementReceiptCount(syncMessageId, timestamp, MessageDatabase.ReceiptType.READ);
handled |= DatabaseFactory.getMmsDatabase(context).incrementReceiptCount(syncMessageId, timestamp, MessageDatabase.ReceiptType.READ);
db.setTransactionSuccessful();
@ -390,6 +391,35 @@ public class MmsSmsDatabase extends Database {
}
}
/**
* @return A list of ID's that were not updated.
*/
public @NonNull Collection<SyncMessageId> incrementViewedReceiptCounts(@NonNull List<SyncMessageId> syncMessageIds, long timestamp) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
List<SyncMessageId> unhandled = new LinkedList<>();
db.beginTransaction();
try {
for (SyncMessageId id : syncMessageIds) {
boolean handled = incrementViewedReceiptCount(id, timestamp);
if (!handled) {
unhandled.add(id);
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return unhandled;
}
public boolean incrementViewedReceiptCount(SyncMessageId syncMessageId, long timestamp) {
return DatabaseFactory.getMmsDatabase(context).incrementReceiptCount(syncMessageId, timestamp, MessageDatabase.ReceiptType.VIEWED);
}
public int getQuotedMessagePosition(long threadId, long quoteId, @NonNull RecipientId recipientId) {
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " + MmsSmsColumns.REMOTE_DELETED + " = 0";
@ -546,7 +576,8 @@ public class MmsSmsDatabase extends Database {
MmsSmsColumns.DATE_SERVER,
MmsSmsColumns.REMOTE_DELETED,
MmsDatabase.MENTIONS_SELF,
MmsSmsColumns.NOTIFIED_TIMESTAMP };
MmsSmsColumns.NOTIFIED_TIMESTAMP,
MmsSmsColumns.VIEWED_RECEIPT_COUNT};
String[] smsProjection = {SmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
@ -581,7 +612,8 @@ public class MmsSmsDatabase extends Database {
MmsSmsColumns.DATE_SERVER,
MmsSmsColumns.REMOTE_DELETED,
MmsDatabase.MENTIONS_SELF,
MmsSmsColumns.NOTIFIED_TIMESTAMP };
MmsSmsColumns.NOTIFIED_TIMESTAMP,
MmsSmsColumns.VIEWED_RECEIPT_COUNT};
SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
@ -637,6 +669,7 @@ public class MmsSmsDatabase extends Database {
mmsColumnsPresent.add(MmsDatabase.REMOTE_DELETED);
mmsColumnsPresent.add(MmsDatabase.MENTIONS_SELF);
mmsColumnsPresent.add(MmsSmsColumns.NOTIFIED_TIMESTAMP);
mmsColumnsPresent.add(MmsSmsColumns.VIEWED_RECEIPT_COUNT);
Set<String> smsColumnsPresent = new HashSet<>();
smsColumnsPresent.add(MmsSmsColumns.ID);

Wyświetl plik

@ -471,7 +471,11 @@ public class SmsDatabase extends MessageDatabase {
}
@Override
public boolean incrementReceiptCount(SyncMessageId messageId, long timestamp, boolean deliveryReceipt) {
public boolean incrementReceiptCount(SyncMessageId messageId, long timestamp, @NonNull ReceiptType receiptType) {
if (receiptType == ReceiptType.VIEWED) {
return false;
}
SQLiteDatabase database = databaseHelper.getWritableDatabase();
boolean foundMessage = false;
@ -483,7 +487,7 @@ public class SmsDatabase extends MessageDatabase {
if (Types.isOutgoingMessageType(cursor.getLong(cursor.getColumnIndexOrThrow(TYPE)))) {
RecipientId theirRecipientId = messageId.getRecipientId();
RecipientId outRecipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT_ID)));
String columnName = deliveryReceipt ? DELIVERY_RECEIPT_COUNT : READ_RECEIPT_COUNT;
String columnName = receiptType.getColumnName();
boolean isFirstIncrement = cursor.getLong(cursor.getColumnIndexOrThrow(columnName)) == 0;
if (outRecipientId.equals(theirRecipientId)) {
@ -507,7 +511,7 @@ public class SmsDatabase extends MessageDatabase {
}
}
if (!foundMessage && deliveryReceipt) {
if (!foundMessage && receiptType == ReceiptType.DELIVERY) {
earlyDeliveryReceiptCache.increment(messageId.getTimetamp(), messageId.getRecipientId());
return true;
}
@ -623,6 +627,16 @@ public class SmsDatabase extends MessageDatabase {
return updateMessageBodyAndType(messageId, body, Types.TOTAL_MASK, type);
}
@Override
public @NonNull List<MarkedMessageInfo> getViewedIncomingMessages(long threadId) {
return Collections.emptyList();
}
@Override
public @Nullable MarkedMessageInfo setIncomingMessageViewed(long messageId) {
return null;
}
private Pair<Long, Long> updateMessageBodyAndType(long messageId, String body, long maskOff, long maskOn) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.execSQL("UPDATE " + TABLE_NAME + " SET " + BODY + " = ?, " +

Wyświetl plik

@ -160,8 +160,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private static final int GV1_MIGRATION = 80;
private static final int NOTIFIED_TIMESTAMP = 81;
private static final int GV1_MIGRATION_LAST_SEEN = 82;
private static final int VIEWED_RECEIPTS = 83;
private static final int DATABASE_VERSION = 82;
private static final int DATABASE_VERSION = 83;
private static final String DATABASE_NAME = "signal.db";
private final Context context;
@ -1170,6 +1171,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE recipient ADD COLUMN last_gv1_migrate_reminder INTEGER DEFAULT 0");
}
if (oldVersion < VIEWED_RECEIPTS) {
db.execSQL("ALTER TABLE mms ADD COLUMN viewed_receipt_count INTEGER DEFAULT 0");
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();

Wyświetl plik

@ -45,10 +45,11 @@ public abstract class DisplayRecord {
private final int deliveryStatus;
private final int deliveryReceiptCount;
private final int readReceiptCount;
private final int viewReceiptCount;
DisplayRecord(String body, Recipient recipient, long dateSent,
long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount,
long type, int readReceiptCount)
long type, int readReceiptCount, int viewReceiptCount)
{
this.threadId = threadId;
this.recipient = recipient;
@ -59,6 +60,7 @@ public abstract class DisplayRecord {
this.deliveryReceiptCount = deliveryReceiptCount;
this.readReceiptCount = readReceiptCount;
this.deliveryStatus = deliveryStatus;
this.viewReceiptCount = viewReceiptCount;
}
public @NonNull String getBody() {
@ -188,6 +190,17 @@ public abstract class DisplayRecord {
return readReceiptCount;
}
/**
* For outgoing messages, this is incremented whenever a remote recipient has viewed our message
* and sends us a VIEWED receipt. For incoming messages, this is an indication of whether local
* user has viewed a piece of content.
*
* @return the number of times this has been viewed.
*/
public int getViewedReceiptCount() {
return viewReceiptCount;
}
public boolean isDelivered() {
return (deliveryStatus >= SmsDatabase.Status.STATUS_COMPLETE &&
deliveryStatus < SmsDatabase.Status.STATUS_PENDING) || deliveryReceiptCount > 0;

Wyświetl plik

@ -74,12 +74,13 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
@NonNull List<ReactionRecord> reactions,
boolean remoteDelete,
boolean mentionsSelf,
long notifiedTimestamp)
long notifiedTimestamp,
int viewedReceiptCount)
{
super(id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent,
dateReceived, dateServer, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures,
subscriptionId, expiresIn, expireStarted, viewOnce, slideDeck,
readReceiptCount, quote, contacts, linkPreviews, unidentified, reactions, remoteDelete, notifiedTimestamp);
readReceiptCount, quote, contacts, linkPreviews, unidentified, reactions, remoteDelete, notifiedTimestamp, viewedReceiptCount);
this.partCount = partCount;
this.mentionsSelf = mentionsSelf;
}

Wyświetl plik

@ -93,10 +93,12 @@ public abstract class MessageRecord extends DisplayRecord {
List<NetworkFailure> networkFailures,
int subscriptionId, long expiresIn, long expireStarted,
int readReceiptCount, boolean unidentified,
@NonNull List<ReactionRecord> reactions, boolean remoteDelete, long notifiedTimestamp)
@NonNull List<ReactionRecord> reactions, boolean remoteDelete, long notifiedTimestamp,
int viewedReceiptCount)
{
super(body, conversationRecipient, dateSent, dateReceived,
threadId, deliveryStatus, deliveryReceiptCount, type, readReceiptCount);
threadId, deliveryStatus, deliveryReceiptCount, type,
readReceiptCount, viewedReceiptCount);
this.id = id;
this.individualRecipient = individualRecipient;
this.recipientDeviceId = recipientDeviceId;

Wyświetl plik

@ -33,9 +33,10 @@ public abstract class MmsMessageRecord extends MessageRecord {
@NonNull SlideDeck slideDeck, int readReceiptCount,
@Nullable Quote quote, @NonNull List<Contact> contacts,
@NonNull List<LinkPreview> linkPreviews, boolean unidentified,
@NonNull List<ReactionRecord> reactions, boolean remoteDelete, long notifiedTimestamp)
@NonNull List<ReactionRecord> reactions, boolean remoteDelete, long notifiedTimestamp,
int viewedReceiptCount)
{
super(id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent, dateReceived, dateServer, threadId, deliveryStatus, deliveryReceiptCount, type, mismatches, networkFailures, subscriptionId, expiresIn, expireStarted, readReceiptCount, unidentified, reactions, remoteDelete, notifiedTimestamp);
super(id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent, dateReceived, dateServer, threadId, deliveryStatus, deliveryReceiptCount, type, mismatches, networkFailures, subscriptionId, expiresIn, expireStarted, readReceiptCount, unidentified, reactions, remoteDelete, notifiedTimestamp, viewedReceiptCount);
this.slideDeck = slideDeck;
this.quote = quote;

Wyświetl plik

@ -50,13 +50,14 @@ public class NotificationMmsMessageRecord extends MmsMessageRecord {
long dateSent, long dateReceived, int deliveryReceiptCount,
long threadId, byte[] contentLocation, long messageSize,
long expiry, int status, byte[] transactionId, long mailbox,
int subscriptionId, SlideDeck slideDeck, int readReceiptCount)
int subscriptionId, SlideDeck slideDeck, int readReceiptCount,
int viewedReceiptCount)
{
super(id, "", conversationRecipient, individualRecipient, recipientDeviceId,
dateSent, dateReceived, -1, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox,
new LinkedList<>(), new LinkedList<>(), subscriptionId,
0, 0, false, slideDeck, readReceiptCount, null, Collections.emptyList(), Collections.emptyList(), false,
Collections.emptyList(), false, 0);
Collections.emptyList(), false, 0, viewedReceiptCount);
this.contentLocation = contentLocation;
this.messageSize = messageSize;

Wyświetl plik

@ -55,7 +55,7 @@ public class SmsMessageRecord extends MessageRecord {
super(id, body, recipient, individualRecipient, recipientDeviceId,
dateSent, dateReceived, dateServer, threadId, status, deliveryReceiptCount, type,
mismatches, new LinkedList<>(), subscriptionId,
expiresIn, expireStarted, readReceiptCount, unidentified, reactions, remoteDelete, notifiedTimestamp);
expiresIn, expireStarted, readReceiptCount, unidentified, reactions, remoteDelete, notifiedTimestamp, 0);
}
public long getType() {

Wyświetl plik

@ -8,9 +8,11 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class Data {
@ -138,6 +140,19 @@ public class Data {
return longArrays.get(key);
}
public List<Long> getLongArrayAsList(@NonNull String key) {
throwIfAbsent(longArrays, key);
long[] array = Objects.requireNonNull(longArrays.get(key));
List<Long> longs = new ArrayList<>(array.length);
for (long l : array) {
longs.add(l);
}
return longs;
}
public boolean hasFloat(@NonNull String key) {
return floats.containsKey(key);
@ -295,6 +310,17 @@ public class Data {
return this;
}
public Builder putLongListAsArray(@NonNull String key, @NonNull List<Long> value) {
long[] longs = new long[value.size()];
for (int i = 0; i < value.size(); i++) {
longs[i] = value.get(i);
}
longArrays.put(key, longs);
return this;
}
public Builder putFloat(@NonNull String key, float value) {
floats.put(key, value);
return this;

Wyświetl plik

@ -39,6 +39,7 @@ import org.thoughtcrime.securesms.jobs.RotateProfileKeyJob;
import org.thoughtcrime.securesms.jobs.RotateSignedPreKeyJob;
import org.thoughtcrime.securesms.jobs.SendDeliveryReceiptJob;
import org.thoughtcrime.securesms.jobs.SendReadReceiptJob;
import org.thoughtcrime.securesms.jobs.SendViewedReceiptJob;
import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob;
import org.thoughtcrime.securesms.jobs.SmsReceiveJob;
import org.thoughtcrime.securesms.jobs.SmsSendJob;
@ -90,6 +91,7 @@ public class WorkManagerFactoryMappings {
put("RotateSignedPreKeyJob", RotateSignedPreKeyJob.KEY);
put("SendDeliveryReceiptJob", SendDeliveryReceiptJob.KEY);
put("SendReadReceiptJob", SendReadReceiptJob.KEY);
put("SendViewedReceiptJob", SendViewedReceiptJob.KEY);
put("ServiceOutageDetectionJob", ServiceOutageDetectionJob.KEY);
put("SmsReceiveJob", SmsReceiveJob.KEY);
put("SmsSendJob", SmsSendJob.KEY);

Wyświetl plik

@ -120,6 +120,7 @@ public final class JobManagerFactories {
put(RotateSignedPreKeyJob.KEY, new RotateSignedPreKeyJob.Factory());
put(SendDeliveryReceiptJob.KEY, new SendDeliveryReceiptJob.Factory());
put(SendReadReceiptJob.KEY, new SendReadReceiptJob.Factory(application));
put(SendViewedReceiptJob.KEY, new SendViewedReceiptJob.Factory(application));
put(ServiceOutageDetectionJob.KEY, new ServiceOutageDetectionJob.Factory());
put(SmsReceiveJob.KEY, new SmsReceiveJob.Factory());
put(SmsSendJob.KEY, new SmsSendJob.Factory());

Wyświetl plik

@ -137,6 +137,7 @@ public class PushMediaSendJob extends PushSendJob {
SyncMessageId id = new SyncMessageId(recipient.getId(), message.getSentTimeMillis());
DatabaseFactory.getMmsSmsDatabase(context).incrementDeliveryReceiptCount(id, System.currentTimeMillis());
DatabaseFactory.getMmsSmsDatabase(context).incrementReadReceiptCount(id, System.currentTimeMillis());
DatabaseFactory.getMmsSmsDatabase(context).incrementViewedReceiptCount(id, System.currentTimeMillis());
}
if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN && profileKey == null) {

Wyświetl plik

@ -433,6 +433,7 @@ public final class PushProcessMessageJob extends BaseJob {
if (message.isReadReceipt()) handleReadReceipt(content, message);
else if (message.isDeliveryReceipt()) handleDeliveryReceipt(content, message);
else if (message.isViewedReceipt()) handleViewedReceipt(content, message);
} else if (content.getTypingMessage().isPresent()) {
handleTypingMessage(content, content.getTypingMessage().get());
} else {
@ -1572,6 +1573,29 @@ public final class PushProcessMessageJob extends BaseJob {
ApplicationDependencies.getJobManager().add(new SendDeliveryReceiptJob(RecipientId.fromHighTrust(content.getSender()), message.getTimestamp()));
}
private void handleViewedReceipt(@NonNull SignalServiceContent content,
@NonNull SignalServiceReceiptMessage message)
{
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
log(TAG, "Ignoring viewed receipts for IDs: " + Util.join(message.getTimestamps(), ", "));
return;
}
log(TAG, "Processing viewed reciepts for IDs: " + Util.join(message.getTimestamps(), ","));
Recipient sender = Recipient.externalHighTrustPush(context, content.getSender());
List<SyncMessageId> ids = Stream.of(message.getTimestamps())
.map(t -> new SyncMessageId(sender.getId(), t))
.toList();
Collection<SyncMessageId> unhandled = DatabaseFactory.getMmsSmsDatabase(context)
.incrementViewedReceiptCounts(ids, content.getTimestamp());
for (SyncMessageId id : unhandled) {
warn(TAG, String.valueOf(content.getTimestamp()), "[handleViewedReceipt] Could not find matching message! timestamp: " + id.getTimetamp() + " author: " + sender.getId());
ApplicationDependencies.getEarlyMessageCache().store(sender.getId(), id.getTimetamp(), content);
}
}
@SuppressLint("DefaultLocale")
private void handleDeliveryReceipt(@NonNull SignalServiceContent content,
@NonNull SignalServiceReceiptMessage message)

Wyświetl plik

@ -0,0 +1,152 @@
package org.thoughtcrime.securesms.jobs;
import android.app.Application;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
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.SignalServiceReceiptMessage;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class SendViewedReceiptJob extends BaseJob {
public static final String KEY = "SendViewedReceiptJob";
private static final String TAG = SendViewedReceiptJob.class.getSimpleName();
private static final String KEY_THREAD = "thread";
private static final String KEY_ADDRESS = "address";
private static final String KEY_RECIPIENT = "recipient";
private static final String KEY_SYNC_TIMESTAMPS = "message_ids";
private static final String KEY_TIMESTAMP = "timestamp";
private long threadId;
private RecipientId recipientId;
private List<Long> syncTimestamps;
private long timestamp;
public SendViewedReceiptJob(long threadId, @NonNull RecipientId recipientId, long syncTimestamp) {
this(threadId, recipientId, Collections.singletonList(syncTimestamp));
}
public SendViewedReceiptJob(long threadId, @NonNull RecipientId recipientId, @NonNull List<Long> syncTimestamps) {
this(new Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(Parameters.UNLIMITED)
.build(),
threadId,
recipientId,
syncTimestamps,
System.currentTimeMillis());
}
private SendViewedReceiptJob(@NonNull Parameters parameters,
long threadId,
@NonNull RecipientId recipientId,
@NonNull List<Long> syncTimestamps,
long timestamp)
{
super(parameters);
this.threadId = threadId;
this.recipientId = recipientId;
this.syncTimestamps = syncTimestamps;
this.timestamp = timestamp;
}
@Override
public @NonNull Data serialize() {
return new Data.Builder().putString(KEY_RECIPIENT, recipientId.serialize())
.putLongListAsArray(KEY_SYNC_TIMESTAMPS, syncTimestamps)
.putLong(KEY_TIMESTAMP, timestamp)
.putLong(KEY_THREAD, threadId)
.build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onRun() throws IOException, UntrustedIdentityException {
if (!TextSecurePreferences.isReadReceiptsEnabled(context) || syncTimestamps.isEmpty()) return;
if (!RecipientUtil.isMessageRequestAccepted(context, threadId)) {
Log.w(TAG, "Refusing to send receipts to untrusted recipient");
return;
}
Recipient recipient = Recipient.resolved(recipientId);
if (recipient.isBlocked()) {
Log.w(TAG, "Refusing to send receipts to blocked recipient");
return;
}
if (recipient.isGroup()) {
Log.w(TAG, "Refusing to send receipts to group");
return;
}
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
SignalServiceAddress remoteAddress = RecipientUtil.toSignalServiceAddress(context, recipient);
SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.VIEWED,
syncTimestamps,
timestamp);
messageSender.sendReceipt(remoteAddress,
UnidentifiedAccessUtil.getAccessFor(context, Recipient.resolved(recipientId)),
receiptMessage);
}
@Override
public boolean onShouldRetry(@NonNull Exception e) {
if (e instanceof PushNetworkException) return true;
return false;
}
@Override
public void onFailure() {
Log.w(TAG, "Failed to send read receipts to: " + recipientId);
}
public static final class Factory implements Job.Factory<SendViewedReceiptJob> {
private final Application application;
public Factory(@NonNull Application application) {
this.application = application;
}
@Override
public @NonNull
SendViewedReceiptJob create(@NonNull Parameters parameters, @NonNull Data data) {
long timestamp = data.getLong(KEY_TIMESTAMP);
List<Long> syncTimestamps = data.getLongArrayAsList(KEY_SYNC_TIMESTAMPS);
RecipientId recipientId = data.hasString(KEY_RECIPIENT) ? RecipientId.from(data.getString(KEY_RECIPIENT))
: Recipient.external(application, data.getString(KEY_ADDRESS)).getId();
long threadId = data.getLong(KEY_THREAD);
return new SendViewedReceiptJob(parameters, threadId, recipientId, syncTimestamps, timestamp);
}
}
}

Wyświetl plik

@ -6,6 +6,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import androidx.core.util.Consumer;
import com.annimon.stream.Stream;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
@ -19,6 +21,7 @@ import org.thoughtcrime.securesms.groups.GroupManager;
import org.thoughtcrime.securesms.groups.ui.GroupChangeErrorCallback;
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
import org.thoughtcrime.securesms.jobs.MultiDeviceMessageRequestResponseJob;
import org.thoughtcrime.securesms.jobs.SendViewedReceiptJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
@ -140,6 +143,16 @@ final class MessageRequestRepository {
ApplicationDependencies.getMessageNotifier().updateNotification(context);
MarkReadReceiver.process(context, messageIds);
List<MessageDatabase.MarkedMessageInfo> viewedInfos = DatabaseFactory.getMmsDatabase(context)
.getViewedIncomingMessages(threadId);
ApplicationDependencies.getJobManager()
.add(new SendViewedReceiptJob(threadId,
liveRecipient.getId(),
Stream.of(viewedInfos)
.map(info -> info.getSyncMessageId().getTimetamp())
.toList()));
if (TextSecurePreferences.isMultiDevice(context)) {
ApplicationDependencies.getJobManager().add(MultiDeviceMessageRequestResponseJob.forAccept(liveRecipient.getId()));
}

Wyświetl plik

@ -8,6 +8,8 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
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.SendViewedReceiptJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.whispersystems.libsignal.util.guava.Optional;
@ -26,6 +28,12 @@ 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()));
}
callback.onComplete(Optional.fromNullable(record));
}
});

Wyświetl plik

@ -505,6 +505,7 @@ public class MessageSender {
mmsSmsDatabase.incrementDeliveryReceiptCount(syncId, System.currentTimeMillis());
mmsSmsDatabase.incrementReadReceiptCount(syncId, System.currentTimeMillis());
mmsSmsDatabase.incrementViewedReceiptCount(syncId, System.currentTimeMillis());
if (message.getExpiresIn() > 0 && !message.isExpirationUpdate()) {
mmsDatabase.markExpireStarted(messageId);

Wyświetl plik

@ -534,6 +534,7 @@ public class SignalServiceMessageSender {
if (message.isDeliveryReceipt()) builder.setType(ReceiptMessage.Type.DELIVERY);
else if (message.isReadReceipt()) builder.setType(ReceiptMessage.Type.READ);
else if (message.isViewedReceipt()) builder.setType(ReceiptMessage.Type.VIEWED);
return container.setReceiptMessage(builder).build().toByteArray();
}

Wyświetl plik

@ -632,6 +632,7 @@ public final class SignalServiceContent {
if (content.getType() == SignalServiceProtos.ReceiptMessage.Type.DELIVERY) type = SignalServiceReceiptMessage.Type.DELIVERY;
else if (content.getType() == SignalServiceProtos.ReceiptMessage.Type.READ) type = SignalServiceReceiptMessage.Type.READ;
else if (content.getType() == SignalServiceProtos.ReceiptMessage.Type.VIEWED) type = SignalServiceReceiptMessage.Type.VIEWED;
else type = SignalServiceReceiptMessage.Type.UNKNOWN;
return new SignalServiceReceiptMessage(type, content.getTimestampList(), metadata.getTimestamp());

Wyświetl plik

@ -6,7 +6,7 @@ import java.util.List;
public class SignalServiceReceiptMessage {
public enum Type {
UNKNOWN, DELIVERY, READ
UNKNOWN, DELIVERY, READ, VIEWED
}
private final Type type;
@ -38,4 +38,8 @@ public class SignalServiceReceiptMessage {
public boolean isReadReceipt() {
return type == Type.READ;
}
public boolean isViewedReceipt() {
return type == Type.VIEWED;
}
}

Wyświetl plik

@ -279,6 +279,7 @@ message ReceiptMessage {
enum Type {
DELIVERY = 0;
READ = 1;
VIEWED = 2;
}
optional Type type = 1;