From cb9bc9659b5926ea6777524fd7fd0fbd67f57b8e Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Fri, 15 Sep 2017 22:38:53 -0700 Subject: [PATCH] Support for read receipts // FREEBIE --- build.gradle | 6 +- res/layout/delivery_status_view.xml | 12 ++ res/values/strings.xml | 4 +- res/xml/preferences_app_protection.xml | 6 + .../securesms/ConversationItem.java | 9 +- .../securesms/ConversationListItem.java | 7 +- .../securesms/PassphraseCreateActivity.java | 1 + .../components/DeliveryStatusView.java | 13 +++ .../securesms/database/DatabaseFactory.java | 9 +- .../securesms/database/MmsDatabase.java | 107 +++++++++++------- .../securesms/database/MmsSmsColumns.java | 3 +- .../securesms/database/MmsSmsDatabase.java | 28 +++-- .../securesms/database/SmsDatabase.java | 62 ++++++---- .../securesms/database/ThreadDatabase.java | 91 ++++++++------- .../loaders/ConversationListLoader.java | 6 +- .../database/model/DisplayRecord.java | 23 +++- .../database/model/MediaMmsMessageRecord.java | 8 +- .../database/model/MessageRecord.java | 9 +- .../database/model/MmsMessageRecord.java | 6 +- .../model/NotificationMmsMessageRecord.java | 8 +- .../database/model/SmsMessageRecord.java | 9 +- .../database/model/ThreadRecord.java | 7 +- .../SignalCommunicationModule.java | 67 ++++++----- .../securesms/gcm/GcmBroadcastReceiver.java | 4 +- .../securesms/jobs/DeliveryReceiptJob.java | 71 ------------ .../jobs/MultiDeviceBlockedUpdateJob.java | 10 +- .../jobs/MultiDeviceContactUpdateJob.java | 9 +- .../jobs/MultiDeviceGroupUpdateJob.java | 9 +- .../jobs/MultiDeviceProfileKeyUpdateJob.java | 6 +- .../jobs/MultiDeviceReadUpdateJob.java | 5 +- .../jobs/MultiDeviceVerifiedUpdateJob.java | 4 +- .../securesms/jobs/PushContentReceiveJob.java | 2 +- .../securesms/jobs/PushDecryptJob.java | 29 +++++ .../securesms/jobs/PushGroupSendJob.java | 5 +- .../securesms/jobs/PushGroupUpdateJob.java | 10 +- .../securesms/jobs/PushMediaSendJob.java | 6 +- .../jobs/PushNotificationReceiveJob.java | 3 +- .../securesms/jobs/PushReceivedJob.java | 17 +-- .../securesms/jobs/PushTextSendJob.java | 5 +- .../securesms/jobs/RequestGroupInfoJob.java | 19 ++-- .../securesms/jobs/SendReadReceiptJob.java | 69 +++++++++++ .../notifications/MarkReadReceiver.java | 18 +++ .../service/MessageRetrievalService.java | 2 +- .../securesms/service/WebRtcCallService.java | 5 +- .../securesms/util/TextSecurePreferences.java | 9 ++ .../jobs/DeliveryReceiptJobTest.java | 104 ----------------- 46 files changed, 471 insertions(+), 451 deletions(-) delete mode 100644 src/org/thoughtcrime/securesms/jobs/DeliveryReceiptJob.java create mode 100644 src/org/thoughtcrime/securesms/jobs/SendReadReceiptJob.java delete mode 100644 test/unitTest/java/org/thoughtcrime/securesms/jobs/DeliveryReceiptJobTest.java diff --git a/build.gradle b/build.gradle index 3035c1cf45..4cace387c3 100644 --- a/build.gradle +++ b/build.gradle @@ -63,7 +63,7 @@ dependencies { compile 'org.whispersystems:jobmanager:1.0.2' compile 'org.whispersystems:libpastelog:1.0.7' - compile 'org.whispersystems:signal-service-android:2.6.5' + compile 'org.whispersystems:signal-service-android:2.6.6' compile 'org.whispersystems:webrtc-android:M59-S1' compile "me.leolin:ShortcutBadger:1.1.16" @@ -138,7 +138,7 @@ dependencyVerification { 'com.google.android.exoplayer:exoplayer:955085aa611a8f7cf6c61b88ae03d1a392f4ad94c9bfbc153f3dedb9ffb14718', 'org.whispersystems:jobmanager:506f679fc2fcf7bb6d10f00f41d6f6ea0abf75c70dc95b913398661ad538a181', 'org.whispersystems:libpastelog:bb331d9a98240fc139101128ba836c1edec3c40e000597cdbb29ebf4cbf34d88', - 'org.whispersystems:signal-service-android:690e04d53c8b5ec8cda064b242d7c00b0e5321851b811798dd0ec3712f5e1a85', + 'org.whispersystems:signal-service-android:731fc8c45f38f42b2d0da1053cf32adcd175708e0c126571150690637108971a', 'org.whispersystems:webrtc-android:de647643afbbea45a26a4f24db75aa10bc8de45426e8eb0d9d563cc10af4f582', 'me.leolin:ShortcutBadger:e3cb3e7625892129b0c92dd5e4bc649faffdd526d5af26d9c45ee31ff8851774', 'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb', @@ -173,7 +173,7 @@ dependencyVerification { 'com.google.android.gms:play-services-basement:95dd882c5ffba15b9a99de3fefb05d3a01946623af67454ca00055d222f85a8d', 'com.google.android.gms:play-services-iid:54e919f9957b8b7820da7ee9b83471d00d0cac1cf08ddea8b5b41aea80bb1a70', 'org.whispersystems:signal-protocol-android:5b8acded7f2a40178eb90ab8e8cbfec89d170d91b3ff5e78487d1098df6185a1', - 'org.whispersystems:signal-service-java:438e8330cf806152e7e226d8241dd6388ee4005f0ea7d2aaa99d7ef514012dca', + 'org.whispersystems:signal-service-java:90aadf941cc31cb0f5af9e1adffa248556f1cfc56a62c141bc62812e7bf3ed52', 'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a', 'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'com.klinkerapps:logger:177e325259a8b111ad6745ec10db5861723c99f402222b80629f576f49408541', diff --git a/res/layout/delivery_status_view.xml b/res/layout/delivery_status_view.xml index a28be96731..4281be8828 100644 --- a/res/layout/delivery_status_view.xml +++ b/res/layout/delivery_status_view.xml @@ -27,6 +27,18 @@ android:paddingBottom="2dp" android:visibility="gone" android:contentDescription="@string/conversation_item_sent__delivered_description" + tools:visibility="gone"/> + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 6ec9785e12..85d85cad5a 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1456,7 +1456,9 @@ Transport icon - + Message read + Read receipts + If you read receipts are disabled, you won\'t be able to see read receipts from others. diff --git a/res/xml/preferences_app_protection.xml b/res/xml/preferences_app_protection.xml index d103fe7430..a0fa003aed 100644 --- a/res/xml/preferences_app_protection.xml +++ b/res/xml/preferences_app_protection.xml @@ -40,6 +40,12 @@ android:title="@string/preferences_advanced__always_relay_calls" android:summary="@string/preferences_advanced__relay_all_calls_through_the_signal_server_to_avoid_revealing_your_ip_address"/> + + diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java index 5202747e5c..e45fbbfec3 100644 --- a/src/org/thoughtcrime/securesms/ConversationItem.java +++ b/src/org/thoughtcrime/securesms/ConversationItem.java @@ -443,10 +443,11 @@ public class ConversationItem extends LinearLayout } else { alertView.setNone(); - if (!messageRecord.isOutgoing()) deliveryStatusIndicator.setNone(); - else if (messageRecord.isPending()) deliveryStatusIndicator.setPending(); - else if (messageRecord.isDelivered()) deliveryStatusIndicator.setDelivered(); - else deliveryStatusIndicator.setSent(); + if (!messageRecord.isOutgoing()) deliveryStatusIndicator.setNone(); + else if (messageRecord.isPending()) deliveryStatusIndicator.setPending(); + else if (messageRecord.isRemoteRead()) deliveryStatusIndicator.setRead(); + else if (messageRecord.isDelivered()) deliveryStatusIndicator.setDelivered(); + else deliveryStatusIndicator.setSent(); } } diff --git a/src/org/thoughtcrime/securesms/ConversationListItem.java b/src/org/thoughtcrime/securesms/ConversationListItem.java index 75438e56cf..a5cfdb32bd 100644 --- a/src/org/thoughtcrime/securesms/ConversationListItem.java +++ b/src/org/thoughtcrime/securesms/ConversationListItem.java @@ -214,9 +214,10 @@ public class ConversationListItem extends RelativeLayout } else { alertView.setNone(); - if (thread.isPending()) deliveryStatusIndicator.setPending(); - else if (thread.isDelivered()) deliveryStatusIndicator.setDelivered(); - else deliveryStatusIndicator.setSent(); + if (thread.isPending()) deliveryStatusIndicator.setPending(); + else if (thread.isRemoteRead()) deliveryStatusIndicator.setRead(); + else if (thread.isDelivered()) deliveryStatusIndicator.setDelivered(); + else deliveryStatusIndicator.setSent(); } } diff --git a/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java b/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java index f1d723e182..1fec151451 100644 --- a/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java +++ b/src/org/thoughtcrime/securesms/PassphraseCreateActivity.java @@ -68,6 +68,7 @@ public class PassphraseCreateActivity extends PassphraseActivity { VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this); TextSecurePreferences.setLastExperienceVersionCode(PassphraseCreateActivity.this, Util.getCurrentApkReleaseVersion(PassphraseCreateActivity.this)); TextSecurePreferences.setPasswordDisabled(PassphraseCreateActivity.this, true); + TextSecurePreferences.setReadReceiptsEnabled(PassphraseCreateActivity.this, true); return null; } diff --git a/src/org/thoughtcrime/securesms/components/DeliveryStatusView.java b/src/org/thoughtcrime/securesms/components/DeliveryStatusView.java index faedeafbd6..ffb53c6d78 100644 --- a/src/org/thoughtcrime/securesms/components/DeliveryStatusView.java +++ b/src/org/thoughtcrime/securesms/components/DeliveryStatusView.java @@ -22,6 +22,7 @@ public class DeliveryStatusView extends FrameLayout { private final ViewGroup pendingIndicatorStub; private final ImageView sentIndicator; private final ImageView deliveredIndicator; + private final ImageView readIndicator; public DeliveryStatusView(Context context) { this(context, null); @@ -39,6 +40,7 @@ public class DeliveryStatusView extends FrameLayout { this.deliveredIndicator = (ImageView) findViewById(R.id.delivered_indicator); this.sentIndicator = (ImageView) findViewById(R.id.sent_indicator); this.pendingIndicatorStub = (ViewGroup) findViewById(R.id.pending_indicator_stub); + this.readIndicator = (ImageView) findViewById(R.id.read_indicator); int iconColor = Color.GRAY; @@ -71,6 +73,7 @@ public class DeliveryStatusView extends FrameLayout { pendingIndicatorStub.setVisibility(View.VISIBLE); sentIndicator.setVisibility(View.GONE); deliveredIndicator.setVisibility(View.GONE); + readIndicator.setVisibility(View.GONE); } public void setSent() { @@ -78,6 +81,7 @@ public class DeliveryStatusView extends FrameLayout { pendingIndicatorStub.setVisibility(View.GONE); sentIndicator.setVisibility(View.VISIBLE); deliveredIndicator.setVisibility(View.GONE); + readIndicator.setVisibility(View.GONE); } public void setDelivered() { @@ -85,5 +89,14 @@ public class DeliveryStatusView extends FrameLayout { pendingIndicatorStub.setVisibility(View.GONE); sentIndicator.setVisibility(View.GONE); deliveredIndicator.setVisibility(View.VISIBLE); + readIndicator.setVisibility(View.GONE); + } + + public void setRead() { + this.setVisibility(View.VISIBLE); + pendingIndicatorStub.setVisibility(View.GONE); + sentIndicator.setVisibility(View.GONE); + deliveredIndicator.setVisibility(View.GONE); + readIndicator.setVisibility(View.VISIBLE); } } diff --git a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java index 9922d5fa56..2ca34af052 100644 --- a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java @@ -106,7 +106,8 @@ public class DatabaseFactory { private static final int PROFILES = 41; private static final int PROFILE_SHARING_APPROVAL = 42; private static final int UNSEEN_NUMBER_OFFER = 43; - private static final int DATABASE_VERSION = 43; + private static final int READ_RECEIPTS = 44; + private static final int DATABASE_VERSION = 44; private static final String DATABASE_NAME = "messages.db"; private static final Object lock = new Object(); @@ -1321,6 +1322,12 @@ public class DatabaseFactory { db.execSQL("ALTER TABLE thread ADD COLUMN has_sent INTEGER DEFAULT 0"); } + if (oldVersion < READ_RECEIPTS) { + db.execSQL("ALTER TABLE sms ADD COLUMN read_receipt_count INTEGER DEFAULT 0"); + db.execSQL("ATLER TABLE mms ADD COLUMN read_receipt_count INTEGER DEFAULT 0"); + db.execSQL("ALTER TABLE thread ADD COLUMN read_receipt_count INTEGER DEFAULT 0"); + } + db.setTransactionSuccessful(); db.endTransaction(); } diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 0e3aea9db9..7ff7447cf8 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -59,6 +59,7 @@ import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.util.JsonUtils; +import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.jobqueue.JobManager; import org.whispersystems.libsignal.InvalidMessageException; @@ -101,10 +102,11 @@ public class MmsDatabase extends MessagingDatabase { STATUS + " INTEGER, " + TRANSACTION_ID + " TEXT, " + "retr_st" + " INTEGER, " + "retr_txt" + " TEXT, " + "retr_txt_cs" + " INTEGER, " + "read_status" + " INTEGER, " + "ct_cls" + " INTEGER, " + "resp_txt" + " TEXT, " + "d_tm" + " INTEGER, " + - RECEIPT_COUNT + " INTEGER DEFAULT 0, " + MISMATCHED_IDENTITIES + " TEXT DEFAULT NULL, " + + DELIVERY_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + MISMATCHED_IDENTITIES + " TEXT DEFAULT NULL, " + NETWORK_FAILURE + " TEXT DEFAULT NULL," + "d_rpt" + " INTEGER, " + SUBSCRIPTION_ID + " INTEGER DEFAULT -1, " + EXPIRES_IN + " INTEGER DEFAULT 0, " + - EXPIRE_STARTED + " INTEGER DEFAULT 0, " + NOTIFIED + " INTEGER DEFAULT 0);"; + EXPIRE_STARTED + " INTEGER DEFAULT 0, " + NOTIFIED + " INTEGER DEFAULT 0, " + + READ_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 + ");", @@ -123,7 +125,7 @@ public class MmsDatabase extends MessagingDatabase { CONTENT_LOCATION, EXPIRY, MESSAGE_TYPE, MESSAGE_SIZE, STATUS, TRANSACTION_ID, BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID, - RECEIPT_COUNT, MISMATCHED_IDENTITIES, NETWORK_FAILURE, SUBSCRIPTION_ID, + DELIVERY_RECEIPT_COUNT, MISMATCHED_IDENTITIES, NETWORK_FAILURE, SUBSCRIPTION_ID, EXPIRES_IN, EXPIRE_STARTED, NOTIFIED, AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + " AS " + AttachmentDatabase.ATTACHMENT_ID_ALIAS, AttachmentDatabase.UNIQUE_ID, @@ -144,7 +146,9 @@ public class MmsDatabase extends MessagingDatabase { private static final String RAW_ID_WHERE = TABLE_NAME + "._id = ?"; - private final EarlyReceiptCache earlyReceiptCache = new EarlyReceiptCache(); + private final EarlyReceiptCache earlyDeliveryReceiptCache = new EarlyReceiptCache(); + private final EarlyReceiptCache earlyReadReceiptCache = new EarlyReceiptCache(); + private final JobManager jobManager; public MmsDatabase(Context context, SQLiteOpenHelper databaseHelper) { @@ -190,7 +194,7 @@ public class MmsDatabase extends MessagingDatabase { } } - public void incrementDeliveryReceiptCount(SyncMessageId messageId) { + public void incrementReceiptCount(SyncMessageId messageId, boolean deliveryReceipt, boolean readReceipt) { SQLiteDatabase database = databaseHelper.getWritableDatabase(); Cursor cursor = null; boolean found = false; @@ -201,7 +205,8 @@ public class MmsDatabase extends MessagingDatabase { while (cursor.moveToNext()) { if (Types.isOutgoingMessageType(cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX)))) { Address theirAddress = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))); - Address ourAddress = messageId.getAddress(); + Address ourAddress = messageId.getAddress(); + String columnName = deliveryReceipt ? DELIVERY_RECEIPT_COUNT : READ_RECEIPT_COUNT; if (ourAddress.equals(theirAddress) || theirAddress.isGroup()) { long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID)); @@ -210,7 +215,7 @@ public class MmsDatabase extends MessagingDatabase { found = true; database.execSQL("UPDATE " + TABLE_NAME + " SET " + - RECEIPT_COUNT + " = " + RECEIPT_COUNT + " + 1 WHERE " + ID + " = ?", + columnName + " = " + columnName + " + 1 WHERE " + ID + " = ?", new String[] {String.valueOf(id)}); DatabaseFactory.getThreadDatabase(context).update(threadId, false); @@ -220,7 +225,8 @@ public class MmsDatabase extends MessagingDatabase { } if (!found) { - earlyReceiptCache.increment(messageId.getTimetamp(), messageId.getAddress()); + if (deliveryReceipt) earlyDeliveryReceiptCache.increment(messageId.getTimetamp(), messageId.getAddress()); + if (readReceipt) earlyReadReceiptCache.increment(messageId.getTimetamp(), messageId.getAddress()); } } finally { if (cursor != null) @@ -803,7 +809,8 @@ public class MmsDatabase extends MessagingDatabase { contentValues.put(SUBSCRIPTION_ID, message.getSubscriptionId()); contentValues.put(EXPIRES_IN, message.getExpiresIn()); contentValues.put(ADDRESS, message.getRecipient().getAddress().serialize()); - contentValues.put(RECEIPT_COUNT, earlyReceiptCache.remove(message.getSentTimeMillis(), message.getRecipient().getAddress())); + contentValues.put(DELIVERY_RECEIPT_COUNT, earlyDeliveryReceiptCache.remove(message.getSentTimeMillis(), message.getRecipient().getAddress())); + contentValues.put(READ_RECEIPT_COUNT, earlyReadReceiptCache.remove(message.getSentTimeMillis(), message.getRecipient().getAddress())); long messageId = insertMediaMessage(masterSecret, message.getBody(), message.getAttachments(), contentValues, insertListener); @@ -1060,7 +1067,7 @@ public class MmsDatabase extends MessagingDatabase { new LinkedList(), message.getSubscriptionId(), message.getExpiresIn(), - System.currentTimeMillis()); + System.currentTimeMillis(), 0); } } @@ -1096,22 +1103,27 @@ public class MmsDatabase extends MessagingDatabase { } private NotificationMmsMessageRecord getNotificationMmsMessageRecord(Cursor cursor) { - long id = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.ID)); - long dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_SENT)); - long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_RECEIVED)); - long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.THREAD_ID)); - long mailbox = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX)); - String address = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS)); - int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS_DEVICE_ID)); - Recipient recipient = getRecipientFor(address); + long id = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.ID)); + long dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_SENT)); + long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_RECEIVED)); + long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.THREAD_ID)); + long mailbox = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX)); + String address = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS)); + int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS_DEVICE_ID)); + Recipient recipient = getRecipientFor(address); - String contentLocation = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.CONTENT_LOCATION)); - String transactionId = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.TRANSACTION_ID)); - long messageSize = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_SIZE)); - long expiry = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRY)); - int status = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.STATUS)); - int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.RECEIPT_COUNT)); - int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.SUBSCRIPTION_ID)); + String contentLocation = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.CONTENT_LOCATION)); + String transactionId = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.TRANSACTION_ID)); + long messageSize = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_SIZE)); + long expiry = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRY)); + int status = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.STATUS)); + 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)); + + if (!TextSecurePreferences.isReadReceiptsEnabled(context)) { + readReceiptCount = 0; + } byte[]contentLocationBytes = null; byte[]transactionIdBytes = null; @@ -1126,27 +1138,33 @@ public class MmsDatabase extends MessagingDatabase { return new NotificationMmsMessageRecord(context, id, recipient, recipient, - addressDeviceId, dateSent, dateReceived, receiptCount, threadId, + addressDeviceId, dateSent, dateReceived, deliveryReceiptCount, threadId, contentLocationBytes, messageSize, expiry, status, - transactionIdBytes, mailbox, subscriptionId, slideDeck); + transactionIdBytes, mailbox, subscriptionId, slideDeck, + readReceiptCount); } private MediaMmsMessageRecord getMediaMmsMessageRecord(Cursor cursor) { - long id = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.ID)); - long dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_SENT)); - long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_RECEIVED)); - long box = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX)); - long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.THREAD_ID)); - String address = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS)); - int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS_DEVICE_ID)); - int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.RECEIPT_COUNT)); - DisplayRecord.Body body = getBody(cursor); - int partCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.PART_COUNT)); - String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.MISMATCHED_IDENTITIES)); - String networkDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.NETWORK_FAILURE)); - int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.SUBSCRIPTION_ID)); - long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRES_IN)); - long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRE_STARTED)); + long id = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.ID)); + long dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_SENT)); + long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_RECEIVED)); + long box = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX)); + long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.THREAD_ID)); + String address = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS)); + int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS_DEVICE_ID)); + int deliveryReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.DELIVERY_RECEIPT_COUNT)); + int readReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.READ_RECEIPT_COUNT)); + DisplayRecord.Body body = getBody(cursor); + int partCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.PART_COUNT)); + String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.MISMATCHED_IDENTITIES)); + String networkDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.NETWORK_FAILURE)); + int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.SUBSCRIPTION_ID)); + long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRES_IN)); + long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRE_STARTED)); + + if (!TextSecurePreferences.isReadReceiptsEnabled(context)) { + readReceiptCount = 0; + } Recipient recipient = getRecipientFor(address); List mismatches = getMismatchedIdentities(mismatchDocument); @@ -1154,9 +1172,10 @@ public class MmsDatabase extends MessagingDatabase { SlideDeck slideDeck = getSlideDeck(cursor); return new MediaMmsMessageRecord(context, id, recipient, recipient, - addressDeviceId, dateSent, dateReceived, receiptCount, + addressDeviceId, dateSent, dateReceived, deliveryReceiptCount, threadId, body, slideDeck, partCount, box, mismatches, - networkFailures, subscriptionId, expiresIn, expireStarted); + networkFailures, subscriptionId, expiresIn, expireStarted, + readReceiptCount); } private Recipient getRecipientFor(String serialized) { diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java index 59849c96ff..840bafb72e 100644 --- a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -11,7 +11,8 @@ public interface MmsSmsColumns { public static final String BODY = "body"; public static final String ADDRESS = "address"; public static final String ADDRESS_DEVICE_ID = "address_device_id"; - public static final String RECEIPT_COUNT = "delivery_receipt_count"; + public static final String DELIVERY_RECEIPT_COUNT = "delivery_receipt_count"; + public static final String READ_RECEIPT_COUNT = "read_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"; diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index 61f6320ab1..801ff2d2a8 100644 --- a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -25,7 +25,6 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; -import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.database.model.MessageRecord; @@ -52,7 +51,9 @@ public class MmsSmsDatabase extends Database { SmsDatabase.STATUS, MmsDatabase.PART_COUNT, MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID, MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, - MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT, + MmsDatabase.STATUS, + MmsSmsColumns.DELIVERY_RECEIPT_COUNT, + MmsSmsColumns.READ_RECEIPT_COUNT, MmsSmsColumns.MISMATCHED_IDENTITIES, MmsDatabase.NETWORK_FAILURE, MmsSmsColumns.SUBSCRIPTION_ID, @@ -137,8 +138,13 @@ public class MmsSmsDatabase extends Database { } public void incrementDeliveryReceiptCount(SyncMessageId syncMessageId) { - DatabaseFactory.getSmsDatabase(context).incrementDeliveryReceiptCount(syncMessageId); - DatabaseFactory.getMmsDatabase(context).incrementDeliveryReceiptCount(syncMessageId); + DatabaseFactory.getSmsDatabase(context).incrementReceiptCount(syncMessageId, true, false); + DatabaseFactory.getMmsDatabase(context).incrementReceiptCount(syncMessageId, true, false); + } + + public void incrementReadReceiptCount(SyncMessageId syncMessageId) { + DatabaseFactory.getSmsDatabase(context).incrementReceiptCount(syncMessageId, false, true); + DatabaseFactory.getMmsDatabase(context).incrementReceiptCount(syncMessageId, false, true); } private Cursor queryTables(String[] projection, String selection, String order, String limit) { @@ -154,10 +160,11 @@ public class MmsSmsDatabase extends Database { MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT, MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID, MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS, - MmsSmsColumns.RECEIPT_COUNT, MmsSmsColumns.MISMATCHED_IDENTITIES, + MmsSmsColumns.DELIVERY_RECEIPT_COUNT, MmsSmsColumns.READ_RECEIPT_COUNT, + MmsSmsColumns.MISMATCHED_IDENTITIES, MmsSmsColumns.SUBSCRIPTION_ID, MmsSmsColumns.EXPIRES_IN, MmsSmsColumns.EXPIRE_STARTED, MmsSmsColumns.NOTIFIED, - MmsDatabase.NETWORK_FAILURE, TRANSPORT, + MmsDatabase.NETWORK_FAILURE, TRANSPORT, AttachmentDatabase.UNIQUE_ID, AttachmentDatabase.MMS_ID, AttachmentDatabase.SIZE, @@ -185,7 +192,8 @@ public class MmsSmsDatabase extends Database { MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT, MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID, MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS, - MmsSmsColumns.RECEIPT_COUNT, MmsSmsColumns.MISMATCHED_IDENTITIES, + MmsSmsColumns.DELIVERY_RECEIPT_COUNT, MmsSmsColumns.READ_RECEIPT_COUNT, + MmsSmsColumns.MISMATCHED_IDENTITIES, MmsSmsColumns.SUBSCRIPTION_ID, MmsSmsColumns.EXPIRES_IN, MmsSmsColumns.EXPIRE_STARTED, MmsSmsColumns.NOTIFIED, MmsDatabase.NETWORK_FAILURE, TRANSPORT, @@ -227,7 +235,8 @@ public class MmsSmsDatabase extends Database { mmsColumnsPresent.add(MmsSmsColumns.BODY); mmsColumnsPresent.add(MmsSmsColumns.ADDRESS); mmsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID); - mmsColumnsPresent.add(MmsSmsColumns.RECEIPT_COUNT); + mmsColumnsPresent.add(MmsSmsColumns.DELIVERY_RECEIPT_COUNT); + mmsColumnsPresent.add(MmsSmsColumns.READ_RECEIPT_COUNT); mmsColumnsPresent.add(MmsSmsColumns.MISMATCHED_IDENTITIES); mmsColumnsPresent.add(MmsSmsColumns.SUBSCRIPTION_ID); mmsColumnsPresent.add(MmsSmsColumns.EXPIRES_IN); @@ -268,7 +277,8 @@ public class MmsSmsDatabase extends Database { smsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID); smsColumnsPresent.add(MmsSmsColumns.READ); smsColumnsPresent.add(MmsSmsColumns.THREAD_ID); - smsColumnsPresent.add(MmsSmsColumns.RECEIPT_COUNT); + smsColumnsPresent.add(MmsSmsColumns.DELIVERY_RECEIPT_COUNT); + smsColumnsPresent.add(MmsSmsColumns.READ_RECEIPT_COUNT); smsColumnsPresent.add(MmsSmsColumns.MISMATCHED_IDENTITIES); smsColumnsPresent.add(MmsSmsColumns.SUBSCRIPTION_ID); smsColumnsPresent.add(MmsSmsColumns.EXPIRES_IN); diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index 342fc20bd7..1f3e5f0a7a 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -39,6 +39,7 @@ import org.thoughtcrime.securesms.sms.IncomingGroupMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.util.JsonUtils; +import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.jobqueue.JobManager; import org.whispersystems.libsignal.util.guava.Optional; @@ -73,9 +74,10 @@ public class SmsDatabase extends MessagingDatabase { THREAD_ID + " INTEGER, " + ADDRESS + " TEXT, " + ADDRESS_DEVICE_ID + " INTEGER DEFAULT 1, " + PERSON + " INTEGER, " + DATE_RECEIVED + " INTEGER, " + DATE_SENT + " INTEGER, " + PROTOCOL + " INTEGER, " + READ + " INTEGER DEFAULT 0, " + STATUS + " INTEGER DEFAULT -1," + TYPE + " INTEGER, " + REPLY_PATH_PRESENT + " INTEGER, " + - RECEIPT_COUNT + " INTEGER DEFAULT 0," + SUBJECT + " TEXT, " + BODY + " TEXT, " + + DELIVERY_RECEIPT_COUNT + " INTEGER DEFAULT 0," + SUBJECT + " TEXT, " + BODY + " TEXT, " + MISMATCHED_IDENTITIES + " TEXT DEFAULT NULL, " + SERVICE_CENTER + " TEXT, " + SUBSCRIPTION_ID + " INTEGER DEFAULT -1, " + - EXPIRES_IN + " INTEGER DEFAULT 0, " + EXPIRE_STARTED + " INTEGER DEFAULT 0, " + NOTIFIED + " DEFAULT 0);"; + EXPIRES_IN + " INTEGER DEFAULT 0, " + EXPIRE_STARTED + " INTEGER DEFAULT 0, " + NOTIFIED + " DEFAULT 0, " + + READ_RECEIPT_COUNT + " INTEGER DEFAULT 0);"; public static final String[] CREATE_INDEXS = { "CREATE INDEX IF NOT EXISTS sms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");", @@ -91,12 +93,14 @@ public class SmsDatabase extends MessagingDatabase { DATE_RECEIVED + " AS " + NORMALIZED_DATE_RECEIVED, DATE_SENT + " AS " + NORMALIZED_DATE_SENT, PROTOCOL, READ, STATUS, TYPE, - REPLY_PATH_PRESENT, SUBJECT, BODY, SERVICE_CENTER, RECEIPT_COUNT, + REPLY_PATH_PRESENT, SUBJECT, BODY, SERVICE_CENTER, DELIVERY_RECEIPT_COUNT, MISMATCHED_IDENTITIES, SUBSCRIPTION_ID, EXPIRES_IN, EXPIRE_STARTED, - NOTIFIED + NOTIFIED, READ_RECEIPT_COUNT }; - private static final EarlyReceiptCache earlyReceiptCache = new EarlyReceiptCache(); + private static final EarlyReceiptCache earlyDeliveryReceiptCache = new EarlyReceiptCache(); + private static final EarlyReceiptCache earlyReadReceiptCache = new EarlyReceiptCache(); + private final JobManager jobManager; public SmsDatabase(Context context, SQLiteOpenHelper databaseHelper) { @@ -277,7 +281,7 @@ public class SmsDatabase extends MessagingDatabase { database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(id)}); } - public void incrementDeliveryReceiptCount(SyncMessageId messageId) { + public void incrementReceiptCount(SyncMessageId messageId, boolean deliveryReceipt, boolean readReceipt) { SQLiteDatabase database = databaseHelper.getWritableDatabase(); Cursor cursor = null; boolean foundMessage = false; @@ -291,12 +295,13 @@ public class SmsDatabase extends MessagingDatabase { if (Types.isOutgoingMessageType(cursor.getLong(cursor.getColumnIndexOrThrow(TYPE)))) { Address theirAddress = messageId.getAddress(); Address ourAddress = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))); + String columnName = deliveryReceipt ? DELIVERY_RECEIPT_COUNT : READ_RECEIPT_COUNT; if (ourAddress.equals(theirAddress)) { long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID)); database.execSQL("UPDATE " + TABLE_NAME + - " SET " + RECEIPT_COUNT + " = " + RECEIPT_COUNT + " + 1 WHERE " + + " SET " + columnName + " = " + columnName + " + 1 WHERE " + ID + " = ?", new String[] {String.valueOf(cursor.getLong(cursor.getColumnIndexOrThrow(ID)))}); @@ -308,7 +313,8 @@ public class SmsDatabase extends MessagingDatabase { } if (!foundMessage) { - earlyReceiptCache.increment(messageId.getTimetamp(), messageId.getAddress()); + if (deliveryReceipt) earlyDeliveryReceiptCache.increment(messageId.getTimetamp(), messageId.getAddress()); + if (readReceipt) earlyReadReceiptCache.increment(messageId.getTimetamp(), messageId.getAddress()); } } finally { @@ -600,7 +606,8 @@ public class SmsDatabase extends MessagingDatabase { contentValues.put(TYPE, type); contentValues.put(SUBSCRIPTION_ID, message.getSubscriptionId()); contentValues.put(EXPIRES_IN, message.getExpiresIn()); - contentValues.put(RECEIPT_COUNT, earlyReceiptCache.remove(date, address)); + contentValues.put(DELIVERY_RECEIPT_COUNT, earlyDeliveryReceiptCache.remove(date, address)); + contentValues.put(READ_RECEIPT_COUNT, earlyReadReceiptCache.remove(date, address)); SQLiteDatabase db = databaseHelper.getWritableDatabase(); long messageId = db.insert(TABLE_NAME, ADDRESS, contentValues); @@ -789,7 +796,7 @@ public class SmsDatabase extends MessagingDatabase { 0, message.isSecureMessage() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(), threadId, 0, new LinkedList(), message.getSubscriptionId(), message.getExpiresIn(), - System.currentTimeMillis()); + System.currentTimeMillis(), 0); } } @@ -814,19 +821,24 @@ public class SmsDatabase extends MessagingDatabase { } public SmsMessageRecord getCurrent() { - long messageId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID)); - Address address = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS))); - int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS_DEVICE_ID)); - long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE)); - long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.NORMALIZED_DATE_RECEIVED)); - long dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.NORMALIZED_DATE_SENT)); - long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.THREAD_ID)); - int status = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.STATUS)); - int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.RECEIPT_COUNT)); - String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.MISMATCHED_IDENTITIES)); - int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.SUBSCRIPTION_ID)); - long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.EXPIRES_IN)); - long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.EXPIRE_STARTED)); + long messageId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID)); + Address address = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS))); + int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS_DEVICE_ID)); + long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE)); + long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.NORMALIZED_DATE_RECEIVED)); + long dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.NORMALIZED_DATE_SENT)); + long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.THREAD_ID)); + int status = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.STATUS)); + int deliveryReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.DELIVERY_RECEIPT_COUNT)); + int readReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.READ_RECEIPT_COUNT)); + String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.MISMATCHED_IDENTITIES)); + int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.SUBSCRIPTION_ID)); + long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.EXPIRES_IN)); + long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.EXPIRE_STARTED)); + + if (!TextSecurePreferences.isReadReceiptsEnabled(context)) { + readReceiptCount = 0; + } List mismatches = getMismatches(mismatchDocument); Recipient recipient = Recipient.from(context, address, true); @@ -835,9 +847,9 @@ public class SmsDatabase extends MessagingDatabase { return new SmsMessageRecord(context, messageId, body, recipient, recipient, addressDeviceId, - dateSent, dateReceived, receiptCount, type, + dateSent, dateReceived, deliveryReceiptCount, type, threadId, status, mismatches, subscriptionId, - expiresIn, expireStarted); + expiresIn, expireStarted, readReceiptCount); } private List getMismatches(String document) { diff --git a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java index 5f8c4dd3ec..66a1ebac64 100644 --- a/src/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/src/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -43,6 +43,7 @@ import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.DelimiterUtil; +import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.InvalidMessageException; import org.whispersystems.libsignal.util.Pair; @@ -56,34 +57,36 @@ public class ThreadDatabase extends Database { private static final String TAG = ThreadDatabase.class.getSimpleName(); - static final String TABLE_NAME = "thread"; - public static final String ID = "_id"; - public static final String DATE = "date"; - public static final String MESSAGE_COUNT = "message_count"; - public static final String ADDRESS = "recipient_ids"; - public static final String SNIPPET = "snippet"; - private static final String SNIPPET_CHARSET = "snippet_cs"; - public static final String READ = "read"; - public static final String TYPE = "type"; - private static final String ERROR = "error"; - public static final String SNIPPET_TYPE = "snippet_type"; - public static final String SNIPPET_URI = "snippet_uri"; - public static final String ARCHIVED = "archived"; - public static final String STATUS = "status"; - public static final String RECEIPT_COUNT = "delivery_receipt_count"; - public static final String EXPIRES_IN = "expires_in"; - public static final String LAST_SEEN = "last_seen"; - private static final String HAS_SENT = "has_sent"; + static final String TABLE_NAME = "thread"; + public static final String ID = "_id"; + public static final String DATE = "date"; + public static final String MESSAGE_COUNT = "message_count"; + public static final String ADDRESS = "recipient_ids"; + public static final String SNIPPET = "snippet"; + private static final String SNIPPET_CHARSET = "snippet_cs"; + public static final String READ = "read"; + public static final String TYPE = "type"; + private static final String ERROR = "error"; + public static final String SNIPPET_TYPE = "snippet_type"; + public static final String SNIPPET_URI = "snippet_uri"; + public static final String ARCHIVED = "archived"; + public static final String STATUS = "status"; + public static final String DELIVERY_RECEIPT_COUNT = "delivery_receipt_count"; + public static final String READ_RECEIPT_COUNT = "read_receipt_count"; + public static final String EXPIRES_IN = "expires_in"; + public static final String LAST_SEEN = "last_seen"; + private static final String HAS_SENT = "has_sent"; public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " + DATE + " INTEGER DEFAULT 0, " + - MESSAGE_COUNT + " INTEGER DEFAULT 0, " + ADDRESS + " TEXT, " + SNIPPET + " TEXT, " + + MESSAGE_COUNT + " INTEGER DEFAULT 0, " + ADDRESS + " TEXT, " + SNIPPET + " TEXT, " + SNIPPET_CHARSET + " INTEGER DEFAULT 0, " + READ + " INTEGER DEFAULT 1, " + TYPE + " INTEGER DEFAULT 0, " + ERROR + " INTEGER DEFAULT 0, " + SNIPPET_TYPE + " INTEGER DEFAULT 0, " + SNIPPET_URI + " TEXT DEFAULT NULL, " + ARCHIVED + " INTEGER DEFAULT 0, " + STATUS + " INTEGER DEFAULT 0, " + - RECEIPT_COUNT + " INTEGER DEFAULT 0, " + EXPIRES_IN + " INTEGER DEFAULT 0, " + - LAST_SEEN + " INTEGER DEFAULT 0, " + HAS_SENT + " INTEGER DEFAULT 0);"; + DELIVERY_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + EXPIRES_IN + " INTEGER DEFAULT 0, " + + LAST_SEEN + " INTEGER DEFAULT 0, " + HAS_SENT + " INTEGER DEFAULT 0, " + + READ_RECEIPT_COUNT + " INTEGER DEFAULT 0);"; public static final String[] CREATE_INDEXS = { "CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + ADDRESS + ");", @@ -92,7 +95,7 @@ public class ThreadDatabase extends Database { private static final String[] THREAD_PROJECTION = { ID, DATE, MESSAGE_COUNT, ADDRESS, SNIPPET, SNIPPET_CHARSET, READ, TYPE, ERROR, SNIPPET_TYPE, - SNIPPET_URI, ARCHIVED, STATUS, RECEIPT_COUNT, EXPIRES_IN, LAST_SEEN + SNIPPET_URI, ARCHIVED, STATUS, DELIVERY_RECEIPT_COUNT, EXPIRES_IN, LAST_SEEN, READ_RECEIPT_COUNT }; private static final List TYPED_THREAD_PROJECTION = Stream.of(THREAD_PROJECTION) @@ -125,8 +128,8 @@ public class ThreadDatabase extends Database { } private void updateThread(long threadId, long count, String body, @Nullable Uri attachment, - long date, int status, int receiptCount, long type, boolean unarchive, - long expiresIn) + long date, int status, int deliveryReceiptCount, long type, boolean unarchive, + long expiresIn, int readReceiptCount) { ContentValues contentValues = new ContentValues(7); contentValues.put(DATE, date - date % 1000); @@ -135,7 +138,8 @@ public class ThreadDatabase extends Database { contentValues.put(SNIPPET_URI, attachment == null ? null : attachment.toString()); contentValues.put(SNIPPET_TYPE, type); contentValues.put(STATUS, status); - contentValues.put(RECEIPT_COUNT, receiptCount); + contentValues.put(DELIVERY_RECEIPT_COUNT, deliveryReceiptCount); + contentValues.put(READ_RECEIPT_COUNT, readReceiptCount); contentValues.put(EXPIRES_IN, expiresIn); if (unarchive) { @@ -550,8 +554,8 @@ public class ThreadDatabase extends Database { if (reader != null && (record = reader.getNext()) != null) { updateThread(threadId, count, record.getBody().getBody(), getAttachmentUriFor(record), - record.getTimestamp(), record.getDeliveryStatus(), record.getReceiptCount(), - record.getType(), unarchive, record.getExpiresIn()); + record.getTimestamp(), record.getDeliveryStatus(), record.getDeliveryReceiptCount(), + record.getType(), unarchive, record.getExpiresIn(), record.getReadReceiptCount()); notifyConversationListListeners(); return false; } else { @@ -633,22 +637,27 @@ public class ThreadDatabase extends Database { groupRecord = Optional.absent(); } - Recipient recipient = Recipient.from(context, address, settings, groupRecord, true); - DisplayRecord.Body body = getPlaintextBody(cursor); - long date = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.DATE)); - long count = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.MESSAGE_COUNT)); - long read = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.READ)); - long type = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_TYPE)); - boolean archived = cursor.getInt(cursor.getColumnIndex(ThreadDatabase.ARCHIVED)) != 0; - int status = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.STATUS)); - int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.RECEIPT_COUNT)); - long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.EXPIRES_IN)); - long lastSeen = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.LAST_SEEN)); - Uri snippetUri = getSnippetUri(cursor); + Recipient recipient = Recipient.from(context, address, settings, groupRecord, true); + DisplayRecord.Body body = getPlaintextBody(cursor); + long date = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.DATE)); + long count = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.MESSAGE_COUNT)); + long read = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.READ)); + long type = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_TYPE)); + boolean archived = cursor.getInt(cursor.getColumnIndex(ThreadDatabase.ARCHIVED)) != 0; + int status = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.STATUS)); + int deliveryReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.DELIVERY_RECEIPT_COUNT)); + int readReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.READ_RECEIPT_COUNT)); + long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.EXPIRES_IN)); + long lastSeen = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.LAST_SEEN)); + Uri snippetUri = getSnippetUri(cursor); + + if (!TextSecurePreferences.isReadReceiptsEnabled(context)) { + readReceiptCount = 0; + } return new ThreadRecord(context, body, snippetUri, recipient, date, count, read == 1, - threadId, receiptCount, status, type, distributionType, archived, - expiresIn, lastSeen); + threadId, deliveryReceiptCount, status, type, distributionType, archived, + expiresIn, lastSeen, readReceiptCount); } private DisplayRecord.Body getPlaintextBody(Cursor cursor) { diff --git a/src/org/thoughtcrime/securesms/database/loaders/ConversationListLoader.java b/src/org/thoughtcrime/securesms/database/loaders/ConversationListLoader.java index 30b29ce76c..1d527fd948 100644 --- a/src/org/thoughtcrime/securesms/database/loaders/ConversationListLoader.java +++ b/src/org/thoughtcrime/securesms/database/loaders/ConversationListLoader.java @@ -44,12 +44,12 @@ public class ConversationListLoader extends AbstractCursorLoader { ThreadDatabase.ID, ThreadDatabase.DATE, ThreadDatabase.MESSAGE_COUNT, ThreadDatabase.ADDRESS, ThreadDatabase.SNIPPET, ThreadDatabase.READ, ThreadDatabase.TYPE, ThreadDatabase.SNIPPET_TYPE, ThreadDatabase.SNIPPET_URI, - ThreadDatabase.ARCHIVED, ThreadDatabase.STATUS, ThreadDatabase.RECEIPT_COUNT, - ThreadDatabase.EXPIRES_IN, ThreadDatabase.LAST_SEEN}, 1); + ThreadDatabase.ARCHIVED, ThreadDatabase.STATUS, ThreadDatabase.DELIVERY_RECEIPT_COUNT, + ThreadDatabase.EXPIRES_IN, ThreadDatabase.LAST_SEEN, ThreadDatabase.READ_RECEIPT_COUNT}, 1); switchToArchiveCursor.addRow(new Object[] {-1L, System.currentTimeMillis(), archivedCount, "-1", null, 1, ThreadDatabase.DistributionTypes.ARCHIVE, - 0, null, 0, -1, 0, 0, 0}); + 0, null, 0, -1, 0, 0, 0, -1}); cursorList.add(switchToArchiveCursor); } diff --git a/src/org/thoughtcrime/securesms/database/model/DisplayRecord.java b/src/org/thoughtcrime/securesms/database/model/DisplayRecord.java index 803fc6104d..b5bad1f4b1 100644 --- a/src/org/thoughtcrime/securesms/database/model/DisplayRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/DisplayRecord.java @@ -42,10 +42,12 @@ public abstract class DisplayRecord { private final long threadId; private final Body body; private final int deliveryStatus; - private final int receiptCount; + private final int deliveryReceiptCount; + private final int readReceiptCount; public DisplayRecord(Context context, Body body, Recipient recipient, long dateSent, - long dateReceived, long threadId, int deliveryStatus, int receiptCount, long type) + long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount, + long type, int readReceiptCount) { this.context = context.getApplicationContext(); this.threadId = threadId; @@ -54,7 +56,8 @@ public abstract class DisplayRecord { this.dateReceived = dateReceived; this.type = type; this.body = body; - this.receiptCount = receiptCount; + this.deliveryReceiptCount = deliveryReceiptCount; + this.readReceiptCount = readReceiptCount; this.deliveryStatus = deliveryStatus; } @@ -145,13 +148,21 @@ public abstract class DisplayRecord { return deliveryStatus; } - public int getReceiptCount() { - return receiptCount; + public int getDeliveryReceiptCount() { + return deliveryReceiptCount; + } + + public int getReadReceiptCount() { + return readReceiptCount; } public boolean isDelivered() { return (deliveryStatus >= SmsDatabase.Status.STATUS_COMPLETE && - deliveryStatus < SmsDatabase.Status.STATUS_PENDING) || receiptCount > 0; + deliveryStatus < SmsDatabase.Status.STATUS_PENDING) || deliveryReceiptCount > 0; + } + + public boolean isRemoteRead() { + return readReceiptCount > 0; } public boolean isPendingInsecureSmsFallback() { diff --git a/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java index 4b126753ce..2888e1ab72 100644 --- a/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java @@ -46,17 +46,17 @@ public class MediaMmsMessageRecord extends MmsMessageRecord { public MediaMmsMessageRecord(Context context, long id, Recipient conversationRecipient, Recipient individualRecipient, int recipientDeviceId, - long dateSent, long dateReceived, int receiptCount, + long dateSent, long dateReceived, int deliveryReceiptCount, long threadId, Body body, @NonNull SlideDeck slideDeck, int partCount, long mailbox, List mismatches, List failures, int subscriptionId, - long expiresIn, long expireStarted) + long expiresIn, long expireStarted, int readReceiptCount) { super(context, id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent, - dateReceived, threadId, Status.STATUS_NONE, receiptCount, mailbox, mismatches, failures, - subscriptionId, expiresIn, expireStarted, slideDeck); + dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures, + subscriptionId, expiresIn, expireStarted, slideDeck, readReceiptCount); this.context = context.getApplicationContext(); this.partCount = partCount; diff --git a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java index 8906c9297f..d1707cbe1c 100644 --- a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -57,13 +57,14 @@ public abstract class MessageRecord extends DisplayRecord { MessageRecord(Context context, long id, Body body, Recipient conversationRecipient, Recipient individualRecipient, int recipientDeviceId, long dateSent, long dateReceived, long threadId, - int deliveryStatus, int receiptCount, long type, + int deliveryStatus, int deliveryReceiptCount, long type, List mismatches, List networkFailures, - int subscriptionId, long expiresIn, long expireStarted) + int subscriptionId, long expiresIn, long expireStarted, + int readReceiptCount) { - super(context, body, conversationRecipient, dateSent, dateReceived, threadId, deliveryStatus, receiptCount, - type); + super(context, body, conversationRecipient, dateSent, dateReceived, + threadId, deliveryStatus, deliveryReceiptCount, type, readReceiptCount); this.id = id; this.individualRecipient = individualRecipient; this.recipientDeviceId = recipientDeviceId; diff --git a/src/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java index 5c16f6022c..94fccc3c1a 100644 --- a/src/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java @@ -18,12 +18,12 @@ public abstract class MmsMessageRecord extends MessageRecord { MmsMessageRecord(Context context, long id, Body body, Recipient conversationRecipient, Recipient individualRecipient, int recipientDeviceId, long dateSent, - long dateReceived, long threadId, int deliveryStatus, int receiptCount, + long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount, long type, List mismatches, List networkFailures, int subscriptionId, long expiresIn, - long expireStarted, @NonNull SlideDeck slideDeck) + long expireStarted, @NonNull SlideDeck slideDeck, int readReceiptCount) { - super(context, id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent, dateReceived, threadId, deliveryStatus, receiptCount, type, mismatches, networkFailures, subscriptionId, expiresIn, expireStarted); + super(context, id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, mismatches, networkFailures, subscriptionId, expiresIn, expireStarted, readReceiptCount); this.slideDeck = slideDeck; } diff --git a/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java index 8a63c1cf0b..be67d3d297 100644 --- a/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java @@ -47,15 +47,15 @@ public class NotificationMmsMessageRecord extends MmsMessageRecord { public NotificationMmsMessageRecord(Context context, long id, Recipient conversationRecipient, Recipient individualRecipient, int recipientDeviceId, - long dateSent, long dateReceived, int receiptCount, + 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 subscriptionId, SlideDeck slideDeck, int readReceiptCount) { super(context, id, new Body("", true), conversationRecipient, individualRecipient, recipientDeviceId, - dateSent, dateReceived, threadId, Status.STATUS_NONE, receiptCount, mailbox, + dateSent, dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, new LinkedList(), new LinkedList(), subscriptionId, - 0, 0, slideDeck); + 0, 0, slideDeck, readReceiptCount); this.contentLocation = contentLocation; this.messageSize = messageSize; diff --git a/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java index e92ceef601..8dc52063e9 100644 --- a/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java @@ -44,15 +44,16 @@ public class SmsMessageRecord extends MessageRecord { Recipient individualRecipient, int recipientDeviceId, long dateSent, long dateReceived, - int receiptCount, + int deliveryReceiptCount, long type, long threadId, int status, List mismatches, - int subscriptionId, long expiresIn, long expireStarted) + int subscriptionId, long expiresIn, long expireStarted, + int readReceiptCount) { super(context, id, body, recipient, individualRecipient, recipientDeviceId, - dateSent, dateReceived, threadId, status, receiptCount, type, + dateSent, dateReceived, threadId, status, deliveryReceiptCount, type, mismatches, new LinkedList(), subscriptionId, - expiresIn, expireStarted); + expiresIn, expireStarted, readReceiptCount); } public long getType() { diff --git a/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java index aac177c28c..8ea53a899a 100644 --- a/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -50,10 +50,11 @@ public class ThreadRecord extends DisplayRecord { public ThreadRecord(@NonNull Context context, @NonNull Body body, @Nullable Uri snippetUri, @NonNull Recipient recipient, long date, long count, boolean read, - long threadId, int receiptCount, int status, long snippetType, - int distributionType, boolean archived, long expiresIn, long lastSeen) + long threadId, int deliveryReceiptCount, int status, long snippetType, + int distributionType, boolean archived, long expiresIn, long lastSeen, + int readReceiptCount) { - super(context, body, recipient, date, date, threadId, status, receiptCount, snippetType); + super(context, body, recipient, date, date, threadId, status, deliveryReceiptCount, snippetType, readReceiptCount); this.context = context.getApplicationContext(); this.snippetUri = snippetUri; this.count = count; diff --git a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java index 2271bffb18..b32f7c02b8 100644 --- a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java +++ b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java @@ -10,7 +10,6 @@ import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob; import org.thoughtcrime.securesms.jobs.AvatarDownloadJob; import org.thoughtcrime.securesms.jobs.CleanPreKeysJob; import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob; -import org.thoughtcrime.securesms.jobs.DeliveryReceiptJob; import org.thoughtcrime.securesms.jobs.GcmRefreshJob; import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob; @@ -29,6 +28,7 @@ import org.thoughtcrime.securesms.jobs.RequestGroupInfoJob; import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob; import org.thoughtcrime.securesms.jobs.RetrieveProfileJob; import org.thoughtcrime.securesms.jobs.RotateSignedPreKeyJob; +import org.thoughtcrime.securesms.jobs.SendReadReceiptJob; import org.thoughtcrime.securesms.push.SecurityEventListener; import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess; import org.thoughtcrime.securesms.service.MessageRetrievalService; @@ -45,7 +45,6 @@ import dagger.Provides; @Module(complete = false, injects = {CleanPreKeysJob.class, CreateSignedPreKeyJob.class, - DeliveryReceiptJob.class, PushGroupSendJob.class, PushTextSendJob.class, PushMediaSendJob.class, @@ -69,48 +68,56 @@ import dagger.Provides; MultiDeviceVerifiedUpdateJob.class, CreateProfileActivity.class, RetrieveProfileAvatarJob.class, - MultiDeviceProfileKeyUpdateJob.class}) + MultiDeviceProfileKeyUpdateJob.class, + SendReadReceiptJob.class}) public class SignalCommunicationModule { - private final Context context; - private final SignalServiceNetworkAccess networkAccess; + private final Context context; + private final SignalServiceNetworkAccess networkAccess; + + private SignalServiceAccountManager accountManager; + private SignalServiceMessageSender messageSender; + private SignalServiceMessageReceiver messageReceiver; public SignalCommunicationModule(Context context, SignalServiceNetworkAccess networkAccess) { this.context = context; this.networkAccess = networkAccess; } - @Provides SignalServiceAccountManager provideSignalAccountManager() { - return new SignalServiceAccountManager(networkAccess.getConfiguration(context), - TextSecurePreferences.getLocalNumber(context), - TextSecurePreferences.getPushServerPassword(context), - BuildConfig.USER_AGENT); + @Provides + synchronized SignalServiceAccountManager provideSignalAccountManager() { + if (this.accountManager == null) { + this.accountManager = new SignalServiceAccountManager(networkAccess.getConfiguration(context), + new DynamicCredentialsProvider(context), + BuildConfig.USER_AGENT); + } + + return this.accountManager; } @Provides - SignalMessageSenderFactory provideSignalMessageSenderFactory() { - return new SignalMessageSenderFactory() { - @Override - public SignalServiceMessageSender create() { - return new SignalServiceMessageSender(networkAccess.getConfiguration(context), - TextSecurePreferences.getLocalNumber(context), - TextSecurePreferences.getPushServerPassword(context), - new SignalProtocolStoreImpl(context), - BuildConfig.USER_AGENT, - Optional.fromNullable(MessageRetrievalService.getPipe()), - Optional.of(new SecurityEventListener(context))); - } - }; + synchronized SignalServiceMessageSender provideSignalMessageSender() { + if (this.messageSender == null) { + this.messageSender = new SignalServiceMessageSender(networkAccess.getConfiguration(context), + new DynamicCredentialsProvider(context), + new SignalProtocolStoreImpl(context), + BuildConfig.USER_AGENT, + Optional.fromNullable(MessageRetrievalService.getPipe()), + Optional.of(new SecurityEventListener(context))); + } + + return this.messageSender; } - @Provides SignalServiceMessageReceiver provideSignalMessageReceiver() { - return new SignalServiceMessageReceiver(networkAccess.getConfiguration(context), - new DynamicCredentialsProvider(context), - BuildConfig.USER_AGENT); - } + @Provides + synchronized SignalServiceMessageReceiver provideSignalMessageReceiver() { + if (this.messageReceiver == null) { + this.messageReceiver = new SignalServiceMessageReceiver(networkAccess.getConfiguration(context), + new DynamicCredentialsProvider(context), + BuildConfig.USER_AGENT); + } - public static interface SignalMessageSenderFactory { - public SignalServiceMessageSender create(); + return this.messageReceiver; } private static class DynamicCredentialsProvider implements CredentialsProvider { diff --git a/src/org/thoughtcrime/securesms/gcm/GcmBroadcastReceiver.java b/src/org/thoughtcrime/securesms/gcm/GcmBroadcastReceiver.java index ac54bafd44..32c433b4f7 100644 --- a/src/org/thoughtcrime/securesms/gcm/GcmBroadcastReceiver.java +++ b/src/org/thoughtcrime/securesms/gcm/GcmBroadcastReceiver.java @@ -30,11 +30,9 @@ public class GcmBroadcastReceiver extends WakefulBroadcastReceiver { return; } - String messageData = intent.getStringExtra("message"); String receiptData = intent.getStringExtra("receipt"); - if (!TextUtils.isEmpty(messageData)) handleReceivedMessage(context, messageData); - else if (!TextUtils.isEmpty(receiptData)) handleReceivedMessage(context, receiptData); + if (!TextUtils.isEmpty(receiptData)) handleReceivedMessage(context, receiptData); else if (intent.hasExtra("notification")) handleReceivedNotification(context); } } diff --git a/src/org/thoughtcrime/securesms/jobs/DeliveryReceiptJob.java b/src/org/thoughtcrime/securesms/jobs/DeliveryReceiptJob.java deleted file mode 100644 index 066ba56dae..0000000000 --- a/src/org/thoughtcrime/securesms/jobs/DeliveryReceiptJob.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - -import android.content.Context; -import android.util.Log; - - -import org.thoughtcrime.securesms.dependencies.InjectableType; -import org.whispersystems.jobqueue.JobParameters; -import org.whispersystems.jobqueue.requirements.NetworkRequirement; -import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.api.SignalServiceMessageSender; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; -import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; - -import java.io.IOException; - -import javax.inject.Inject; - -import static org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory; - -public class DeliveryReceiptJob extends ContextJob implements InjectableType { - - private static final long serialVersionUID = 1L; - - private static final String TAG = DeliveryReceiptJob.class.getSimpleName(); - - @Inject transient SignalMessageSenderFactory messageSenderFactory; - - private final String destination; - private final long timestamp; - private final String relay; - - public DeliveryReceiptJob(Context context, String destination, long timestamp, String relay) { - super(context, JobParameters.newBuilder() - .withRequirement(new NetworkRequirement(context)) - .withPersistence() - .withRetryCount(50) - .create()); - - this.destination = destination; - this.timestamp = timestamp; - this.relay = relay; - } - - @Override - public void onAdded() {} - - @Override - public void onRun() throws IOException { - Log.w("DeliveryReceiptJob", "Sending delivery receipt..."); - SignalServiceMessageSender messageSender = messageSenderFactory.create(); - SignalServiceAddress textSecureAddress = new SignalServiceAddress(destination, Optional.fromNullable(relay)); - - messageSender.sendDeliveryReceipt(textSecureAddress, timestamp); - } - - @Override - public void onCanceled() { - Log.w(TAG, "Failed to send receipt after retry exhausted!"); - } - - @Override - public boolean onShouldRetry(Exception exception) { - Log.w(TAG, exception); - if (exception instanceof NonSuccessfulResponseCodeException) return false; - if (exception instanceof PushNetworkException) return true; - - return false; - } -} diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java index 4dd86760e7..e4e6bd538d 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java @@ -7,7 +7,6 @@ import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase.BlockedReader; import org.thoughtcrime.securesms.dependencies.InjectableType; -import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.recipients.Recipient; import org.whispersystems.jobqueue.JobParameters; @@ -30,7 +29,7 @@ public class MultiDeviceBlockedUpdateJob extends MasterSecretJob implements Inje private static final String TAG = MultiDeviceBlockedUpdateJob.class.getSimpleName(); - @Inject transient SignalMessageSenderFactory messageSenderFactory; + @Inject transient SignalServiceMessageSender messageSender; public MultiDeviceBlockedUpdateJob(Context context) { super(context, JobParameters.newBuilder() @@ -45,10 +44,9 @@ public class MultiDeviceBlockedUpdateJob extends MasterSecretJob implements Inje public void onRun(MasterSecret masterSecret) throws IOException, UntrustedIdentityException { - RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context); - SignalServiceMessageSender messageSender = messageSenderFactory.create(); - BlockedReader reader = database.readerForBlocked(database.getBlocked()); - List blocked = new LinkedList<>(); + RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context); + BlockedReader reader = database.readerForBlocked(database.getBlocked()); + List blocked = new LinkedList<>(); Recipient recipient; diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java index f3ea85cb1d..6e4bfa9ad8 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java @@ -18,7 +18,6 @@ import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.dependencies.InjectableType; -import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.Base64; @@ -54,7 +53,7 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje private static final String TAG = MultiDeviceContactUpdateJob.class.getSimpleName(); - @Inject transient SignalMessageSenderFactory messageSenderFactory; + @Inject transient SignalServiceMessageSender messageSender; private final @Nullable String address; @@ -90,8 +89,7 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje private void generateSingleContactUpdate(@NonNull Address address) throws IOException, UntrustedIdentityException, NetworkException { - SignalServiceMessageSender messageSender = messageSenderFactory.create(); - File contactDataFile = createTempFile("multidevice-contact-update"); + File contactDataFile = createTempFile("multidevice-contact-update"); try { DeviceContactsOutputStream out = new DeviceContactsOutputStream(new FileOutputStream(contactDataFile)); @@ -119,8 +117,7 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje private void generateFullContactUpdate() throws IOException, UntrustedIdentityException, NetworkException { - SignalServiceMessageSender messageSender = messageSenderFactory.create(); - File contactDataFile = createTempFile("multidevice-contact-update"); + File contactDataFile = createTempFile("multidevice-contact-update"); try { DeviceContactsOutputStream out = new DeviceContactsOutputStream(new FileOutputStream(contactDataFile)); diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java index 5a48e76387..8265432fd6 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java @@ -9,7 +9,6 @@ import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.dependencies.InjectableType; -import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.requirements.NetworkRequirement; @@ -38,8 +37,7 @@ public class MultiDeviceGroupUpdateJob extends MasterSecretJob implements Inject private static final long serialVersionUID = 1L; private static final String TAG = MultiDeviceGroupUpdateJob.class.getSimpleName(); - @Inject - transient SignalCommunicationModule.SignalMessageSenderFactory messageSenderFactory; + @Inject transient SignalServiceMessageSender messageSender; public MultiDeviceGroupUpdateJob(Context context) { super(context, JobParameters.newBuilder() @@ -52,9 +50,8 @@ public class MultiDeviceGroupUpdateJob extends MasterSecretJob implements Inject @Override public void onRun(MasterSecret masterSecret) throws Exception { - SignalServiceMessageSender messageSender = messageSenderFactory.create(); - File contactDataFile = createTempFile("multidevice-contact-update"); - GroupDatabase.Reader reader = null; + File contactDataFile = createTempFile("multidevice-contact-update"); + GroupDatabase.Reader reader = null; GroupDatabase.GroupRecord record; diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java index b91aab8da7..091805d1df 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java @@ -7,11 +7,11 @@ import android.util.Log; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.dependencies.InjectableType; -import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.requirements.NetworkRequirement; import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream; @@ -33,7 +33,7 @@ public class MultiDeviceProfileKeyUpdateJob extends MasterSecretJob implements I private static final long serialVersionUID = 1L; private static final String TAG = MultiDeviceProfileKeyUpdateJob.class.getSimpleName(); - @Inject SignalMessageSenderFactory messageSender; + @Inject transient SignalServiceMessageSender messageSender; public MultiDeviceProfileKeyUpdateJob(Context context) { super(context, JobParameters.newBuilder() @@ -71,7 +71,7 @@ public class MultiDeviceProfileKeyUpdateJob extends MasterSecretJob implements I SignalServiceSyncMessage syncMessage = SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream, false)); - messageSender.create().sendMessage(syncMessage); + messageSender.sendMessage(syncMessage); } @Override diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java index 27155cdd6f..5b9a3acc30 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java @@ -6,7 +6,6 @@ import android.util.Log; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.dependencies.InjectableType; -import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.jobqueue.JobParameters; @@ -31,8 +30,7 @@ public class MultiDeviceReadUpdateJob extends MasterSecretJob implements Injecta private final List messageIds; - @Inject - transient SignalCommunicationModule.SignalMessageSenderFactory messageSenderFactory; + @Inject transient SignalServiceMessageSender messageSender; public MultiDeviceReadUpdateJob(Context context, List messageIds) { super(context, JobParameters.newBuilder() @@ -62,7 +60,6 @@ public class MultiDeviceReadUpdateJob extends MasterSecretJob implements Injecta readMessages.add(new ReadMessage(messageId.sender, messageId.timestamp)); } - SignalServiceMessageSender messageSender = messageSenderFactory.create(); messageSender.sendMessage(SignalServiceSyncMessage.forRead(readMessages)); } diff --git a/src/org/thoughtcrime/securesms/jobs/MultiDeviceVerifiedUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/MultiDeviceVerifiedUpdateJob.java index 141b60e3cd..f6ce372968 100644 --- a/src/org/thoughtcrime/securesms/jobs/MultiDeviceVerifiedUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MultiDeviceVerifiedUpdateJob.java @@ -7,7 +7,6 @@ import android.util.Log; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus; import org.thoughtcrime.securesms.dependencies.InjectableType; -import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.requirements.NetworkRequirement; @@ -30,7 +29,7 @@ public class MultiDeviceVerifiedUpdateJob extends ContextJob implements Injectab private static final String TAG = MultiDeviceVerifiedUpdateJob.class.getSimpleName(); @Inject - transient SignalCommunicationModule.SignalMessageSenderFactory messageSenderFactory; + transient SignalServiceMessageSender messageSender; private final String destination; private final byte[] identityKey; @@ -65,7 +64,6 @@ public class MultiDeviceVerifiedUpdateJob extends ContextJob implements Injectab Address canonicalDestination = Address.fromSerialized(destination); VerifiedMessage.VerifiedState verifiedState = getVerifiedState(verifiedStatus); - SignalServiceMessageSender messageSender = messageSenderFactory.create(); VerifiedMessage verifiedMessage = new VerifiedMessage(canonicalDestination.toPhoneString(), new IdentityKey(identityKey, 0), verifiedState, timestamp); messageSender.sendMessage(SignalServiceSyncMessage.forVerified(verifiedMessage)); diff --git a/src/org/thoughtcrime/securesms/jobs/PushContentReceiveJob.java b/src/org/thoughtcrime/securesms/jobs/PushContentReceiveJob.java index 11e41c86ee..512f636235 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushContentReceiveJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushContentReceiveJob.java @@ -39,7 +39,7 @@ public class PushContentReceiveJob extends PushReceivedJob { String sessionKey = TextSecurePreferences.getSignalingKey(context); SignalServiceEnvelope envelope = new SignalServiceEnvelope(data, sessionKey); - handle(envelope, true); + handle(envelope); } catch (IOException | InvalidVersionException e) { Log.w(TAG, e); } diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 11166c6050..8ff3c03745 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -68,6 +68,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.messages.SignalServiceGroup; +import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; import org.whispersystems.signalservice.api.messages.calls.AnswerMessage; import org.whispersystems.signalservice.api.messages.calls.BusyMessage; import org.whispersystems.signalservice.api.messages.calls.HangupMessage; @@ -186,6 +187,11 @@ public class PushDecryptJob extends ContextJob { else if (message.getIceUpdateMessages().isPresent()) handleCallIceUpdateMessage(envelope, message.getIceUpdateMessages().get()); else if (message.getHangupMessage().isPresent()) handleCallHangupMessage(envelope, message.getHangupMessage().get(), smsMessageId); else if (message.getBusyMessage().isPresent()) handleCallBusyMessage(envelope, message.getBusyMessage().get()); + } else if (content.getReceiptMessage().isPresent()) { + SignalServiceReceiptMessage message = content.getReceiptMessage().get(); + + if (message.isReadReceipt()) handleReadReceipt(envelope, message); + else if (message.isDeliveryReceipt()) handleDeliveryReceipt(envelope, message); } else { Log.w(TAG, "Got unrecognized message..."); } @@ -824,6 +830,29 @@ public class PushDecryptJob extends ContextJob { } } + private void handleDeliveryReceipt(@NonNull SignalServiceEnvelope envelope, + @NonNull SignalServiceReceiptMessage message) + { + for (long timestamp : message.getTimestamps()) { + Log.w(TAG, String.format("Received encrypted delivery receipt: (XXXXX, %d)", timestamp)); + DatabaseFactory.getMmsSmsDatabase(context) + .incrementDeliveryReceiptCount(new SyncMessageId(Address.fromExternal(context, envelope.getSource()), timestamp)); + } + } + + private void handleReadReceipt(@NonNull SignalServiceEnvelope envelope, + @NonNull SignalServiceReceiptMessage message) + { + if (TextSecurePreferences.isReadReceiptsEnabled(context)) { + for (long timestamp : message.getTimestamps()) { + Log.w(TAG, String.format("Received encrypted read receipt: (XXXXX, %d)", timestamp)); + + DatabaseFactory.getMmsSmsDatabase(context) + .incrementReadReceiptCount(new SyncMessageId(Address.fromExternal(context, envelope.getSource()), timestamp)); + } + } + } + private Optional insertPlaceholder(@NonNull SignalServiceEnvelope envelope) { EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()), diff --git a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java index 8735a7fdc6..20e07126bb 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java @@ -43,15 +43,13 @@ import java.util.List; import javax.inject.Inject; -import static org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory; - public class PushGroupSendJob extends PushSendJob implements InjectableType { private static final long serialVersionUID = 1L; private static final String TAG = PushGroupSendJob.class.getSimpleName(); - @Inject transient SignalMessageSenderFactory messageSenderFactory; + @Inject transient SignalServiceMessageSender messageSender; private final long messageId; private final long filterRecipientId; // Deprecated @@ -137,7 +135,6 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType { throws IOException, RecipientFormattingException, InvalidNumberException, EncapsulatedExceptions, UndeliverableMessageException { - SignalServiceMessageSender messageSender = messageSenderFactory.create(); String groupId = message.getRecipient().getAddress().toGroupString(); Optional profileKey = getProfileKey(message.getRecipient()); List recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupId, false); diff --git a/src/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java b/src/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java index cfb463b497..d57e8d3ca2 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java @@ -9,7 +9,6 @@ import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import org.thoughtcrime.securesms.dependencies.InjectableType; -import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory; import org.thoughtcrime.securesms.util.GroupUtil; import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.requirements.NetworkRequirement; @@ -37,7 +36,7 @@ public class PushGroupUpdateJob extends ContextJob implements InjectableType { private static final long serialVersionUID = 0L; - @Inject transient SignalMessageSenderFactory messageSenderFactory; + @Inject transient SignalServiceMessageSender messageSender; private final String source; private final byte[] groupId; @@ -59,10 +58,9 @@ public class PushGroupUpdateJob extends ContextJob implements InjectableType { @Override public void onRun() throws IOException, UntrustedIdentityException { - SignalServiceMessageSender messageSender = messageSenderFactory.create(); - GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); - Optional record = groupDatabase.getGroup(GroupUtil.getEncodedId(groupId, false)); - SignalServiceAttachment avatar = null; + GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); + Optional record = groupDatabase.getGroup(GroupUtil.getEncodedId(groupId, false)); + SignalServiceAttachment avatar = null; if (record == null) { Log.w(TAG, "No information for group record info request: " + new String(groupId)); diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java index 0f37cf3709..ddd75dab0b 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java @@ -32,15 +32,13 @@ import java.util.List; import javax.inject.Inject; -import static org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory; - public class PushMediaSendJob extends PushSendJob implements InjectableType { private static final long serialVersionUID = 1L; private static final String TAG = PushMediaSendJob.class.getSimpleName(); - @Inject transient SignalMessageSenderFactory messageSenderFactory; + @Inject transient SignalServiceMessageSender messageSender; private final long messageId; @@ -107,8 +105,6 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { throw new UndeliverableMessageException("No destination address."); } - SignalServiceMessageSender messageSender = messageSenderFactory.create(); - try { SignalServiceAddress address = getPushAddress(message.getRecipient().getAddress()); MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints(); diff --git a/src/org/thoughtcrime/securesms/jobs/PushNotificationReceiveJob.java b/src/org/thoughtcrime/securesms/jobs/PushNotificationReceiveJob.java index e028e44226..ccb0c9a12c 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushNotificationReceiveJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushNotificationReceiveJob.java @@ -4,7 +4,6 @@ import android.content.Context; import android.util.Log; import org.thoughtcrime.securesms.dependencies.InjectableType; -import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.requirements.NetworkRequirement; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; @@ -37,7 +36,7 @@ public class PushNotificationReceiveJob extends PushReceivedJob implements Injec receiver.retrieveMessages(new SignalServiceMessageReceiver.MessageReceivedCallback() { @Override public void onMessage(SignalServiceEnvelope envelope) { - handle(envelope, false); + handle(envelope); } }); } diff --git a/src/org/thoughtcrime/securesms/jobs/PushReceivedJob.java b/src/org/thoughtcrime/securesms/jobs/PushReceivedJob.java index 51734245d9..cee65de3db 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushReceivedJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushReceivedJob.java @@ -9,17 +9,12 @@ import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.database.RecipientDatabase; -import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.service.KeyCachingService; -import org.thoughtcrime.securesms.util.Util; import org.whispersystems.jobqueue.JobManager; import org.whispersystems.jobqueue.JobParameters; -import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; -import java.util.LinkedList; - public abstract class PushReceivedJob extends ContextJob { private static final String TAG = PushReceivedJob.class.getSimpleName(); @@ -28,7 +23,7 @@ public abstract class PushReceivedJob extends ContextJob { super(context, parameters); } - public void handle(SignalServiceEnvelope envelope, boolean sendExplicitReceipt) { + public void handle(SignalServiceEnvelope envelope) { Address source = Address.fromExternal(context, envelope.getSource()); Recipient recipient = Recipient.from(context, source, false); @@ -40,13 +35,13 @@ public abstract class PushReceivedJob extends ContextJob { if (envelope.isReceipt()) { handleReceipt(envelope); } else if (envelope.isPreKeySignalMessage() || envelope.isSignalMessage()) { - handleMessage(envelope, source, sendExplicitReceipt); + handleMessage(envelope, source); } else { Log.w(TAG, "Received envelope of unknown type: " + envelope.getType()); } } - private void handleMessage(SignalServiceEnvelope envelope, Address source, boolean sendExplicitReceipt) { + private void handleMessage(SignalServiceEnvelope envelope, Address source) { Recipient recipients = Recipient.from(context, source, false); JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); @@ -56,12 +51,6 @@ public abstract class PushReceivedJob extends ContextJob { } else { Log.w(TAG, "*** Received blocked push message, ignoring..."); } - - if (sendExplicitReceipt) { - jobManager.add(new DeliveryReceiptJob(context, envelope.getSource(), - envelope.getTimestamp(), - envelope.getRelay())); - } } private void handleReceipt(SignalServiceEnvelope envelope) { diff --git a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java index 2ef512e1f3..011d48951f 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java @@ -27,15 +27,13 @@ import java.io.IOException; import javax.inject.Inject; -import static org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory; - public class PushTextSendJob extends PushSendJob implements InjectableType { private static final long serialVersionUID = 1L; private static final String TAG = PushTextSendJob.class.getSimpleName(); - @Inject transient SignalMessageSenderFactory messageSenderFactory; + @Inject transient SignalServiceMessageSender messageSender; private final long messageId; @@ -101,7 +99,6 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { { try { SignalServiceAddress address = getPushAddress(message.getIndividualRecipient().getAddress()); - SignalServiceMessageSender messageSender = messageSenderFactory.create(); Optional profileKey = getProfileKey(message.getIndividualRecipient()); SignalServiceDataMessage textSecureMessage = SignalServiceDataMessage.newBuilder() .withTimestamp(message.getDateSent()) diff --git a/src/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java b/src/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java index a2679d4ebd..fe1b568afe 100644 --- a/src/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java +++ b/src/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java @@ -4,7 +4,6 @@ import android.content.Context; import android.support.annotation.NonNull; import org.thoughtcrime.securesms.dependencies.InjectableType; -import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory; import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.requirements.NetworkRequirement; import org.whispersystems.signalservice.api.SignalServiceMessageSender; @@ -25,7 +24,7 @@ public class RequestGroupInfoJob extends ContextJob implements InjectableType { private static final long serialVersionUID = 0L; - @Inject transient SignalMessageSenderFactory messageSenderFactory; + @Inject transient SignalServiceMessageSender messageSender; private final String source; private final byte[] groupId; @@ -46,16 +45,14 @@ public class RequestGroupInfoJob extends ContextJob implements InjectableType { @Override public void onRun() throws IOException, UntrustedIdentityException { - SignalServiceMessageSender messageSender = messageSenderFactory.create(); + SignalServiceGroup group = SignalServiceGroup.newBuilder(Type.REQUEST_INFO) + .withId(groupId) + .build(); - SignalServiceGroup group = SignalServiceGroup.newBuilder(Type.REQUEST_INFO) - .withId(groupId) - .build(); - - SignalServiceDataMessage message = SignalServiceDataMessage.newBuilder() - .asGroupMessage(group) - .withTimestamp(System.currentTimeMillis()) - .build(); + SignalServiceDataMessage message = SignalServiceDataMessage.newBuilder() + .asGroupMessage(group) + .withTimestamp(System.currentTimeMillis()) + .build(); messageSender.sendMessage(new SignalServiceAddress(source), message); } diff --git a/src/org/thoughtcrime/securesms/jobs/SendReadReceiptJob.java b/src/org/thoughtcrime/securesms/jobs/SendReadReceiptJob.java new file mode 100644 index 0000000000..abbeb68a13 --- /dev/null +++ b/src/org/thoughtcrime/securesms/jobs/SendReadReceiptJob.java @@ -0,0 +1,69 @@ +package org.thoughtcrime.securesms.jobs; + + +import android.content.Context; +import android.util.Log; + +import org.thoughtcrime.securesms.database.Address; +import org.thoughtcrime.securesms.dependencies.InjectableType; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.jobqueue.JobParameters; +import org.whispersystems.jobqueue.requirements.NetworkRequirement; +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.List; + +import javax.inject.Inject; + +public class SendReadReceiptJob extends ContextJob implements InjectableType { + + private static final long serialVersionUID = 1L; + + private static final String TAG = SendReadReceiptJob.class.getSimpleName(); + + @Inject transient SignalServiceMessageSender messageSender; + + private final String address; + private final List messageIds; + private final long timestamp; + + public SendReadReceiptJob(Context context, Address address, List messageIds) { + super(context, JobParameters.newBuilder() + .withRequirement(new NetworkRequirement(context)) + .withPersistence() + .create()); + + this.address = address.serialize(); + this.messageIds = messageIds; + this.timestamp = System.currentTimeMillis(); + } + + @Override + public void onAdded() {} + + @Override + public void onRun() throws IOException, UntrustedIdentityException { + if (!TextSecurePreferences.isReadReceiptsEnabled(context)) return; + + SignalServiceAddress remoteAddress = new SignalServiceAddress(address); + SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ, messageIds, timestamp); + + messageSender.sendReceipt(remoteAddress, receiptMessage); + } + + @Override + public boolean onShouldRetry(Exception e) { + if (e instanceof PushNetworkException) return true; + return false; + } + + @Override + public void onCanceled() { + Log.w(TAG, "Failed to send read receipts to: " + address); + } +} diff --git a/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java b/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java index 1d7d8cdede..bbc3401a12 100644 --- a/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java +++ b/src/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java @@ -8,17 +8,23 @@ import android.support.annotation.Nullable; import android.support.v4.app.NotificationManagerCompat; import android.util.Log; +import com.annimon.stream.Collectors; +import com.annimon.stream.Stream; + import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessagingDatabase.ExpirationInfo; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob; +import org.thoughtcrime.securesms.jobs.SendReadReceiptJob; import org.thoughtcrime.securesms.service.ExpiringMessageManager; import java.util.LinkedList; import java.util.List; +import java.util.Map; public class MarkReadReceiver extends MasterSecretBroadcastReceiver { @@ -72,6 +78,18 @@ public class MarkReadReceiver extends MasterSecretBroadcastReceiver { ApplicationContext.getInstance(context) .getJobManager() .add(new MultiDeviceReadUpdateJob(context, syncMessageIds)); + + Map> addressMap = Stream.of(markedReadMessages) + .map(MarkedMessageInfo::getSyncMessageId) + .collect(Collectors.groupingBy(SyncMessageId::getAddress)); + + for (Address address : addressMap.keySet()) { + List timestamps = Stream.of(addressMap.get(address)).map(SyncMessageId::getTimetamp).toList(); + + ApplicationContext.getInstance(context) + .getJobManager() + .add(new SendReadReceiptJob(context, address, timestamps)); + } } private static void scheduleDeletion(Context context, ExpirationInfo expirationInfo) { diff --git a/src/org/thoughtcrime/securesms/service/MessageRetrievalService.java b/src/org/thoughtcrime/securesms/service/MessageRetrievalService.java index 51698d8df9..fb4d914cf6 100644 --- a/src/org/thoughtcrime/securesms/service/MessageRetrievalService.java +++ b/src/org/thoughtcrime/securesms/service/MessageRetrievalService.java @@ -214,7 +214,7 @@ public class MessageRetrievalService extends Service implements InjectableType, Log.w(TAG, "Retrieved envelope! " + envelope.getSource()); PushContentReceiveJob receiveJob = new PushContentReceiveJob(MessageRetrievalService.this); - receiveJob.handle(envelope, false); + receiveJob.handle(envelope); decrementPushReceived(); } diff --git a/src/org/thoughtcrime/securesms/service/WebRtcCallService.java b/src/org/thoughtcrime/securesms/service/WebRtcCallService.java index 39e4c3ccc0..246083a7d3 100644 --- a/src/org/thoughtcrime/securesms/service/WebRtcCallService.java +++ b/src/org/thoughtcrime/securesms/service/WebRtcCallService.java @@ -29,7 +29,6 @@ import org.thoughtcrime.securesms.contacts.ContactAccessor; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.dependencies.InjectableType; -import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory; import org.thoughtcrime.securesms.events.WebRtcViewModel; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.Recipient; @@ -148,10 +147,9 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo private boolean remoteVideoEnabled = false; private boolean bluetoothAvailable = false; - @Inject public SignalMessageSenderFactory messageSenderFactory; + @Inject public SignalServiceMessageSender messageSender; @Inject public SignalServiceAccountManager accountManager; - private SignalServiceMessageSender messageSender; private PeerConnectionFactory peerConnectionFactory; private SignalAudioManager audioManager; private BluetoothStateManager bluetoothStateManager; @@ -271,7 +269,6 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo this.peerConnectionFactory = new PeerConnectionFactory(new PeerConnectionFactoryOptions()); this.audioManager = new SignalAudioManager(this); this.bluetoothStateManager = new BluetoothStateManager(this, this); - this.messageSender = messageSenderFactory.create(); this.messageSender.setSoTimeoutMillis(TimeUnit.SECONDS.toMillis(10)); this.accountManager.setSoTimeoutMillis(TimeUnit.SECONDS.toMillis(10)); } diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java index 4efce0b15c..7149b7bee3 100644 --- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -113,6 +113,15 @@ public class TextSecurePreferences { private static final String ALWAYS_RELAY_CALLS_PREF = "pref_turn_only"; private static final String PROFILE_KEY_PREF = "pref_profile_key"; private static final String PROFILE_NAME_PREF = "pref_profile_name"; + private static final String READ_RECEIPTS_PREF = "pref_read_receipts"; + + public static boolean isReadReceiptsEnabled(Context context) { + return getBooleanPreference(context, READ_RECEIPTS_PREF, false); + } + + public static void setReadReceiptsEnabled(Context context, boolean enabled) { + setBooleanPreference(context, READ_RECEIPTS_PREF, enabled); + } public static @Nullable String getProfileKey(Context context) { return getStringPreference(context, PROFILE_KEY_PREF, null); diff --git a/test/unitTest/java/org/thoughtcrime/securesms/jobs/DeliveryReceiptJobTest.java b/test/unitTest/java/org/thoughtcrime/securesms/jobs/DeliveryReceiptJobTest.java deleted file mode 100644 index ead94c85c0..0000000000 --- a/test/unitTest/java/org/thoughtcrime/securesms/jobs/DeliveryReceiptJobTest.java +++ /dev/null @@ -1,104 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; -import org.thoughtcrime.securesms.BaseUnitTest; -import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory; -import org.whispersystems.signalservice.api.SignalServiceMessageSender; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.whispersystems.signalservice.api.push.exceptions.NotFoundException; -import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; - -import java.io.IOException; - -import dagger.Module; -import dagger.ObjectGraph; -import dagger.Provides; - -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -public class DeliveryReceiptJobTest extends BaseUnitTest { - @Test - public void testDelivery() throws IOException { - SignalServiceMessageSender textSecureMessageSender = mock(SignalServiceMessageSender.class); - long timestamp = System.currentTimeMillis(); - - DeliveryReceiptJob deliveryReceiptJob = new DeliveryReceiptJob(context, - "+14152222222", - timestamp, "foo"); - - ObjectGraph objectGraph = ObjectGraph.create(new TestModule(textSecureMessageSender)); - objectGraph.inject(deliveryReceiptJob); - - deliveryReceiptJob.onRun(); - - ArgumentCaptor captor = ArgumentCaptor.forClass(SignalServiceAddress.class); - verify(textSecureMessageSender).sendDeliveryReceipt(captor.capture(), eq(timestamp)); - - assertTrue(captor.getValue().getRelay().get().equals("foo")); - assertTrue(captor.getValue().getNumber().equals("+14152222222")); - } - - @Test - public void testNetworkError() throws IOException { - SignalServiceMessageSender textSecureMessageSender = mock(SignalServiceMessageSender.class); - long timestamp = System.currentTimeMillis(); - - Mockito.doThrow(new PushNetworkException("network error")) - .when(textSecureMessageSender) - .sendDeliveryReceipt(any(SignalServiceAddress.class), eq(timestamp)); - - - DeliveryReceiptJob deliveryReceiptJob = new DeliveryReceiptJob(context, - "+14152222222", - timestamp, "foo"); - - ObjectGraph objectGraph = ObjectGraph.create(new TestModule(textSecureMessageSender)); - objectGraph.inject(deliveryReceiptJob); - - try { - deliveryReceiptJob.onRun(); - throw new AssertionError(); - } catch (IOException e) { - assertTrue(deliveryReceiptJob.onShouldRetry(e)); - } - - Mockito.doThrow(new NotFoundException("not found")) - .when(textSecureMessageSender) - .sendDeliveryReceipt(any(SignalServiceAddress.class), eq(timestamp)); - - try { - deliveryReceiptJob.onRun(); - throw new AssertionError(); - } catch (IOException e) { - assertFalse(deliveryReceiptJob.onShouldRetry(e)); - } - } - - @Module(injects = DeliveryReceiptJob.class) - public static class TestModule { - - private final SignalServiceMessageSender textSecureMessageSender; - - public TestModule(SignalServiceMessageSender textSecureMessageSender) { - this.textSecureMessageSender = textSecureMessageSender; - } - - @Provides - SignalMessageSenderFactory provideSignalServiceMessageSenderFactory() { - return new SignalMessageSenderFactory() { - @Override - public SignalServiceMessageSender create() { - return textSecureMessageSender; - } - }; - } - } - -}