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

View File

@ -27,6 +27,18 @@
android:paddingBottom="2dp"
android:visibility="gone"
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"/>
</merge>

View File

@ -1456,7 +1456,9 @@
<!-- transport_selection_list_item -->
<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 -->

View File

@ -40,6 +40,12 @@
android:title="@string/preferences_advanced__always_relay_calls"
android:summary="@string/preferences_advanced__relay_all_calls_through_the_signal_server_to_avoid_revealing_your_ip_address"/>
<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"
android:title="@string/preferences_app_protection__blocked_contacts" />
</PreferenceCategory>

View File

@ -445,6 +445,7 @@ public class ConversationItem extends LinearLayout
if (!messageRecord.isOutgoing()) deliveryStatusIndicator.setNone();
else if (messageRecord.isPending()) deliveryStatusIndicator.setPending();
else if (messageRecord.isRemoteRead()) deliveryStatusIndicator.setRead();
else if (messageRecord.isDelivered()) deliveryStatusIndicator.setDelivered();
else deliveryStatusIndicator.setSent();
}

View File

@ -215,6 +215,7 @@ public class ConversationListItem extends RelativeLayout
alertView.setNone();
if (thread.isPending()) deliveryStatusIndicator.setPending();
else if (thread.isRemoteRead()) deliveryStatusIndicator.setRead();
else if (thread.isDelivered()) deliveryStatusIndicator.setDelivered();
else deliveryStatusIndicator.setSent();
}

View File

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

View File

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

View File

@ -106,7 +106,8 @@ public class DatabaseFactory {
private static final int PROFILES = 41;
private static final int PROFILE_SHARING_APPROVAL = 42;
private static final int UNSEEN_NUMBER_OFFER = 43;
private static final int DATABASE_VERSION = 43;
private static final int READ_RECEIPTS = 44;
private static final int DATABASE_VERSION = 44;
private static final String DATABASE_NAME = "messages.db";
private static final Object lock = new Object();
@ -1321,6 +1322,12 @@ public class DatabaseFactory {
db.execSQL("ALTER TABLE thread ADD COLUMN has_sent INTEGER DEFAULT 0");
}
if (oldVersion < READ_RECEIPTS) {
db.execSQL("ALTER TABLE sms ADD COLUMN read_receipt_count INTEGER DEFAULT 0");
db.execSQL("ATLER TABLE mms ADD COLUMN read_receipt_count INTEGER DEFAULT 0");
db.execSQL("ALTER TABLE thread ADD COLUMN read_receipt_count INTEGER DEFAULT 0");
}
db.setTransactionSuccessful();
db.endTransaction();
}

View File

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

View File

@ -11,7 +11,8 @@ public interface MmsSmsColumns {
public static final String BODY = "body";
public static final String ADDRESS = "address";
public static final String ADDRESS_DEVICE_ID = "address_device_id";
public static final String RECEIPT_COUNT = "delivery_receipt_count";
public static final String DELIVERY_RECEIPT_COUNT = "delivery_receipt_count";
public static final String READ_RECEIPT_COUNT = "read_receipt_count";
public static final String MISMATCHED_IDENTITIES = "mismatched_identities";
public static final String UNIQUE_ROW_ID = "unique_row_id";
public static final String SUBSCRIPTION_ID = "subscription_id";

View File

@ -25,7 +25,6 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
import org.thoughtcrime.securesms.database.model.MessageRecord;
@ -52,7 +51,9 @@ public class MmsSmsDatabase extends Database {
SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT,
MmsDatabase.STATUS,
MmsSmsColumns.DELIVERY_RECEIPT_COUNT,
MmsSmsColumns.READ_RECEIPT_COUNT,
MmsSmsColumns.MISMATCHED_IDENTITIES,
MmsDatabase.NETWORK_FAILURE,
MmsSmsColumns.SUBSCRIPTION_ID,
@ -137,8 +138,13 @@ public class MmsSmsDatabase extends Database {
}
public void incrementDeliveryReceiptCount(SyncMessageId syncMessageId) {
DatabaseFactory.getSmsDatabase(context).incrementDeliveryReceiptCount(syncMessageId);
DatabaseFactory.getMmsDatabase(context).incrementDeliveryReceiptCount(syncMessageId);
DatabaseFactory.getSmsDatabase(context).incrementReceiptCount(syncMessageId, true, false);
DatabaseFactory.getMmsDatabase(context).incrementReceiptCount(syncMessageId, true, false);
}
public void incrementReadReceiptCount(SyncMessageId syncMessageId) {
DatabaseFactory.getSmsDatabase(context).incrementReceiptCount(syncMessageId, false, true);
DatabaseFactory.getMmsDatabase(context).incrementReceiptCount(syncMessageId, false, true);
}
private Cursor queryTables(String[] projection, String selection, String order, String limit) {
@ -154,7 +160,8 @@ public class MmsSmsDatabase extends Database {
MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
MmsSmsColumns.RECEIPT_COUNT, MmsSmsColumns.MISMATCHED_IDENTITIES,
MmsSmsColumns.DELIVERY_RECEIPT_COUNT, MmsSmsColumns.READ_RECEIPT_COUNT,
MmsSmsColumns.MISMATCHED_IDENTITIES,
MmsSmsColumns.SUBSCRIPTION_ID, MmsSmsColumns.EXPIRES_IN, MmsSmsColumns.EXPIRE_STARTED,
MmsSmsColumns.NOTIFIED,
MmsDatabase.NETWORK_FAILURE, TRANSPORT,
@ -185,7 +192,8 @@ public class MmsSmsDatabase extends Database {
MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
MmsSmsColumns.RECEIPT_COUNT, MmsSmsColumns.MISMATCHED_IDENTITIES,
MmsSmsColumns.DELIVERY_RECEIPT_COUNT, MmsSmsColumns.READ_RECEIPT_COUNT,
MmsSmsColumns.MISMATCHED_IDENTITIES,
MmsSmsColumns.SUBSCRIPTION_ID, MmsSmsColumns.EXPIRES_IN, MmsSmsColumns.EXPIRE_STARTED,
MmsSmsColumns.NOTIFIED,
MmsDatabase.NETWORK_FAILURE, TRANSPORT,
@ -227,7 +235,8 @@ public class MmsSmsDatabase extends Database {
mmsColumnsPresent.add(MmsSmsColumns.BODY);
mmsColumnsPresent.add(MmsSmsColumns.ADDRESS);
mmsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID);
mmsColumnsPresent.add(MmsSmsColumns.RECEIPT_COUNT);
mmsColumnsPresent.add(MmsSmsColumns.DELIVERY_RECEIPT_COUNT);
mmsColumnsPresent.add(MmsSmsColumns.READ_RECEIPT_COUNT);
mmsColumnsPresent.add(MmsSmsColumns.MISMATCHED_IDENTITIES);
mmsColumnsPresent.add(MmsSmsColumns.SUBSCRIPTION_ID);
mmsColumnsPresent.add(MmsSmsColumns.EXPIRES_IN);
@ -268,7 +277,8 @@ public class MmsSmsDatabase extends Database {
smsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID);
smsColumnsPresent.add(MmsSmsColumns.READ);
smsColumnsPresent.add(MmsSmsColumns.THREAD_ID);
smsColumnsPresent.add(MmsSmsColumns.RECEIPT_COUNT);
smsColumnsPresent.add(MmsSmsColumns.DELIVERY_RECEIPT_COUNT);
smsColumnsPresent.add(MmsSmsColumns.READ_RECEIPT_COUNT);
smsColumnsPresent.add(MmsSmsColumns.MISMATCHED_IDENTITIES);
smsColumnsPresent.add(MmsSmsColumns.SUBSCRIPTION_ID);
smsColumnsPresent.add(MmsSmsColumns.EXPIRES_IN);

View File

@ -39,6 +39,7 @@ import org.thoughtcrime.securesms.sms.IncomingGroupMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.JsonUtils;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.jobqueue.JobManager;
import org.whispersystems.libsignal.util.guava.Optional;
@ -73,9 +74,10 @@ public class SmsDatabase extends MessagingDatabase {
THREAD_ID + " INTEGER, " + ADDRESS + " TEXT, " + ADDRESS_DEVICE_ID + " INTEGER DEFAULT 1, " + PERSON + " INTEGER, " +
DATE_RECEIVED + " INTEGER, " + DATE_SENT + " INTEGER, " + PROTOCOL + " INTEGER, " + READ + " INTEGER DEFAULT 0, " +
STATUS + " INTEGER DEFAULT -1," + TYPE + " INTEGER, " + REPLY_PATH_PRESENT + " INTEGER, " +
RECEIPT_COUNT + " INTEGER DEFAULT 0," + SUBJECT + " TEXT, " + BODY + " TEXT, " +
DELIVERY_RECEIPT_COUNT + " INTEGER DEFAULT 0," + SUBJECT + " TEXT, " + BODY + " TEXT, " +
MISMATCHED_IDENTITIES + " TEXT DEFAULT NULL, " + SERVICE_CENTER + " TEXT, " + SUBSCRIPTION_ID + " INTEGER DEFAULT -1, " +
EXPIRES_IN + " INTEGER DEFAULT 0, " + EXPIRE_STARTED + " INTEGER DEFAULT 0, " + NOTIFIED + " DEFAULT 0);";
EXPIRES_IN + " INTEGER DEFAULT 0, " + EXPIRE_STARTED + " INTEGER DEFAULT 0, " + NOTIFIED + " DEFAULT 0, " +
READ_RECEIPT_COUNT + " INTEGER DEFAULT 0);";
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS sms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
@ -91,12 +93,14 @@ public class SmsDatabase extends MessagingDatabase {
DATE_RECEIVED + " AS " + NORMALIZED_DATE_RECEIVED,
DATE_SENT + " AS " + NORMALIZED_DATE_SENT,
PROTOCOL, READ, STATUS, TYPE,
REPLY_PATH_PRESENT, SUBJECT, BODY, SERVICE_CENTER, RECEIPT_COUNT,
REPLY_PATH_PRESENT, SUBJECT, BODY, SERVICE_CENTER, DELIVERY_RECEIPT_COUNT,
MISMATCHED_IDENTITIES, SUBSCRIPTION_ID, EXPIRES_IN, EXPIRE_STARTED,
NOTIFIED
NOTIFIED, READ_RECEIPT_COUNT
};
private static final EarlyReceiptCache earlyReceiptCache = new EarlyReceiptCache();
private static final EarlyReceiptCache earlyDeliveryReceiptCache = new EarlyReceiptCache();
private static final EarlyReceiptCache earlyReadReceiptCache = new EarlyReceiptCache();
private final JobManager jobManager;
public SmsDatabase(Context context, SQLiteOpenHelper databaseHelper) {
@ -277,7 +281,7 @@ public class SmsDatabase extends MessagingDatabase {
database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(id)});
}
public void incrementDeliveryReceiptCount(SyncMessageId messageId) {
public void incrementReceiptCount(SyncMessageId messageId, boolean deliveryReceipt, boolean readReceipt) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
Cursor cursor = null;
boolean foundMessage = false;
@ -291,12 +295,13 @@ public class SmsDatabase extends MessagingDatabase {
if (Types.isOutgoingMessageType(cursor.getLong(cursor.getColumnIndexOrThrow(TYPE)))) {
Address theirAddress = messageId.getAddress();
Address ourAddress = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)));
String columnName = deliveryReceipt ? DELIVERY_RECEIPT_COUNT : READ_RECEIPT_COUNT;
if (ourAddress.equals(theirAddress)) {
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
database.execSQL("UPDATE " + TABLE_NAME +
" SET " + RECEIPT_COUNT + " = " + RECEIPT_COUNT + " + 1 WHERE " +
" SET " + columnName + " = " + columnName + " + 1 WHERE " +
ID + " = ?",
new String[] {String.valueOf(cursor.getLong(cursor.getColumnIndexOrThrow(ID)))});
@ -308,7 +313,8 @@ public class SmsDatabase extends MessagingDatabase {
}
if (!foundMessage) {
earlyReceiptCache.increment(messageId.getTimetamp(), messageId.getAddress());
if (deliveryReceipt) earlyDeliveryReceiptCache.increment(messageId.getTimetamp(), messageId.getAddress());
if (readReceipt) earlyReadReceiptCache.increment(messageId.getTimetamp(), messageId.getAddress());
}
} finally {
@ -600,7 +606,8 @@ public class SmsDatabase extends MessagingDatabase {
contentValues.put(TYPE, type);
contentValues.put(SUBSCRIPTION_ID, message.getSubscriptionId());
contentValues.put(EXPIRES_IN, message.getExpiresIn());
contentValues.put(RECEIPT_COUNT, earlyReceiptCache.remove(date, address));
contentValues.put(DELIVERY_RECEIPT_COUNT, earlyDeliveryReceiptCache.remove(date, address));
contentValues.put(READ_RECEIPT_COUNT, earlyReadReceiptCache.remove(date, address));
SQLiteDatabase db = databaseHelper.getWritableDatabase();
long messageId = db.insert(TABLE_NAME, ADDRESS, contentValues);
@ -789,7 +796,7 @@ public class SmsDatabase extends MessagingDatabase {
0, message.isSecureMessage() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(),
threadId, 0, new LinkedList<IdentityKeyMismatch>(),
message.getSubscriptionId(), message.getExpiresIn(),
System.currentTimeMillis());
System.currentTimeMillis(), 0);
}
}
@ -822,12 +829,17 @@ public class SmsDatabase extends MessagingDatabase {
long dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.NORMALIZED_DATE_SENT));
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.THREAD_ID));
int status = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.STATUS));
int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.RECEIPT_COUNT));
int deliveryReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.DELIVERY_RECEIPT_COUNT));
int readReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.READ_RECEIPT_COUNT));
String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.MISMATCHED_IDENTITIES));
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.SUBSCRIPTION_ID));
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.EXPIRES_IN));
long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.EXPIRE_STARTED));
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
readReceiptCount = 0;
}
List<IdentityKeyMismatch> mismatches = getMismatches(mismatchDocument);
Recipient recipient = Recipient.from(context, address, true);
DisplayRecord.Body body = getBody(cursor);
@ -835,9 +847,9 @@ public class SmsDatabase extends MessagingDatabase {
return new SmsMessageRecord(context, messageId, body, recipient,
recipient,
addressDeviceId,
dateSent, dateReceived, receiptCount, type,
dateSent, dateReceived, deliveryReceiptCount, type,
threadId, status, mismatches, subscriptionId,
expiresIn, expireStarted);
expiresIn, expireStarted, readReceiptCount);
}
private List<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.recipients.Recipient;
import org.thoughtcrime.securesms.util.DelimiterUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.util.Pair;
@ -70,7 +71,8 @@ public class ThreadDatabase extends Database {
public static final String SNIPPET_URI = "snippet_uri";
public static final String ARCHIVED = "archived";
public static final String STATUS = "status";
public static final String RECEIPT_COUNT = "delivery_receipt_count";
public static final String DELIVERY_RECEIPT_COUNT = "delivery_receipt_count";
public static final String READ_RECEIPT_COUNT = "read_receipt_count";
public static final String EXPIRES_IN = "expires_in";
public static final String LAST_SEEN = "last_seen";
private static final String HAS_SENT = "has_sent";
@ -82,8 +84,9 @@ public class ThreadDatabase extends Database {
TYPE + " INTEGER DEFAULT 0, " + ERROR + " INTEGER DEFAULT 0, " +
SNIPPET_TYPE + " INTEGER DEFAULT 0, " + SNIPPET_URI + " TEXT DEFAULT NULL, " +
ARCHIVED + " INTEGER DEFAULT 0, " + STATUS + " INTEGER DEFAULT 0, " +
RECEIPT_COUNT + " INTEGER DEFAULT 0, " + EXPIRES_IN + " INTEGER DEFAULT 0, " +
LAST_SEEN + " INTEGER DEFAULT 0, " + HAS_SENT + " INTEGER DEFAULT 0);";
DELIVERY_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + EXPIRES_IN + " INTEGER DEFAULT 0, " +
LAST_SEEN + " INTEGER DEFAULT 0, " + HAS_SENT + " INTEGER DEFAULT 0, " +
READ_RECEIPT_COUNT + " INTEGER DEFAULT 0);";
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + ADDRESS + ");",
@ -92,7 +95,7 @@ public class ThreadDatabase extends Database {
private static final String[] THREAD_PROJECTION = {
ID, DATE, MESSAGE_COUNT, ADDRESS, SNIPPET, SNIPPET_CHARSET, READ, TYPE, ERROR, SNIPPET_TYPE,
SNIPPET_URI, ARCHIVED, STATUS, RECEIPT_COUNT, EXPIRES_IN, LAST_SEEN
SNIPPET_URI, ARCHIVED, STATUS, DELIVERY_RECEIPT_COUNT, EXPIRES_IN, LAST_SEEN, READ_RECEIPT_COUNT
};
private static final List<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,
long date, int status, int receiptCount, long type, boolean unarchive,
long expiresIn)
long date, int status, int deliveryReceiptCount, long type, boolean unarchive,
long expiresIn, int readReceiptCount)
{
ContentValues contentValues = new ContentValues(7);
contentValues.put(DATE, date - date % 1000);
@ -135,7 +138,8 @@ public class ThreadDatabase extends Database {
contentValues.put(SNIPPET_URI, attachment == null ? null : attachment.toString());
contentValues.put(SNIPPET_TYPE, type);
contentValues.put(STATUS, status);
contentValues.put(RECEIPT_COUNT, receiptCount);
contentValues.put(DELIVERY_RECEIPT_COUNT, deliveryReceiptCount);
contentValues.put(READ_RECEIPT_COUNT, readReceiptCount);
contentValues.put(EXPIRES_IN, expiresIn);
if (unarchive) {
@ -550,8 +554,8 @@ public class ThreadDatabase extends Database {
if (reader != null && (record = reader.getNext()) != null) {
updateThread(threadId, count, record.getBody().getBody(), getAttachmentUriFor(record),
record.getTimestamp(), record.getDeliveryStatus(), record.getReceiptCount(),
record.getType(), unarchive, record.getExpiresIn());
record.getTimestamp(), record.getDeliveryStatus(), record.getDeliveryReceiptCount(),
record.getType(), unarchive, record.getExpiresIn(), record.getReadReceiptCount());
notifyConversationListListeners();
return false;
} else {
@ -641,14 +645,19 @@ public class ThreadDatabase extends Database {
long type = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_TYPE));
boolean archived = cursor.getInt(cursor.getColumnIndex(ThreadDatabase.ARCHIVED)) != 0;
int status = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.STATUS));
int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.RECEIPT_COUNT));
int deliveryReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.DELIVERY_RECEIPT_COUNT));
int readReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.READ_RECEIPT_COUNT));
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.EXPIRES_IN));
long lastSeen = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.LAST_SEEN));
Uri snippetUri = getSnippetUri(cursor);
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
readReceiptCount = 0;
}
return new ThreadRecord(context, body, snippetUri, recipient, date, count, read == 1,
threadId, receiptCount, status, type, distributionType, archived,
expiresIn, lastSeen);
threadId, deliveryReceiptCount, status, type, distributionType, archived,
expiresIn, lastSeen, readReceiptCount);
}
private DisplayRecord.Body getPlaintextBody(Cursor cursor) {

View File

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

View File

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

View File

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

View File

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

View File

@ -18,12 +18,12 @@ public abstract class MmsMessageRecord extends MessageRecord {
MmsMessageRecord(Context context, long id, Body body, Recipient conversationRecipient,
Recipient individualRecipient, int recipientDeviceId, long dateSent,
long dateReceived, long threadId, int deliveryStatus, int receiptCount,
long dateReceived, long threadId, int deliveryStatus, int deliveryReceiptCount,
long type, List<IdentityKeyMismatch> mismatches,
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;
}

View File

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

View File

@ -44,15 +44,16 @@ public class SmsMessageRecord extends MessageRecord {
Recipient individualRecipient,
int recipientDeviceId,
long dateSent, long dateReceived,
int receiptCount,
int deliveryReceiptCount,
long type, long threadId,
int status, List<IdentityKeyMismatch> mismatches,
int subscriptionId, long expiresIn, long expireStarted)
int subscriptionId, long expiresIn, long expireStarted,
int readReceiptCount)
{
super(context, id, body, recipient, individualRecipient, recipientDeviceId,
dateSent, dateReceived, threadId, status, receiptCount, type,
dateSent, dateReceived, threadId, status, deliveryReceiptCount, type,
mismatches, new LinkedList<NetworkFailure>(), subscriptionId,
expiresIn, expireStarted);
expiresIn, expireStarted, readReceiptCount);
}
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,
@NonNull Recipient recipient, long date, long count, boolean read,
long threadId, int receiptCount, int status, long snippetType,
int distributionType, boolean archived, long expiresIn, long lastSeen)
long threadId, int deliveryReceiptCount, int status, long snippetType,
int distributionType, boolean archived, long expiresIn, long lastSeen,
int readReceiptCount)
{
super(context, body, recipient, date, date, threadId, status, receiptCount, snippetType);
super(context, body, recipient, date, date, threadId, status, deliveryReceiptCount, snippetType, readReceiptCount);
this.context = context.getApplicationContext();
this.snippetUri = snippetUri;
this.count = count;

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
import org.whispersystems.signalservice.api.messages.calls.AnswerMessage;
import org.whispersystems.signalservice.api.messages.calls.BusyMessage;
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
@ -186,6 +187,11 @@ public class PushDecryptJob extends ContextJob {
else if (message.getIceUpdateMessages().isPresent()) handleCallIceUpdateMessage(envelope, message.getIceUpdateMessages().get());
else if (message.getHangupMessage().isPresent()) handleCallHangupMessage(envelope, message.getHangupMessage().get(), smsMessageId);
else if (message.getBusyMessage().isPresent()) handleCallBusyMessage(envelope, message.getBusyMessage().get());
} else if (content.getReceiptMessage().isPresent()) {
SignalServiceReceiptMessage message = content.getReceiptMessage().get();
if (message.isReadReceipt()) handleReadReceipt(envelope, message);
else if (message.isDeliveryReceipt()) handleDeliveryReceipt(envelope, message);
} else {
Log.w(TAG, "Got unrecognized message...");
}
@ -824,6 +830,29 @@ public class PushDecryptJob extends ContextJob {
}
}
private void handleDeliveryReceipt(@NonNull SignalServiceEnvelope envelope,
@NonNull SignalServiceReceiptMessage message)
{
for (long timestamp : message.getTimestamps()) {
Log.w(TAG, String.format("Received encrypted delivery receipt: (XXXXX, %d)", timestamp));
DatabaseFactory.getMmsSmsDatabase(context)
.incrementDeliveryReceiptCount(new SyncMessageId(Address.fromExternal(context, envelope.getSource()), timestamp));
}
}
private void handleReadReceipt(@NonNull SignalServiceEnvelope envelope,
@NonNull SignalServiceReceiptMessage message)
{
if (TextSecurePreferences.isReadReceiptsEnabled(context)) {
for (long timestamp : message.getTimestamps()) {
Log.w(TAG, String.format("Received encrypted read receipt: (XXXXX, %d)", timestamp));
DatabaseFactory.getMmsSmsDatabase(context)
.incrementReadReceiptCount(new SyncMessageId(Address.fromExternal(context, envelope.getSource()), timestamp));
}
}
}
private Optional<InsertResult> insertPlaceholder(@NonNull SignalServiceEnvelope envelope) {
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()),

View File

@ -43,15 +43,13 @@ import java.util.List;
import javax.inject.Inject;
import static org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory;
public class PushGroupSendJob extends PushSendJob implements InjectableType {
private static final long serialVersionUID = 1L;
private static final String TAG = PushGroupSendJob.class.getSimpleName();
@Inject transient SignalMessageSenderFactory messageSenderFactory;
@Inject transient SignalServiceMessageSender messageSender;
private final long messageId;
private final long filterRecipientId; // Deprecated
@ -137,7 +135,6 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
throws IOException, RecipientFormattingException, InvalidNumberException,
EncapsulatedExceptions, UndeliverableMessageException
{
SignalServiceMessageSender messageSender = messageSenderFactory.create();
String groupId = message.getRecipient().getAddress().toGroupString();
Optional<byte[]> profileKey = getProfileKey(message.getRecipient());
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.GroupRecord;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
@ -37,7 +36,7 @@ public class PushGroupUpdateJob extends ContextJob implements InjectableType {
private static final long serialVersionUID = 0L;
@Inject transient SignalMessageSenderFactory messageSenderFactory;
@Inject transient SignalServiceMessageSender messageSender;
private final String source;
private final byte[] groupId;
@ -59,7 +58,6 @@ public class PushGroupUpdateJob extends ContextJob implements InjectableType {
@Override
public void onRun() throws IOException, UntrustedIdentityException {
SignalServiceMessageSender messageSender = messageSenderFactory.create();
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
Optional<GroupRecord> record = groupDatabase.getGroup(GroupUtil.getEncodedId(groupId, false));
SignalServiceAttachment avatar = null;

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,6 @@ import android.content.Context;
import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
@ -25,7 +24,7 @@ public class RequestGroupInfoJob extends ContextJob implements InjectableType {
private static final long serialVersionUID = 0L;
@Inject transient SignalMessageSenderFactory messageSenderFactory;
@Inject transient SignalServiceMessageSender messageSender;
private final String source;
private final byte[] groupId;
@ -46,8 +45,6 @@ public class RequestGroupInfoJob extends ContextJob implements InjectableType {
@Override
public void onRun() throws IOException, UntrustedIdentityException {
SignalServiceMessageSender messageSender = messageSenderFactory.create();
SignalServiceGroup group = SignalServiceGroup.newBuilder(Type.REQUEST_INFO)
.withId(groupId)
.build();

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.util.Log;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.ExpirationInfo;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob;
import org.thoughtcrime.securesms.jobs.SendReadReceiptJob;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class MarkReadReceiver extends MasterSecretBroadcastReceiver {
@ -72,6 +78,18 @@ public class MarkReadReceiver extends MasterSecretBroadcastReceiver {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new MultiDeviceReadUpdateJob(context, syncMessageIds));
Map<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) {

View File

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

View File

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

View File

@ -113,6 +113,15 @@ public class TextSecurePreferences {
private static final String ALWAYS_RELAY_CALLS_PREF = "pref_turn_only";
private static final String PROFILE_KEY_PREF = "pref_profile_key";
private static final String PROFILE_NAME_PREF = "pref_profile_name";
private static final String READ_RECEIPTS_PREF = "pref_read_receipts";
public static boolean isReadReceiptsEnabled(Context context) {
return getBooleanPreference(context, READ_RECEIPTS_PREF, false);
}
public static void setReadReceiptsEnabled(Context context, boolean enabled) {
setBooleanPreference(context, READ_RECEIPTS_PREF, enabled);
}
public static @Nullable String getProfileKey(Context context) {
return getStringPreference(context, PROFILE_KEY_PREF, null);

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