Support for read receipts

// FREEBIE
This commit is contained in:
Moxie Marlinspike 2017-09-15 22:38:53 -07:00
parent 65124fd1f2
commit cb9bc9659b
46 changed files with 471 additions and 451 deletions

View File

@ -63,7 +63,7 @@ dependencies {
compile 'org.whispersystems:jobmanager:1.0.2' compile 'org.whispersystems:jobmanager:1.0.2'
compile 'org.whispersystems:libpastelog:1.0.7' 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 'org.whispersystems:webrtc-android:M59-S1'
compile "me.leolin:ShortcutBadger:1.1.16" compile "me.leolin:ShortcutBadger:1.1.16"
@ -138,7 +138,7 @@ dependencyVerification {
'com.google.android.exoplayer:exoplayer:955085aa611a8f7cf6c61b88ae03d1a392f4ad94c9bfbc153f3dedb9ffb14718', 'com.google.android.exoplayer:exoplayer:955085aa611a8f7cf6c61b88ae03d1a392f4ad94c9bfbc153f3dedb9ffb14718',
'org.whispersystems:jobmanager:506f679fc2fcf7bb6d10f00f41d6f6ea0abf75c70dc95b913398661ad538a181', 'org.whispersystems:jobmanager:506f679fc2fcf7bb6d10f00f41d6f6ea0abf75c70dc95b913398661ad538a181',
'org.whispersystems:libpastelog:bb331d9a98240fc139101128ba836c1edec3c40e000597cdbb29ebf4cbf34d88', 'org.whispersystems:libpastelog:bb331d9a98240fc139101128ba836c1edec3c40e000597cdbb29ebf4cbf34d88',
'org.whispersystems:signal-service-android:690e04d53c8b5ec8cda064b242d7c00b0e5321851b811798dd0ec3712f5e1a85', 'org.whispersystems:signal-service-android:731fc8c45f38f42b2d0da1053cf32adcd175708e0c126571150690637108971a',
'org.whispersystems:webrtc-android:de647643afbbea45a26a4f24db75aa10bc8de45426e8eb0d9d563cc10af4f582', 'org.whispersystems:webrtc-android:de647643afbbea45a26a4f24db75aa10bc8de45426e8eb0d9d563cc10af4f582',
'me.leolin:ShortcutBadger:e3cb3e7625892129b0c92dd5e4bc649faffdd526d5af26d9c45ee31ff8851774', 'me.leolin:ShortcutBadger:e3cb3e7625892129b0c92dd5e4bc649faffdd526d5af26d9c45ee31ff8851774',
'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb', 'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb',
@ -173,7 +173,7 @@ dependencyVerification {
'com.google.android.gms:play-services-basement:95dd882c5ffba15b9a99de3fefb05d3a01946623af67454ca00055d222f85a8d', 'com.google.android.gms:play-services-basement:95dd882c5ffba15b9a99de3fefb05d3a01946623af67454ca00055d222f85a8d',
'com.google.android.gms:play-services-iid:54e919f9957b8b7820da7ee9b83471d00d0cac1cf08ddea8b5b41aea80bb1a70', 'com.google.android.gms:play-services-iid:54e919f9957b8b7820da7ee9b83471d00d0cac1cf08ddea8b5b41aea80bb1a70',
'org.whispersystems:signal-protocol-android:5b8acded7f2a40178eb90ab8e8cbfec89d170d91b3ff5e78487d1098df6185a1', 'org.whispersystems:signal-protocol-android:5b8acded7f2a40178eb90ab8e8cbfec89d170d91b3ff5e78487d1098df6185a1',
'org.whispersystems:signal-service-java:438e8330cf806152e7e226d8241dd6388ee4005f0ea7d2aaa99d7ef514012dca', 'org.whispersystems:signal-service-java:90aadf941cc31cb0f5af9e1adffa248556f1cfc56a62c141bc62812e7bf3ed52',
'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a', 'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'com.klinkerapps:logger:177e325259a8b111ad6745ec10db5861723c99f402222b80629f576f49408541', 'com.klinkerapps:logger:177e325259a8b111ad6745ec10db5861723c99f402222b80629f576f49408541',

View File

@ -27,6 +27,18 @@
android:paddingBottom="2dp" android:paddingBottom="2dp"
android:visibility="gone" android:visibility="gone"
android:contentDescription="@string/conversation_item_sent__delivered_description" android:contentDescription="@string/conversation_item_sent__delivered_description"
tools:visibility="gone"/>
<ImageView android:id="@+id/read_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:src="@drawable/ic_done_all_white_18dp"
android:paddingLeft="2dp"
android:paddingBottom="2dp"
android:visibility="gone"
android:contentDescription="@string/conversation_item_sent__message_read"
android:tint="@color/blue_800"
tools:visibility="visible"/> tools:visibility="visible"/>
</merge> </merge>

View File

@ -1456,7 +1456,9 @@
<!-- transport_selection_list_item --> <!-- transport_selection_list_item -->
<string name="transport_selection_list_item__transport_icon">Transport icon</string> <string name="transport_selection_list_item__transport_icon">Transport icon</string>
<string name="conversation_item_sent__message_read">Message read</string>
<string name="preferences__read_receipts">Read receipts</string>
<string name="preferences__if_read_receipts_are_disabled_you_wont_be_able_to_see_read_receipts">If you read receipts are disabled, you won\'t be able to see read receipts from others.</string>
<!-- EOF --> <!-- EOF -->

View File

@ -40,6 +40,12 @@
android:title="@string/preferences_advanced__always_relay_calls" 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"/> android:summary="@string/preferences_advanced__relay_all_calls_through_the_signal_server_to_avoid_revealing_your_ip_address"/>
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_read_receipts"
android:title="@string/preferences__read_receipts"
android:summary="@string/preferences__if_read_receipts_are_disabled_you_wont_be_able_to_see_read_receipts"/>
<Preference android:key="preference_category_blocked" <Preference android:key="preference_category_blocked"
android:title="@string/preferences_app_protection__blocked_contacts" /> android:title="@string/preferences_app_protection__blocked_contacts" />
</PreferenceCategory> </PreferenceCategory>

View File

@ -443,10 +443,11 @@ public class ConversationItem extends LinearLayout
} else { } else {
alertView.setNone(); alertView.setNone();
if (!messageRecord.isOutgoing()) deliveryStatusIndicator.setNone(); if (!messageRecord.isOutgoing()) deliveryStatusIndicator.setNone();
else if (messageRecord.isPending()) deliveryStatusIndicator.setPending(); else if (messageRecord.isPending()) deliveryStatusIndicator.setPending();
else if (messageRecord.isDelivered()) deliveryStatusIndicator.setDelivered(); else if (messageRecord.isRemoteRead()) deliveryStatusIndicator.setRead();
else deliveryStatusIndicator.setSent(); else if (messageRecord.isDelivered()) deliveryStatusIndicator.setDelivered();
else deliveryStatusIndicator.setSent();
} }
} }

View File

@ -214,9 +214,10 @@ public class ConversationListItem extends RelativeLayout
} else { } else {
alertView.setNone(); alertView.setNone();
if (thread.isPending()) deliveryStatusIndicator.setPending(); if (thread.isPending()) deliveryStatusIndicator.setPending();
else if (thread.isDelivered()) deliveryStatusIndicator.setDelivered(); else if (thread.isRemoteRead()) deliveryStatusIndicator.setRead();
else deliveryStatusIndicator.setSent(); else if (thread.isDelivered()) deliveryStatusIndicator.setDelivered();
else deliveryStatusIndicator.setSent();
} }
} }

View File

@ -68,6 +68,7 @@ public class PassphraseCreateActivity extends PassphraseActivity {
VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this); VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this);
TextSecurePreferences.setLastExperienceVersionCode(PassphraseCreateActivity.this, Util.getCurrentApkReleaseVersion(PassphraseCreateActivity.this)); TextSecurePreferences.setLastExperienceVersionCode(PassphraseCreateActivity.this, Util.getCurrentApkReleaseVersion(PassphraseCreateActivity.this));
TextSecurePreferences.setPasswordDisabled(PassphraseCreateActivity.this, true); TextSecurePreferences.setPasswordDisabled(PassphraseCreateActivity.this, true);
TextSecurePreferences.setReadReceiptsEnabled(PassphraseCreateActivity.this, true);
return null; return null;
} }

View File

@ -22,6 +22,7 @@ public class DeliveryStatusView extends FrameLayout {
private final ViewGroup pendingIndicatorStub; private final ViewGroup pendingIndicatorStub;
private final ImageView sentIndicator; private final ImageView sentIndicator;
private final ImageView deliveredIndicator; private final ImageView deliveredIndicator;
private final ImageView readIndicator;
public DeliveryStatusView(Context context) { public DeliveryStatusView(Context context) {
this(context, null); this(context, null);
@ -39,6 +40,7 @@ public class DeliveryStatusView extends FrameLayout {
this.deliveredIndicator = (ImageView) findViewById(R.id.delivered_indicator); this.deliveredIndicator = (ImageView) findViewById(R.id.delivered_indicator);
this.sentIndicator = (ImageView) findViewById(R.id.sent_indicator); this.sentIndicator = (ImageView) findViewById(R.id.sent_indicator);
this.pendingIndicatorStub = (ViewGroup) findViewById(R.id.pending_indicator_stub); this.pendingIndicatorStub = (ViewGroup) findViewById(R.id.pending_indicator_stub);
this.readIndicator = (ImageView) findViewById(R.id.read_indicator);
int iconColor = Color.GRAY; int iconColor = Color.GRAY;
@ -71,6 +73,7 @@ public class DeliveryStatusView extends FrameLayout {
pendingIndicatorStub.setVisibility(View.VISIBLE); pendingIndicatorStub.setVisibility(View.VISIBLE);
sentIndicator.setVisibility(View.GONE); sentIndicator.setVisibility(View.GONE);
deliveredIndicator.setVisibility(View.GONE); deliveredIndicator.setVisibility(View.GONE);
readIndicator.setVisibility(View.GONE);
} }
public void setSent() { public void setSent() {
@ -78,6 +81,7 @@ public class DeliveryStatusView extends FrameLayout {
pendingIndicatorStub.setVisibility(View.GONE); pendingIndicatorStub.setVisibility(View.GONE);
sentIndicator.setVisibility(View.VISIBLE); sentIndicator.setVisibility(View.VISIBLE);
deliveredIndicator.setVisibility(View.GONE); deliveredIndicator.setVisibility(View.GONE);
readIndicator.setVisibility(View.GONE);
} }
public void setDelivered() { public void setDelivered() {
@ -85,5 +89,14 @@ public class DeliveryStatusView extends FrameLayout {
pendingIndicatorStub.setVisibility(View.GONE); pendingIndicatorStub.setVisibility(View.GONE);
sentIndicator.setVisibility(View.GONE); sentIndicator.setVisibility(View.GONE);
deliveredIndicator.setVisibility(View.VISIBLE); 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);
} }
} }

View File

@ -106,7 +106,8 @@ public class DatabaseFactory {
private static final int PROFILES = 41; private static final int PROFILES = 41;
private static final int PROFILE_SHARING_APPROVAL = 42; private static final int PROFILE_SHARING_APPROVAL = 42;
private static final int UNSEEN_NUMBER_OFFER = 43; 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 String DATABASE_NAME = "messages.db";
private static final Object lock = new Object(); 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"); 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.setTransactionSuccessful();
db.endTransaction(); db.endTransaction();
} }

View File

@ -59,6 +59,7 @@ import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.JsonUtils;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.jobqueue.JobManager; import org.whispersystems.jobqueue.JobManager;
import org.whispersystems.libsignal.InvalidMessageException; import org.whispersystems.libsignal.InvalidMessageException;
@ -101,10 +102,11 @@ public class MmsDatabase extends MessagingDatabase {
STATUS + " INTEGER, " + TRANSACTION_ID + " TEXT, " + "retr_st" + " INTEGER, " + STATUS + " INTEGER, " + TRANSACTION_ID + " TEXT, " + "retr_st" + " INTEGER, " +
"retr_txt" + " TEXT, " + "retr_txt_cs" + " INTEGER, " + "read_status" + " INTEGER, " + "retr_txt" + " TEXT, " + "retr_txt_cs" + " INTEGER, " + "read_status" + " INTEGER, " +
"ct_cls" + " INTEGER, " + "resp_txt" + " TEXT, " + "d_tm" + " 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, " + NETWORK_FAILURE + " TEXT DEFAULT NULL," + "d_rpt" + " INTEGER, " +
SUBSCRIPTION_ID + " INTEGER DEFAULT -1, " + EXPIRES_IN + " INTEGER DEFAULT 0, " + 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 = { public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS mms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");", "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, CONTENT_LOCATION, EXPIRY, MESSAGE_TYPE,
MESSAGE_SIZE, STATUS, TRANSACTION_ID, MESSAGE_SIZE, STATUS, TRANSACTION_ID,
BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_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, EXPIRES_IN, EXPIRE_STARTED, NOTIFIED,
AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + " AS " + AttachmentDatabase.ATTACHMENT_ID_ALIAS, AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + " AS " + AttachmentDatabase.ATTACHMENT_ID_ALIAS,
AttachmentDatabase.UNIQUE_ID, AttachmentDatabase.UNIQUE_ID,
@ -144,7 +146,9 @@ public class MmsDatabase extends MessagingDatabase {
private static final String RAW_ID_WHERE = TABLE_NAME + "._id = ?"; 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; private final JobManager jobManager;
public MmsDatabase(Context context, SQLiteOpenHelper databaseHelper) { 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(); SQLiteDatabase database = databaseHelper.getWritableDatabase();
Cursor cursor = null; Cursor cursor = null;
boolean found = false; boolean found = false;
@ -201,7 +205,8 @@ public class MmsDatabase extends MessagingDatabase {
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
if (Types.isOutgoingMessageType(cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX)))) { if (Types.isOutgoingMessageType(cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX)))) {
Address theirAddress = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))); 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()) { if (ourAddress.equals(theirAddress) || theirAddress.isGroup()) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID)); long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
@ -210,7 +215,7 @@ public class MmsDatabase extends MessagingDatabase {
found = true; found = true;
database.execSQL("UPDATE " + TABLE_NAME + " SET " + database.execSQL("UPDATE " + TABLE_NAME + " SET " +
RECEIPT_COUNT + " = " + RECEIPT_COUNT + " + 1 WHERE " + ID + " = ?", columnName + " = " + columnName + " + 1 WHERE " + ID + " = ?",
new String[] {String.valueOf(id)}); new String[] {String.valueOf(id)});
DatabaseFactory.getThreadDatabase(context).update(threadId, false); DatabaseFactory.getThreadDatabase(context).update(threadId, false);
@ -220,7 +225,8 @@ public class MmsDatabase extends MessagingDatabase {
} }
if (!found) { 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 { } finally {
if (cursor != null) if (cursor != null)
@ -803,7 +809,8 @@ public class MmsDatabase extends MessagingDatabase {
contentValues.put(SUBSCRIPTION_ID, message.getSubscriptionId()); contentValues.put(SUBSCRIPTION_ID, message.getSubscriptionId());
contentValues.put(EXPIRES_IN, message.getExpiresIn()); contentValues.put(EXPIRES_IN, message.getExpiresIn());
contentValues.put(ADDRESS, message.getRecipient().getAddress().serialize()); 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); long messageId = insertMediaMessage(masterSecret, message.getBody(), message.getAttachments(), contentValues, insertListener);
@ -1060,7 +1067,7 @@ public class MmsDatabase extends MessagingDatabase {
new LinkedList<NetworkFailure>(), new LinkedList<NetworkFailure>(),
message.getSubscriptionId(), message.getSubscriptionId(),
message.getExpiresIn(), message.getExpiresIn(),
System.currentTimeMillis()); System.currentTimeMillis(), 0);
} }
} }
@ -1096,22 +1103,27 @@ public class MmsDatabase extends MessagingDatabase {
} }
private NotificationMmsMessageRecord getNotificationMmsMessageRecord(Cursor cursor) { private NotificationMmsMessageRecord getNotificationMmsMessageRecord(Cursor cursor) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.ID)); long id = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.ID));
long dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_SENT)); long dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_SENT));
long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_RECEIVED)); long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_RECEIVED));
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.THREAD_ID)); long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.THREAD_ID));
long mailbox = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX)); long mailbox = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX));
String address = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS)); String address = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS));
int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS_DEVICE_ID)); int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS_DEVICE_ID));
Recipient recipient = getRecipientFor(address); Recipient recipient = getRecipientFor(address);
String contentLocation = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.CONTENT_LOCATION)); String contentLocation = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.CONTENT_LOCATION));
String transactionId = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.TRANSACTION_ID)); String transactionId = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.TRANSACTION_ID));
long messageSize = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_SIZE)); long messageSize = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_SIZE));
long expiry = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRY)); long expiry = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRY));
int status = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.STATUS)); int status = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.STATUS));
int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.RECEIPT_COUNT)); int deliveryReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.DELIVERY_RECEIPT_COUNT));
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.SUBSCRIPTION_ID)); 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[]contentLocationBytes = null;
byte[]transactionIdBytes = null; byte[]transactionIdBytes = null;
@ -1126,27 +1138,33 @@ public class MmsDatabase extends MessagingDatabase {
return new NotificationMmsMessageRecord(context, id, recipient, recipient, return new NotificationMmsMessageRecord(context, id, recipient, recipient,
addressDeviceId, dateSent, dateReceived, receiptCount, threadId, addressDeviceId, dateSent, dateReceived, deliveryReceiptCount, threadId,
contentLocationBytes, messageSize, expiry, status, contentLocationBytes, messageSize, expiry, status,
transactionIdBytes, mailbox, subscriptionId, slideDeck); transactionIdBytes, mailbox, subscriptionId, slideDeck,
readReceiptCount);
} }
private MediaMmsMessageRecord getMediaMmsMessageRecord(Cursor cursor) { private MediaMmsMessageRecord getMediaMmsMessageRecord(Cursor cursor) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.ID)); long id = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.ID));
long dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_SENT)); long dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_SENT));
long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_RECEIVED)); long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_RECEIVED));
long box = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX)); long box = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX));
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.THREAD_ID)); long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.THREAD_ID));
String address = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS)); String address = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS));
int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS_DEVICE_ID)); int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS_DEVICE_ID));
int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.RECEIPT_COUNT)); int deliveryReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.DELIVERY_RECEIPT_COUNT));
DisplayRecord.Body body = getBody(cursor); int readReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.READ_RECEIPT_COUNT));
int partCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.PART_COUNT)); DisplayRecord.Body body = getBody(cursor);
String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.MISMATCHED_IDENTITIES)); int partCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.PART_COUNT));
String networkDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.NETWORK_FAILURE)); String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.MISMATCHED_IDENTITIES));
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.SUBSCRIPTION_ID)); String networkDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.NETWORK_FAILURE));
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRES_IN)); int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.SUBSCRIPTION_ID));
long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRE_STARTED)); 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); Recipient recipient = getRecipientFor(address);
List<IdentityKeyMismatch> mismatches = getMismatchedIdentities(mismatchDocument); List<IdentityKeyMismatch> mismatches = getMismatchedIdentities(mismatchDocument);
@ -1154,9 +1172,10 @@ public class MmsDatabase extends MessagingDatabase {
SlideDeck slideDeck = getSlideDeck(cursor); SlideDeck slideDeck = getSlideDeck(cursor);
return new MediaMmsMessageRecord(context, id, recipient, recipient, return new MediaMmsMessageRecord(context, id, recipient, recipient,
addressDeviceId, dateSent, dateReceived, receiptCount, addressDeviceId, dateSent, dateReceived, deliveryReceiptCount,
threadId, body, slideDeck, partCount, box, mismatches, threadId, body, slideDeck, partCount, box, mismatches,
networkFailures, subscriptionId, expiresIn, expireStarted); networkFailures, subscriptionId, expiresIn, expireStarted,
readReceiptCount);
} }
private Recipient getRecipientFor(String serialized) { private Recipient getRecipientFor(String serialized) {

View File

@ -11,7 +11,8 @@ public interface MmsSmsColumns {
public static final String BODY = "body"; public static final String BODY = "body";
public static final String ADDRESS = "address"; public static final String ADDRESS = "address";
public static final String ADDRESS_DEVICE_ID = "address_device_id"; 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 MISMATCHED_IDENTITIES = "mismatched_identities";
public static final String UNIQUE_ROW_ID = "unique_row_id"; public static final String UNIQUE_ROW_ID = "unique_row_id";
public static final String SUBSCRIPTION_ID = "subscription_id"; public static final String SUBSCRIPTION_ID = "subscription_id";

View File

@ -25,7 +25,6 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
@ -52,7 +51,9 @@ public class MmsSmsDatabase extends Database {
SmsDatabase.STATUS, MmsDatabase.PART_COUNT, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID, MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT, MmsDatabase.STATUS,
MmsSmsColumns.DELIVERY_RECEIPT_COUNT,
MmsSmsColumns.READ_RECEIPT_COUNT,
MmsSmsColumns.MISMATCHED_IDENTITIES, MmsSmsColumns.MISMATCHED_IDENTITIES,
MmsDatabase.NETWORK_FAILURE, MmsDatabase.NETWORK_FAILURE,
MmsSmsColumns.SUBSCRIPTION_ID, MmsSmsColumns.SUBSCRIPTION_ID,
@ -137,8 +138,13 @@ public class MmsSmsDatabase extends Database {
} }
public void incrementDeliveryReceiptCount(SyncMessageId syncMessageId) { public void incrementDeliveryReceiptCount(SyncMessageId syncMessageId) {
DatabaseFactory.getSmsDatabase(context).incrementDeliveryReceiptCount(syncMessageId); DatabaseFactory.getSmsDatabase(context).incrementReceiptCount(syncMessageId, true, false);
DatabaseFactory.getMmsDatabase(context).incrementDeliveryReceiptCount(syncMessageId); 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) { 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.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID, MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS, 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.SUBSCRIPTION_ID, MmsSmsColumns.EXPIRES_IN, MmsSmsColumns.EXPIRE_STARTED,
MmsSmsColumns.NOTIFIED, MmsSmsColumns.NOTIFIED,
MmsDatabase.NETWORK_FAILURE, TRANSPORT, MmsDatabase.NETWORK_FAILURE, TRANSPORT,
AttachmentDatabase.UNIQUE_ID, AttachmentDatabase.UNIQUE_ID,
AttachmentDatabase.MMS_ID, AttachmentDatabase.MMS_ID,
AttachmentDatabase.SIZE, AttachmentDatabase.SIZE,
@ -185,7 +192,8 @@ public class MmsSmsDatabase extends Database {
MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT, MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID, MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS, 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.SUBSCRIPTION_ID, MmsSmsColumns.EXPIRES_IN, MmsSmsColumns.EXPIRE_STARTED,
MmsSmsColumns.NOTIFIED, MmsSmsColumns.NOTIFIED,
MmsDatabase.NETWORK_FAILURE, TRANSPORT, MmsDatabase.NETWORK_FAILURE, TRANSPORT,
@ -227,7 +235,8 @@ public class MmsSmsDatabase extends Database {
mmsColumnsPresent.add(MmsSmsColumns.BODY); mmsColumnsPresent.add(MmsSmsColumns.BODY);
mmsColumnsPresent.add(MmsSmsColumns.ADDRESS); mmsColumnsPresent.add(MmsSmsColumns.ADDRESS);
mmsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID); 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.MISMATCHED_IDENTITIES);
mmsColumnsPresent.add(MmsSmsColumns.SUBSCRIPTION_ID); mmsColumnsPresent.add(MmsSmsColumns.SUBSCRIPTION_ID);
mmsColumnsPresent.add(MmsSmsColumns.EXPIRES_IN); mmsColumnsPresent.add(MmsSmsColumns.EXPIRES_IN);
@ -268,7 +277,8 @@ public class MmsSmsDatabase extends Database {
smsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID); smsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID);
smsColumnsPresent.add(MmsSmsColumns.READ); smsColumnsPresent.add(MmsSmsColumns.READ);
smsColumnsPresent.add(MmsSmsColumns.THREAD_ID); 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.MISMATCHED_IDENTITIES);
smsColumnsPresent.add(MmsSmsColumns.SUBSCRIPTION_ID); smsColumnsPresent.add(MmsSmsColumns.SUBSCRIPTION_ID);
smsColumnsPresent.add(MmsSmsColumns.EXPIRES_IN); smsColumnsPresent.add(MmsSmsColumns.EXPIRES_IN);

View File

@ -39,6 +39,7 @@ import org.thoughtcrime.securesms.sms.IncomingGroupMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.JsonUtils;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.jobqueue.JobManager; import org.whispersystems.jobqueue.JobManager;
import org.whispersystems.libsignal.util.guava.Optional; 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, " + 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, " + DATE_RECEIVED + " INTEGER, " + DATE_SENT + " INTEGER, " + PROTOCOL + " INTEGER, " + READ + " INTEGER DEFAULT 0, " +
STATUS + " INTEGER DEFAULT -1," + TYPE + " INTEGER, " + REPLY_PATH_PRESENT + " INTEGER, " + 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, " + 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 = { public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS sms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");", "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_RECEIVED + " AS " + NORMALIZED_DATE_RECEIVED,
DATE_SENT + " AS " + NORMALIZED_DATE_SENT, DATE_SENT + " AS " + NORMALIZED_DATE_SENT,
PROTOCOL, READ, STATUS, TYPE, 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, 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; private final JobManager jobManager;
public SmsDatabase(Context context, SQLiteOpenHelper databaseHelper) { 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)}); 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(); SQLiteDatabase database = databaseHelper.getWritableDatabase();
Cursor cursor = null; Cursor cursor = null;
boolean foundMessage = false; boolean foundMessage = false;
@ -291,12 +295,13 @@ public class SmsDatabase extends MessagingDatabase {
if (Types.isOutgoingMessageType(cursor.getLong(cursor.getColumnIndexOrThrow(TYPE)))) { if (Types.isOutgoingMessageType(cursor.getLong(cursor.getColumnIndexOrThrow(TYPE)))) {
Address theirAddress = messageId.getAddress(); Address theirAddress = messageId.getAddress();
Address ourAddress = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))); Address ourAddress = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)));
String columnName = deliveryReceipt ? DELIVERY_RECEIPT_COUNT : READ_RECEIPT_COUNT;
if (ourAddress.equals(theirAddress)) { if (ourAddress.equals(theirAddress)) {
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID)); long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
database.execSQL("UPDATE " + TABLE_NAME + database.execSQL("UPDATE " + TABLE_NAME +
" SET " + RECEIPT_COUNT + " = " + RECEIPT_COUNT + " + 1 WHERE " + " SET " + columnName + " = " + columnName + " + 1 WHERE " +
ID + " = ?", ID + " = ?",
new String[] {String.valueOf(cursor.getLong(cursor.getColumnIndexOrThrow(ID)))}); new String[] {String.valueOf(cursor.getLong(cursor.getColumnIndexOrThrow(ID)))});
@ -308,7 +313,8 @@ public class SmsDatabase extends MessagingDatabase {
} }
if (!foundMessage) { 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 { } finally {
@ -600,7 +606,8 @@ public class SmsDatabase extends MessagingDatabase {
contentValues.put(TYPE, type); contentValues.put(TYPE, type);
contentValues.put(SUBSCRIPTION_ID, message.getSubscriptionId()); contentValues.put(SUBSCRIPTION_ID, message.getSubscriptionId());
contentValues.put(EXPIRES_IN, message.getExpiresIn()); 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(); SQLiteDatabase db = databaseHelper.getWritableDatabase();
long messageId = db.insert(TABLE_NAME, ADDRESS, contentValues); 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(), 0, message.isSecureMessage() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(),
threadId, 0, new LinkedList<IdentityKeyMismatch>(), threadId, 0, new LinkedList<IdentityKeyMismatch>(),
message.getSubscriptionId(), message.getExpiresIn(), message.getSubscriptionId(), message.getExpiresIn(),
System.currentTimeMillis()); System.currentTimeMillis(), 0);
} }
} }
@ -814,19 +821,24 @@ public class SmsDatabase extends MessagingDatabase {
} }
public SmsMessageRecord getCurrent() { public SmsMessageRecord getCurrent() {
long messageId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID)); long messageId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID));
Address address = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS))); Address address = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS)));
int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS_DEVICE_ID)); int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS_DEVICE_ID));
long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE)); long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE));
long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.NORMALIZED_DATE_RECEIVED)); long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.NORMALIZED_DATE_RECEIVED));
long dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.NORMALIZED_DATE_SENT)); long dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.NORMALIZED_DATE_SENT));
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.THREAD_ID)); long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.THREAD_ID));
int status = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.STATUS)); int status = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.STATUS));
int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.RECEIPT_COUNT)); int deliveryReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.DELIVERY_RECEIPT_COUNT));
String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.MISMATCHED_IDENTITIES)); int readReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.READ_RECEIPT_COUNT));
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.SUBSCRIPTION_ID)); String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.MISMATCHED_IDENTITIES));
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.EXPIRES_IN)); int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.SUBSCRIPTION_ID));
long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.EXPIRE_STARTED)); 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<IdentityKeyMismatch> mismatches = getMismatches(mismatchDocument); List<IdentityKeyMismatch> mismatches = getMismatches(mismatchDocument);
Recipient recipient = Recipient.from(context, address, true); Recipient recipient = Recipient.from(context, address, true);
@ -835,9 +847,9 @@ public class SmsDatabase extends MessagingDatabase {
return new SmsMessageRecord(context, messageId, body, recipient, return new SmsMessageRecord(context, messageId, body, recipient,
recipient, recipient,
addressDeviceId, addressDeviceId,
dateSent, dateReceived, receiptCount, type, dateSent, dateReceived, deliveryReceiptCount, type,
threadId, status, mismatches, subscriptionId, threadId, status, mismatches, subscriptionId,
expiresIn, expireStarted); expiresIn, expireStarted, readReceiptCount);
} }
private List<IdentityKeyMismatch> getMismatches(String document) { private List<IdentityKeyMismatch> getMismatches(String document) {

View File

@ -43,6 +43,7 @@ import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.DelimiterUtil; import org.thoughtcrime.securesms.util.DelimiterUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.InvalidMessageException; import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.util.Pair; import org.whispersystems.libsignal.util.Pair;
@ -56,34 +57,36 @@ public class ThreadDatabase extends Database {
private static final String TAG = ThreadDatabase.class.getSimpleName(); private static final String TAG = ThreadDatabase.class.getSimpleName();
static final String TABLE_NAME = "thread"; static final String TABLE_NAME = "thread";
public static final String ID = "_id"; public static final String ID = "_id";
public static final String DATE = "date"; public static final String DATE = "date";
public static final String MESSAGE_COUNT = "message_count"; public static final String MESSAGE_COUNT = "message_count";
public static final String ADDRESS = "recipient_ids"; public static final String ADDRESS = "recipient_ids";
public static final String SNIPPET = "snippet"; public static final String SNIPPET = "snippet";
private static final String SNIPPET_CHARSET = "snippet_cs"; private static final String SNIPPET_CHARSET = "snippet_cs";
public static final String READ = "read"; public static final String READ = "read";
public static final String TYPE = "type"; public static final String TYPE = "type";
private static final String ERROR = "error"; private static final String ERROR = "error";
public static final String SNIPPET_TYPE = "snippet_type"; public static final String SNIPPET_TYPE = "snippet_type";
public static final String SNIPPET_URI = "snippet_uri"; public static final String SNIPPET_URI = "snippet_uri";
public static final String ARCHIVED = "archived"; public static final String ARCHIVED = "archived";
public static final String STATUS = "status"; public static final String STATUS = "status";
public static final String RECEIPT_COUNT = "delivery_receipt_count"; public static final String DELIVERY_RECEIPT_COUNT = "delivery_receipt_count";
public static final String EXPIRES_IN = "expires_in"; public static final String READ_RECEIPT_COUNT = "read_receipt_count";
public static final String LAST_SEEN = "last_seen"; public static final String EXPIRES_IN = "expires_in";
private static final String HAS_SENT = "has_sent"; 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 + " (" + public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" +
ID + " INTEGER PRIMARY KEY, " + DATE + " INTEGER DEFAULT 0, " + 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, " + SNIPPET_CHARSET + " INTEGER DEFAULT 0, " + READ + " INTEGER DEFAULT 1, " +
TYPE + " INTEGER DEFAULT 0, " + ERROR + " INTEGER DEFAULT 0, " + TYPE + " INTEGER DEFAULT 0, " + ERROR + " INTEGER DEFAULT 0, " +
SNIPPET_TYPE + " INTEGER DEFAULT 0, " + SNIPPET_URI + " TEXT DEFAULT NULL, " + SNIPPET_TYPE + " INTEGER DEFAULT 0, " + SNIPPET_URI + " TEXT DEFAULT NULL, " +
ARCHIVED + " INTEGER DEFAULT 0, " + STATUS + " INTEGER DEFAULT 0, " + ARCHIVED + " INTEGER DEFAULT 0, " + STATUS + " INTEGER DEFAULT 0, " +
RECEIPT_COUNT + " INTEGER DEFAULT 0, " + EXPIRES_IN + " INTEGER DEFAULT 0, " + DELIVERY_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + EXPIRES_IN + " INTEGER DEFAULT 0, " +
LAST_SEEN + " INTEGER DEFAULT 0, " + HAS_SENT + " 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 = { public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + ADDRESS + ");", "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 = { private static final String[] THREAD_PROJECTION = {
ID, DATE, MESSAGE_COUNT, ADDRESS, SNIPPET, SNIPPET_CHARSET, READ, TYPE, ERROR, SNIPPET_TYPE, 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<String> TYPED_THREAD_PROJECTION = Stream.of(THREAD_PROJECTION) private static final List<String> 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, private void updateThread(long threadId, long count, String body, @Nullable Uri attachment,
long date, int status, int receiptCount, long type, boolean unarchive, long date, int status, int deliveryReceiptCount, long type, boolean unarchive,
long expiresIn) long expiresIn, int readReceiptCount)
{ {
ContentValues contentValues = new ContentValues(7); ContentValues contentValues = new ContentValues(7);
contentValues.put(DATE, date - date % 1000); 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_URI, attachment == null ? null : attachment.toString());
contentValues.put(SNIPPET_TYPE, type); contentValues.put(SNIPPET_TYPE, type);
contentValues.put(STATUS, status); 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); contentValues.put(EXPIRES_IN, expiresIn);
if (unarchive) { if (unarchive) {
@ -550,8 +554,8 @@ public class ThreadDatabase extends Database {
if (reader != null && (record = reader.getNext()) != null) { if (reader != null && (record = reader.getNext()) != null) {
updateThread(threadId, count, record.getBody().getBody(), getAttachmentUriFor(record), updateThread(threadId, count, record.getBody().getBody(), getAttachmentUriFor(record),
record.getTimestamp(), record.getDeliveryStatus(), record.getReceiptCount(), record.getTimestamp(), record.getDeliveryStatus(), record.getDeliveryReceiptCount(),
record.getType(), unarchive, record.getExpiresIn()); record.getType(), unarchive, record.getExpiresIn(), record.getReadReceiptCount());
notifyConversationListListeners(); notifyConversationListListeners();
return false; return false;
} else { } else {
@ -633,22 +637,27 @@ public class ThreadDatabase extends Database {
groupRecord = Optional.absent(); groupRecord = Optional.absent();
} }
Recipient recipient = Recipient.from(context, address, settings, groupRecord, true); Recipient recipient = Recipient.from(context, address, settings, groupRecord, true);
DisplayRecord.Body body = getPlaintextBody(cursor); DisplayRecord.Body body = getPlaintextBody(cursor);
long date = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.DATE)); long date = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.DATE));
long count = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.MESSAGE_COUNT)); long count = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.MESSAGE_COUNT));
long read = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.READ)); long read = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.READ));
long type = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_TYPE)); long type = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_TYPE));
boolean archived = cursor.getInt(cursor.getColumnIndex(ThreadDatabase.ARCHIVED)) != 0; boolean archived = cursor.getInt(cursor.getColumnIndex(ThreadDatabase.ARCHIVED)) != 0;
int status = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.STATUS)); int status = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.STATUS));
int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.RECEIPT_COUNT)); int deliveryReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.DELIVERY_RECEIPT_COUNT));
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.EXPIRES_IN)); int readReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.READ_RECEIPT_COUNT));
long lastSeen = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.LAST_SEEN)); long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.EXPIRES_IN));
Uri snippetUri = getSnippetUri(cursor); 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, return new ThreadRecord(context, body, snippetUri, recipient, date, count, read == 1,
threadId, receiptCount, status, type, distributionType, archived, threadId, deliveryReceiptCount, status, type, distributionType, archived,
expiresIn, lastSeen); expiresIn, lastSeen, readReceiptCount);
} }
private DisplayRecord.Body getPlaintextBody(Cursor cursor) { private DisplayRecord.Body getPlaintextBody(Cursor cursor) {

View File

@ -44,12 +44,12 @@ public class ConversationListLoader extends AbstractCursorLoader {
ThreadDatabase.ID, ThreadDatabase.DATE, ThreadDatabase.MESSAGE_COUNT, ThreadDatabase.ID, ThreadDatabase.DATE, ThreadDatabase.MESSAGE_COUNT,
ThreadDatabase.ADDRESS, ThreadDatabase.SNIPPET, ThreadDatabase.READ, ThreadDatabase.ADDRESS, ThreadDatabase.SNIPPET, ThreadDatabase.READ,
ThreadDatabase.TYPE, ThreadDatabase.SNIPPET_TYPE, ThreadDatabase.SNIPPET_URI, ThreadDatabase.TYPE, ThreadDatabase.SNIPPET_TYPE, ThreadDatabase.SNIPPET_URI,
ThreadDatabase.ARCHIVED, ThreadDatabase.STATUS, ThreadDatabase.RECEIPT_COUNT, ThreadDatabase.ARCHIVED, ThreadDatabase.STATUS, ThreadDatabase.DELIVERY_RECEIPT_COUNT,
ThreadDatabase.EXPIRES_IN, ThreadDatabase.LAST_SEEN}, 1); ThreadDatabase.EXPIRES_IN, ThreadDatabase.LAST_SEEN, ThreadDatabase.READ_RECEIPT_COUNT}, 1);
switchToArchiveCursor.addRow(new Object[] {-1L, System.currentTimeMillis(), archivedCount, switchToArchiveCursor.addRow(new Object[] {-1L, System.currentTimeMillis(), archivedCount,
"-1", null, 1, ThreadDatabase.DistributionTypes.ARCHIVE, "-1", null, 1, ThreadDatabase.DistributionTypes.ARCHIVE,
0, null, 0, -1, 0, 0, 0}); 0, null, 0, -1, 0, 0, 0, -1});
cursorList.add(switchToArchiveCursor); cursorList.add(switchToArchiveCursor);
} }

View File

@ -42,10 +42,12 @@ public abstract class DisplayRecord {
private final long threadId; private final long threadId;
private final Body body; private final Body body;
private final int deliveryStatus; 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, 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.context = context.getApplicationContext();
this.threadId = threadId; this.threadId = threadId;
@ -54,7 +56,8 @@ public abstract class DisplayRecord {
this.dateReceived = dateReceived; this.dateReceived = dateReceived;
this.type = type; this.type = type;
this.body = body; this.body = body;
this.receiptCount = receiptCount; this.deliveryReceiptCount = deliveryReceiptCount;
this.readReceiptCount = readReceiptCount;
this.deliveryStatus = deliveryStatus; this.deliveryStatus = deliveryStatus;
} }
@ -145,13 +148,21 @@ public abstract class DisplayRecord {
return deliveryStatus; return deliveryStatus;
} }
public int getReceiptCount() { public int getDeliveryReceiptCount() {
return receiptCount; return deliveryReceiptCount;
}
public int getReadReceiptCount() {
return readReceiptCount;
} }
public boolean isDelivered() { public boolean isDelivered() {
return (deliveryStatus >= SmsDatabase.Status.STATUS_COMPLETE && 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() { public boolean isPendingInsecureSmsFallback() {

View File

@ -46,17 +46,17 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
public MediaMmsMessageRecord(Context context, long id, Recipient conversationRecipient, public MediaMmsMessageRecord(Context context, long id, Recipient conversationRecipient,
Recipient individualRecipient, int recipientDeviceId, Recipient individualRecipient, int recipientDeviceId,
long dateSent, long dateReceived, int receiptCount, long dateSent, long dateReceived, int deliveryReceiptCount,
long threadId, Body body, long threadId, Body body,
@NonNull SlideDeck slideDeck, @NonNull SlideDeck slideDeck,
int partCount, long mailbox, int partCount, long mailbox,
List<IdentityKeyMismatch> mismatches, List<IdentityKeyMismatch> mismatches,
List<NetworkFailure> failures, int subscriptionId, List<NetworkFailure> failures, int subscriptionId,
long expiresIn, long expireStarted) long expiresIn, long expireStarted, int readReceiptCount)
{ {
super(context, id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent, super(context, id, body, conversationRecipient, individualRecipient, recipientDeviceId, dateSent,
dateReceived, threadId, Status.STATUS_NONE, receiptCount, mailbox, mismatches, failures, dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures,
subscriptionId, expiresIn, expireStarted, slideDeck); subscriptionId, expiresIn, expireStarted, slideDeck, readReceiptCount);
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
this.partCount = partCount; this.partCount = partCount;

View File

@ -57,13 +57,14 @@ public abstract class MessageRecord extends DisplayRecord {
MessageRecord(Context context, long id, Body body, Recipient conversationRecipient, MessageRecord(Context context, long id, Body body, Recipient conversationRecipient,
Recipient individualRecipient, int recipientDeviceId, Recipient individualRecipient, int recipientDeviceId,
long dateSent, long dateReceived, long threadId, long dateSent, long dateReceived, long threadId,
int deliveryStatus, int receiptCount, long type, int deliveryStatus, int deliveryReceiptCount, long type,
List<IdentityKeyMismatch> mismatches, List<IdentityKeyMismatch> mismatches,
List<NetworkFailure> networkFailures, List<NetworkFailure> networkFailures,
int subscriptionId, long expiresIn, long expireStarted) int subscriptionId, long expiresIn, long expireStarted,
int readReceiptCount)
{ {
super(context, body, conversationRecipient, dateSent, dateReceived, threadId, deliveryStatus, receiptCount, super(context, body, conversationRecipient, dateSent, dateReceived,
type); threadId, deliveryStatus, deliveryReceiptCount, type, readReceiptCount);
this.id = id; this.id = id;
this.individualRecipient = individualRecipient; this.individualRecipient = individualRecipient;
this.recipientDeviceId = recipientDeviceId; this.recipientDeviceId = recipientDeviceId;

View File

@ -18,12 +18,12 @@ public abstract class MmsMessageRecord extends MessageRecord {
MmsMessageRecord(Context context, long id, Body body, Recipient conversationRecipient, MmsMessageRecord(Context context, long id, Body body, Recipient conversationRecipient,
Recipient individualRecipient, int recipientDeviceId, long dateSent, 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<IdentityKeyMismatch> mismatches, long type, List<IdentityKeyMismatch> mismatches,
List<NetworkFailure> networkFailures, int subscriptionId, long expiresIn, List<NetworkFailure> 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; this.slideDeck = slideDeck;
} }

View File

@ -47,15 +47,15 @@ public class NotificationMmsMessageRecord extends MmsMessageRecord {
public NotificationMmsMessageRecord(Context context, long id, Recipient conversationRecipient, public NotificationMmsMessageRecord(Context context, long id, Recipient conversationRecipient,
Recipient individualRecipient, int recipientDeviceId, Recipient individualRecipient, int recipientDeviceId,
long dateSent, long dateReceived, int receiptCount, long dateSent, long dateReceived, int deliveryReceiptCount,
long threadId, byte[] contentLocation, long messageSize, long threadId, byte[] contentLocation, long messageSize,
long expiry, int status, byte[] transactionId, long mailbox, 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, 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<IdentityKeyMismatch>(), new LinkedList<NetworkFailure>(), subscriptionId, new LinkedList<IdentityKeyMismatch>(), new LinkedList<NetworkFailure>(), subscriptionId,
0, 0, slideDeck); 0, 0, slideDeck, readReceiptCount);
this.contentLocation = contentLocation; this.contentLocation = contentLocation;
this.messageSize = messageSize; this.messageSize = messageSize;

View File

@ -44,15 +44,16 @@ public class SmsMessageRecord extends MessageRecord {
Recipient individualRecipient, Recipient individualRecipient,
int recipientDeviceId, int recipientDeviceId,
long dateSent, long dateReceived, long dateSent, long dateReceived,
int receiptCount, int deliveryReceiptCount,
long type, long threadId, long type, long threadId,
int status, List<IdentityKeyMismatch> mismatches, int status, List<IdentityKeyMismatch> mismatches,
int subscriptionId, long expiresIn, long expireStarted) int subscriptionId, long expiresIn, long expireStarted,
int readReceiptCount)
{ {
super(context, id, body, recipient, individualRecipient, recipientDeviceId, super(context, id, body, recipient, individualRecipient, recipientDeviceId,
dateSent, dateReceived, threadId, status, receiptCount, type, dateSent, dateReceived, threadId, status, deliveryReceiptCount, type,
mismatches, new LinkedList<NetworkFailure>(), subscriptionId, mismatches, new LinkedList<NetworkFailure>(), subscriptionId,
expiresIn, expireStarted); expiresIn, expireStarted, readReceiptCount);
} }
public long getType() { public long getType() {

View File

@ -50,10 +50,11 @@ public class ThreadRecord extends DisplayRecord {
public ThreadRecord(@NonNull Context context, @NonNull Body body, @Nullable Uri snippetUri, public ThreadRecord(@NonNull Context context, @NonNull Body body, @Nullable Uri snippetUri,
@NonNull Recipient recipient, long date, long count, boolean read, @NonNull Recipient recipient, long date, long count, boolean read,
long threadId, int receiptCount, int status, long snippetType, long threadId, int deliveryReceiptCount, int status, long snippetType,
int distributionType, boolean archived, long expiresIn, long lastSeen) 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.context = context.getApplicationContext();
this.snippetUri = snippetUri; this.snippetUri = snippetUri;
this.count = count; this.count = count;

View File

@ -10,7 +10,6 @@ import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
import org.thoughtcrime.securesms.jobs.AvatarDownloadJob; import org.thoughtcrime.securesms.jobs.AvatarDownloadJob;
import org.thoughtcrime.securesms.jobs.CleanPreKeysJob; import org.thoughtcrime.securesms.jobs.CleanPreKeysJob;
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob; import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
import org.thoughtcrime.securesms.jobs.DeliveryReceiptJob;
import org.thoughtcrime.securesms.jobs.GcmRefreshJob; import org.thoughtcrime.securesms.jobs.GcmRefreshJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob; 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.RetrieveProfileAvatarJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob; import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
import org.thoughtcrime.securesms.jobs.RotateSignedPreKeyJob; import org.thoughtcrime.securesms.jobs.RotateSignedPreKeyJob;
import org.thoughtcrime.securesms.jobs.SendReadReceiptJob;
import org.thoughtcrime.securesms.push.SecurityEventListener; import org.thoughtcrime.securesms.push.SecurityEventListener;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess; import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.service.MessageRetrievalService; import org.thoughtcrime.securesms.service.MessageRetrievalService;
@ -45,7 +45,6 @@ import dagger.Provides;
@Module(complete = false, injects = {CleanPreKeysJob.class, @Module(complete = false, injects = {CleanPreKeysJob.class,
CreateSignedPreKeyJob.class, CreateSignedPreKeyJob.class,
DeliveryReceiptJob.class,
PushGroupSendJob.class, PushGroupSendJob.class,
PushTextSendJob.class, PushTextSendJob.class,
PushMediaSendJob.class, PushMediaSendJob.class,
@ -69,48 +68,56 @@ import dagger.Provides;
MultiDeviceVerifiedUpdateJob.class, MultiDeviceVerifiedUpdateJob.class,
CreateProfileActivity.class, CreateProfileActivity.class,
RetrieveProfileAvatarJob.class, RetrieveProfileAvatarJob.class,
MultiDeviceProfileKeyUpdateJob.class}) MultiDeviceProfileKeyUpdateJob.class,
SendReadReceiptJob.class})
public class SignalCommunicationModule { public class SignalCommunicationModule {
private final Context context; private final Context context;
private final SignalServiceNetworkAccess networkAccess; private final SignalServiceNetworkAccess networkAccess;
private SignalServiceAccountManager accountManager;
private SignalServiceMessageSender messageSender;
private SignalServiceMessageReceiver messageReceiver;
public SignalCommunicationModule(Context context, SignalServiceNetworkAccess networkAccess) { public SignalCommunicationModule(Context context, SignalServiceNetworkAccess networkAccess) {
this.context = context; this.context = context;
this.networkAccess = networkAccess; this.networkAccess = networkAccess;
} }
@Provides SignalServiceAccountManager provideSignalAccountManager() { @Provides
return new SignalServiceAccountManager(networkAccess.getConfiguration(context), synchronized SignalServiceAccountManager provideSignalAccountManager() {
TextSecurePreferences.getLocalNumber(context), if (this.accountManager == null) {
TextSecurePreferences.getPushServerPassword(context), this.accountManager = new SignalServiceAccountManager(networkAccess.getConfiguration(context),
BuildConfig.USER_AGENT); new DynamicCredentialsProvider(context),
BuildConfig.USER_AGENT);
}
return this.accountManager;
} }
@Provides @Provides
SignalMessageSenderFactory provideSignalMessageSenderFactory() { synchronized SignalServiceMessageSender provideSignalMessageSender() {
return new SignalMessageSenderFactory() { if (this.messageSender == null) {
@Override this.messageSender = new SignalServiceMessageSender(networkAccess.getConfiguration(context),
public SignalServiceMessageSender create() { new DynamicCredentialsProvider(context),
return new SignalServiceMessageSender(networkAccess.getConfiguration(context), new SignalProtocolStoreImpl(context),
TextSecurePreferences.getLocalNumber(context), BuildConfig.USER_AGENT,
TextSecurePreferences.getPushServerPassword(context), Optional.fromNullable(MessageRetrievalService.getPipe()),
new SignalProtocolStoreImpl(context), Optional.<SignalServiceMessageSender.EventListener>of(new SecurityEventListener(context)));
BuildConfig.USER_AGENT, }
Optional.fromNullable(MessageRetrievalService.getPipe()),
Optional.<SignalServiceMessageSender.EventListener>of(new SecurityEventListener(context))); return this.messageSender;
}
};
} }
@Provides SignalServiceMessageReceiver provideSignalMessageReceiver() { @Provides
return new SignalServiceMessageReceiver(networkAccess.getConfiguration(context), synchronized SignalServiceMessageReceiver provideSignalMessageReceiver() {
new DynamicCredentialsProvider(context), if (this.messageReceiver == null) {
BuildConfig.USER_AGENT); this.messageReceiver = new SignalServiceMessageReceiver(networkAccess.getConfiguration(context),
} new DynamicCredentialsProvider(context),
BuildConfig.USER_AGENT);
}
public static interface SignalMessageSenderFactory { return this.messageReceiver;
public SignalServiceMessageSender create();
} }
private static class DynamicCredentialsProvider implements CredentialsProvider { private static class DynamicCredentialsProvider implements CredentialsProvider {

View File

@ -30,11 +30,9 @@ public class GcmBroadcastReceiver extends WakefulBroadcastReceiver {
return; return;
} }
String messageData = intent.getStringExtra("message");
String receiptData = intent.getStringExtra("receipt"); String receiptData = intent.getStringExtra("receipt");
if (!TextUtils.isEmpty(messageData)) handleReceivedMessage(context, messageData); if (!TextUtils.isEmpty(receiptData)) handleReceivedMessage(context, receiptData);
else if (!TextUtils.isEmpty(receiptData)) handleReceivedMessage(context, receiptData);
else if (intent.hasExtra("notification")) handleReceivedNotification(context); else if (intent.hasExtra("notification")) handleReceivedNotification(context);
} }
} }

View File

@ -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;
}
}

View File

@ -7,7 +7,6 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase.BlockedReader; import org.thoughtcrime.securesms.database.RecipientDatabase.BlockedReader;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.JobParameters;
@ -30,7 +29,7 @@ public class MultiDeviceBlockedUpdateJob extends MasterSecretJob implements Inje
private static final String TAG = MultiDeviceBlockedUpdateJob.class.getSimpleName(); private static final String TAG = MultiDeviceBlockedUpdateJob.class.getSimpleName();
@Inject transient SignalMessageSenderFactory messageSenderFactory; @Inject transient SignalServiceMessageSender messageSender;
public MultiDeviceBlockedUpdateJob(Context context) { public MultiDeviceBlockedUpdateJob(Context context) {
super(context, JobParameters.newBuilder() super(context, JobParameters.newBuilder()
@ -45,10 +44,9 @@ public class MultiDeviceBlockedUpdateJob extends MasterSecretJob implements Inje
public void onRun(MasterSecret masterSecret) public void onRun(MasterSecret masterSecret)
throws IOException, UntrustedIdentityException throws IOException, UntrustedIdentityException
{ {
RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context); RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context);
SignalServiceMessageSender messageSender = messageSenderFactory.create(); BlockedReader reader = database.readerForBlocked(database.getBlocked());
BlockedReader reader = database.readerForBlocked(database.getBlocked()); List<String> blocked = new LinkedList<>();
List<String> blocked = new LinkedList<>();
Recipient recipient; Recipient recipient;

View File

@ -18,7 +18,6 @@ import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Base64; 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(); private static final String TAG = MultiDeviceContactUpdateJob.class.getSimpleName();
@Inject transient SignalMessageSenderFactory messageSenderFactory; @Inject transient SignalServiceMessageSender messageSender;
private final @Nullable String address; private final @Nullable String address;
@ -90,8 +89,7 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje
private void generateSingleContactUpdate(@NonNull Address address) private void generateSingleContactUpdate(@NonNull Address address)
throws IOException, UntrustedIdentityException, NetworkException throws IOException, UntrustedIdentityException, NetworkException
{ {
SignalServiceMessageSender messageSender = messageSenderFactory.create(); File contactDataFile = createTempFile("multidevice-contact-update");
File contactDataFile = createTempFile("multidevice-contact-update");
try { try {
DeviceContactsOutputStream out = new DeviceContactsOutputStream(new FileOutputStream(contactDataFile)); DeviceContactsOutputStream out = new DeviceContactsOutputStream(new FileOutputStream(contactDataFile));
@ -119,8 +117,7 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje
private void generateFullContactUpdate() private void generateFullContactUpdate()
throws IOException, UntrustedIdentityException, NetworkException throws IOException, UntrustedIdentityException, NetworkException
{ {
SignalServiceMessageSender messageSender = messageSenderFactory.create(); File contactDataFile = createTempFile("multidevice-contact-update");
File contactDataFile = createTempFile("multidevice-contact-update");
try { try {
DeviceContactsOutputStream out = new DeviceContactsOutputStream(new FileOutputStream(contactDataFile)); DeviceContactsOutputStream out = new DeviceContactsOutputStream(new FileOutputStream(contactDataFile));

View File

@ -9,7 +9,6 @@ import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement; 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 long serialVersionUID = 1L;
private static final String TAG = MultiDeviceGroupUpdateJob.class.getSimpleName(); private static final String TAG = MultiDeviceGroupUpdateJob.class.getSimpleName();
@Inject @Inject transient SignalServiceMessageSender messageSender;
transient SignalCommunicationModule.SignalMessageSenderFactory messageSenderFactory;
public MultiDeviceGroupUpdateJob(Context context) { public MultiDeviceGroupUpdateJob(Context context) {
super(context, JobParameters.newBuilder() super(context, JobParameters.newBuilder()
@ -52,9 +50,8 @@ public class MultiDeviceGroupUpdateJob extends MasterSecretJob implements Inject
@Override @Override
public void onRun(MasterSecret masterSecret) throws Exception { public void onRun(MasterSecret masterSecret) throws Exception {
SignalServiceMessageSender messageSender = messageSenderFactory.create(); File contactDataFile = createTempFile("multidevice-contact-update");
File contactDataFile = createTempFile("multidevice-contact-update"); GroupDatabase.Reader reader = null;
GroupDatabase.Reader reader = null;
GroupDatabase.GroupRecord record; GroupDatabase.GroupRecord record;

View File

@ -7,11 +7,11 @@ import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement; import org.whispersystems.jobqueue.requirements.NetworkRequirement;
import org.whispersystems.libsignal.util.guava.Optional; 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.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream; 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 long serialVersionUID = 1L;
private static final String TAG = MultiDeviceProfileKeyUpdateJob.class.getSimpleName(); private static final String TAG = MultiDeviceProfileKeyUpdateJob.class.getSimpleName();
@Inject SignalMessageSenderFactory messageSender; @Inject transient SignalServiceMessageSender messageSender;
public MultiDeviceProfileKeyUpdateJob(Context context) { public MultiDeviceProfileKeyUpdateJob(Context context) {
super(context, JobParameters.newBuilder() super(context, JobParameters.newBuilder()
@ -71,7 +71,7 @@ public class MultiDeviceProfileKeyUpdateJob extends MasterSecretJob implements I
SignalServiceSyncMessage syncMessage = SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream, false)); SignalServiceSyncMessage syncMessage = SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream, false));
messageSender.create().sendMessage(syncMessage); messageSender.sendMessage(syncMessage);
} }
@Override @Override

View File

@ -6,7 +6,6 @@ import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.JobParameters;
@ -31,8 +30,7 @@ public class MultiDeviceReadUpdateJob extends MasterSecretJob implements Injecta
private final List<SerializableSyncMessageId> messageIds; private final List<SerializableSyncMessageId> messageIds;
@Inject @Inject transient SignalServiceMessageSender messageSender;
transient SignalCommunicationModule.SignalMessageSenderFactory messageSenderFactory;
public MultiDeviceReadUpdateJob(Context context, List<SyncMessageId> messageIds) { public MultiDeviceReadUpdateJob(Context context, List<SyncMessageId> messageIds) {
super(context, JobParameters.newBuilder() super(context, JobParameters.newBuilder()
@ -62,7 +60,6 @@ public class MultiDeviceReadUpdateJob extends MasterSecretJob implements Injecta
readMessages.add(new ReadMessage(messageId.sender, messageId.timestamp)); readMessages.add(new ReadMessage(messageId.sender, messageId.timestamp));
} }
SignalServiceMessageSender messageSender = messageSenderFactory.create();
messageSender.sendMessage(SignalServiceSyncMessage.forRead(readMessages)); messageSender.sendMessage(SignalServiceSyncMessage.forRead(readMessages));
} }

View File

@ -7,7 +7,6 @@ import android.util.Log;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus; import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement; 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(); private static final String TAG = MultiDeviceVerifiedUpdateJob.class.getSimpleName();
@Inject @Inject
transient SignalCommunicationModule.SignalMessageSenderFactory messageSenderFactory; transient SignalServiceMessageSender messageSender;
private final String destination; private final String destination;
private final byte[] identityKey; private final byte[] identityKey;
@ -65,7 +64,6 @@ public class MultiDeviceVerifiedUpdateJob extends ContextJob implements Injectab
Address canonicalDestination = Address.fromSerialized(destination); Address canonicalDestination = Address.fromSerialized(destination);
VerifiedMessage.VerifiedState verifiedState = getVerifiedState(verifiedStatus); VerifiedMessage.VerifiedState verifiedState = getVerifiedState(verifiedStatus);
SignalServiceMessageSender messageSender = messageSenderFactory.create();
VerifiedMessage verifiedMessage = new VerifiedMessage(canonicalDestination.toPhoneString(), new IdentityKey(identityKey, 0), verifiedState, timestamp); VerifiedMessage verifiedMessage = new VerifiedMessage(canonicalDestination.toPhoneString(), new IdentityKey(identityKey, 0), verifiedState, timestamp);
messageSender.sendMessage(SignalServiceSyncMessage.forVerified(verifiedMessage)); messageSender.sendMessage(SignalServiceSyncMessage.forVerified(verifiedMessage));

View File

@ -39,7 +39,7 @@ public class PushContentReceiveJob extends PushReceivedJob {
String sessionKey = TextSecurePreferences.getSignalingKey(context); String sessionKey = TextSecurePreferences.getSignalingKey(context);
SignalServiceEnvelope envelope = new SignalServiceEnvelope(data, sessionKey); SignalServiceEnvelope envelope = new SignalServiceEnvelope(data, sessionKey);
handle(envelope, true); handle(envelope);
} catch (IOException | InvalidVersionException e) { } catch (IOException | InvalidVersionException e) {
Log.w(TAG, e); Log.w(TAG, e);
} }

View File

@ -68,6 +68,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceContent;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup; 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.AnswerMessage;
import org.whispersystems.signalservice.api.messages.calls.BusyMessage; import org.whispersystems.signalservice.api.messages.calls.BusyMessage;
import org.whispersystems.signalservice.api.messages.calls.HangupMessage; 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.getIceUpdateMessages().isPresent()) handleCallIceUpdateMessage(envelope, message.getIceUpdateMessages().get());
else if (message.getHangupMessage().isPresent()) handleCallHangupMessage(envelope, message.getHangupMessage().get(), smsMessageId); else if (message.getHangupMessage().isPresent()) handleCallHangupMessage(envelope, message.getHangupMessage().get(), smsMessageId);
else if (message.getBusyMessage().isPresent()) handleCallBusyMessage(envelope, message.getBusyMessage().get()); 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 { } else {
Log.w(TAG, "Got unrecognized message..."); 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<InsertResult> insertPlaceholder(@NonNull SignalServiceEnvelope envelope) { private Optional<InsertResult> insertPlaceholder(@NonNull SignalServiceEnvelope envelope) {
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()), IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()),

View File

@ -43,15 +43,13 @@ import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import static org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory;
public class PushGroupSendJob extends PushSendJob implements InjectableType { public class PushGroupSendJob extends PushSendJob implements InjectableType {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final String TAG = PushGroupSendJob.class.getSimpleName(); private static final String TAG = PushGroupSendJob.class.getSimpleName();
@Inject transient SignalMessageSenderFactory messageSenderFactory; @Inject transient SignalServiceMessageSender messageSender;
private final long messageId; private final long messageId;
private final long filterRecipientId; // Deprecated private final long filterRecipientId; // Deprecated
@ -137,7 +135,6 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
throws IOException, RecipientFormattingException, InvalidNumberException, throws IOException, RecipientFormattingException, InvalidNumberException,
EncapsulatedExceptions, UndeliverableMessageException EncapsulatedExceptions, UndeliverableMessageException
{ {
SignalServiceMessageSender messageSender = messageSenderFactory.create();
String groupId = message.getRecipient().getAddress().toGroupString(); String groupId = message.getRecipient().getAddress().toGroupString();
Optional<byte[]> profileKey = getProfileKey(message.getRecipient()); Optional<byte[]> profileKey = getProfileKey(message.getRecipient());
List<Recipient> recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupId, false); List<Recipient> recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupId, false);

View File

@ -9,7 +9,6 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory;
import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.GroupUtil;
import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement; import org.whispersystems.jobqueue.requirements.NetworkRequirement;
@ -37,7 +36,7 @@ public class PushGroupUpdateJob extends ContextJob implements InjectableType {
private static final long serialVersionUID = 0L; private static final long serialVersionUID = 0L;
@Inject transient SignalMessageSenderFactory messageSenderFactory; @Inject transient SignalServiceMessageSender messageSender;
private final String source; private final String source;
private final byte[] groupId; private final byte[] groupId;
@ -59,10 +58,9 @@ public class PushGroupUpdateJob extends ContextJob implements InjectableType {
@Override @Override
public void onRun() throws IOException, UntrustedIdentityException { public void onRun() throws IOException, UntrustedIdentityException {
SignalServiceMessageSender messageSender = messageSenderFactory.create(); GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); Optional<GroupRecord> record = groupDatabase.getGroup(GroupUtil.getEncodedId(groupId, false));
Optional<GroupRecord> record = groupDatabase.getGroup(GroupUtil.getEncodedId(groupId, false)); SignalServiceAttachment avatar = null;
SignalServiceAttachment avatar = null;
if (record == null) { if (record == null) {
Log.w(TAG, "No information for group record info request: " + new String(groupId)); Log.w(TAG, "No information for group record info request: " + new String(groupId));

View File

@ -32,15 +32,13 @@ import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import static org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory;
public class PushMediaSendJob extends PushSendJob implements InjectableType { public class PushMediaSendJob extends PushSendJob implements InjectableType {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final String TAG = PushMediaSendJob.class.getSimpleName(); private static final String TAG = PushMediaSendJob.class.getSimpleName();
@Inject transient SignalMessageSenderFactory messageSenderFactory; @Inject transient SignalServiceMessageSender messageSender;
private final long messageId; private final long messageId;
@ -107,8 +105,6 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
throw new UndeliverableMessageException("No destination address."); throw new UndeliverableMessageException("No destination address.");
} }
SignalServiceMessageSender messageSender = messageSenderFactory.create();
try { try {
SignalServiceAddress address = getPushAddress(message.getRecipient().getAddress()); SignalServiceAddress address = getPushAddress(message.getRecipient().getAddress());
MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints(); MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints();

View File

@ -4,7 +4,6 @@ import android.content.Context;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement; import org.whispersystems.jobqueue.requirements.NetworkRequirement;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
@ -37,7 +36,7 @@ public class PushNotificationReceiveJob extends PushReceivedJob implements Injec
receiver.retrieveMessages(new SignalServiceMessageReceiver.MessageReceivedCallback() { receiver.retrieveMessages(new SignalServiceMessageReceiver.MessageReceivedCallback() {
@Override @Override
public void onMessage(SignalServiceEnvelope envelope) { public void onMessage(SignalServiceEnvelope envelope) {
handle(envelope, false); handle(envelope);
} }
}); });
} }

View File

@ -9,17 +9,12 @@ import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.jobqueue.JobManager; import org.whispersystems.jobqueue.JobManager;
import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import java.util.LinkedList;
public abstract class PushReceivedJob extends ContextJob { public abstract class PushReceivedJob extends ContextJob {
private static final String TAG = PushReceivedJob.class.getSimpleName(); private static final String TAG = PushReceivedJob.class.getSimpleName();
@ -28,7 +23,7 @@ public abstract class PushReceivedJob extends ContextJob {
super(context, parameters); super(context, parameters);
} }
public void handle(SignalServiceEnvelope envelope, boolean sendExplicitReceipt) { public void handle(SignalServiceEnvelope envelope) {
Address source = Address.fromExternal(context, envelope.getSource()); Address source = Address.fromExternal(context, envelope.getSource());
Recipient recipient = Recipient.from(context, source, false); Recipient recipient = Recipient.from(context, source, false);
@ -40,13 +35,13 @@ public abstract class PushReceivedJob extends ContextJob {
if (envelope.isReceipt()) { if (envelope.isReceipt()) {
handleReceipt(envelope); handleReceipt(envelope);
} else if (envelope.isPreKeySignalMessage() || envelope.isSignalMessage()) { } else if (envelope.isPreKeySignalMessage() || envelope.isSignalMessage()) {
handleMessage(envelope, source, sendExplicitReceipt); handleMessage(envelope, source);
} else { } else {
Log.w(TAG, "Received envelope of unknown type: " + envelope.getType()); 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); Recipient recipients = Recipient.from(context, source, false);
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
@ -56,12 +51,6 @@ public abstract class PushReceivedJob extends ContextJob {
} else { } else {
Log.w(TAG, "*** Received blocked push message, ignoring..."); 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) { private void handleReceipt(SignalServiceEnvelope envelope) {

View File

@ -27,15 +27,13 @@ import java.io.IOException;
import javax.inject.Inject; import javax.inject.Inject;
import static org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory;
public class PushTextSendJob extends PushSendJob implements InjectableType { public class PushTextSendJob extends PushSendJob implements InjectableType {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final String TAG = PushTextSendJob.class.getSimpleName(); private static final String TAG = PushTextSendJob.class.getSimpleName();
@Inject transient SignalMessageSenderFactory messageSenderFactory; @Inject transient SignalServiceMessageSender messageSender;
private final long messageId; private final long messageId;
@ -101,7 +99,6 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
{ {
try { try {
SignalServiceAddress address = getPushAddress(message.getIndividualRecipient().getAddress()); SignalServiceAddress address = getPushAddress(message.getIndividualRecipient().getAddress());
SignalServiceMessageSender messageSender = messageSenderFactory.create();
Optional<byte[]> profileKey = getProfileKey(message.getIndividualRecipient()); Optional<byte[]> profileKey = getProfileKey(message.getIndividualRecipient());
SignalServiceDataMessage textSecureMessage = SignalServiceDataMessage.newBuilder() SignalServiceDataMessage textSecureMessage = SignalServiceDataMessage.newBuilder()
.withTimestamp(message.getDateSent()) .withTimestamp(message.getDateSent())

View File

@ -4,7 +4,6 @@ import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory;
import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement; import org.whispersystems.jobqueue.requirements.NetworkRequirement;
import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.SignalServiceMessageSender;
@ -25,7 +24,7 @@ public class RequestGroupInfoJob extends ContextJob implements InjectableType {
private static final long serialVersionUID = 0L; private static final long serialVersionUID = 0L;
@Inject transient SignalMessageSenderFactory messageSenderFactory; @Inject transient SignalServiceMessageSender messageSender;
private final String source; private final String source;
private final byte[] groupId; private final byte[] groupId;
@ -46,16 +45,14 @@ public class RequestGroupInfoJob extends ContextJob implements InjectableType {
@Override @Override
public void onRun() throws IOException, UntrustedIdentityException { 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) SignalServiceDataMessage message = SignalServiceDataMessage.newBuilder()
.withId(groupId) .asGroupMessage(group)
.build(); .withTimestamp(System.currentTimeMillis())
.build();
SignalServiceDataMessage message = SignalServiceDataMessage.newBuilder()
.asGroupMessage(group)
.withTimestamp(System.currentTimeMillis())
.build();
messageSender.sendMessage(new SignalServiceAddress(source), message); messageSender.sendMessage(new SignalServiceAddress(source), message);
} }

View File

@ -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<Long> messageIds;
private final long timestamp;
public SendReadReceiptJob(Context context, Address address, List<Long> 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);
}
}

View File

@ -8,17 +8,23 @@ import android.support.annotation.Nullable;
import android.support.v4.app.NotificationManagerCompat; import android.support.v4.app.NotificationManagerCompat;
import android.util.Log; import android.util.Log;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.ExpirationInfo; import org.thoughtcrime.securesms.database.MessagingDatabase.ExpirationInfo;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob;
import org.thoughtcrime.securesms.jobs.SendReadReceiptJob;
import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
public class MarkReadReceiver extends MasterSecretBroadcastReceiver { public class MarkReadReceiver extends MasterSecretBroadcastReceiver {
@ -72,6 +78,18 @@ public class MarkReadReceiver extends MasterSecretBroadcastReceiver {
ApplicationContext.getInstance(context) ApplicationContext.getInstance(context)
.getJobManager() .getJobManager()
.add(new MultiDeviceReadUpdateJob(context, syncMessageIds)); .add(new MultiDeviceReadUpdateJob(context, syncMessageIds));
Map<Address, List<SyncMessageId>> addressMap = Stream.of(markedReadMessages)
.map(MarkedMessageInfo::getSyncMessageId)
.collect(Collectors.groupingBy(SyncMessageId::getAddress));
for (Address address : addressMap.keySet()) {
List<Long> 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) { private static void scheduleDeletion(Context context, ExpirationInfo expirationInfo) {

View File

@ -214,7 +214,7 @@ public class MessageRetrievalService extends Service implements InjectableType,
Log.w(TAG, "Retrieved envelope! " + envelope.getSource()); Log.w(TAG, "Retrieved envelope! " + envelope.getSource());
PushContentReceiveJob receiveJob = new PushContentReceiveJob(MessageRetrievalService.this); PushContentReceiveJob receiveJob = new PushContentReceiveJob(MessageRetrievalService.this);
receiveJob.handle(envelope, false); receiveJob.handle(envelope);
decrementPushReceived(); decrementPushReceived();
} }

View File

@ -29,7 +29,6 @@ import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory;
import org.thoughtcrime.securesms.events.WebRtcViewModel; import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
@ -148,10 +147,9 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
private boolean remoteVideoEnabled = false; private boolean remoteVideoEnabled = false;
private boolean bluetoothAvailable = false; private boolean bluetoothAvailable = false;
@Inject public SignalMessageSenderFactory messageSenderFactory; @Inject public SignalServiceMessageSender messageSender;
@Inject public SignalServiceAccountManager accountManager; @Inject public SignalServiceAccountManager accountManager;
private SignalServiceMessageSender messageSender;
private PeerConnectionFactory peerConnectionFactory; private PeerConnectionFactory peerConnectionFactory;
private SignalAudioManager audioManager; private SignalAudioManager audioManager;
private BluetoothStateManager bluetoothStateManager; private BluetoothStateManager bluetoothStateManager;
@ -271,7 +269,6 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
this.peerConnectionFactory = new PeerConnectionFactory(new PeerConnectionFactoryOptions()); this.peerConnectionFactory = new PeerConnectionFactory(new PeerConnectionFactoryOptions());
this.audioManager = new SignalAudioManager(this); this.audioManager = new SignalAudioManager(this);
this.bluetoothStateManager = new BluetoothStateManager(this, this); this.bluetoothStateManager = new BluetoothStateManager(this, this);
this.messageSender = messageSenderFactory.create();
this.messageSender.setSoTimeoutMillis(TimeUnit.SECONDS.toMillis(10)); this.messageSender.setSoTimeoutMillis(TimeUnit.SECONDS.toMillis(10));
this.accountManager.setSoTimeoutMillis(TimeUnit.SECONDS.toMillis(10)); this.accountManager.setSoTimeoutMillis(TimeUnit.SECONDS.toMillis(10));
} }

View File

@ -113,6 +113,15 @@ public class TextSecurePreferences {
private static final String ALWAYS_RELAY_CALLS_PREF = "pref_turn_only"; 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_KEY_PREF = "pref_profile_key";
private static final String PROFILE_NAME_PREF = "pref_profile_name"; 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) { public static @Nullable String getProfileKey(Context context) {
return getStringPreference(context, PROFILE_KEY_PREF, null); return getStringPreference(context, PROFILE_KEY_PREF, null);

View File

@ -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<SignalServiceAddress> 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;
}
};
}
}
}