From 49f60971bde2f3e4fbc8327039c88df4e7b44a60 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Tue, 8 Dec 2015 20:32:54 -0800 Subject: [PATCH] Cache delivery receipts when they arrive before sync message // FREEBIE --- .../securesms/database/EarlyReceiptCache.java | 56 +++++++++++++++++++ .../securesms/database/MmsDatabase.java | 22 ++++++++ .../securesms/database/SmsDatabase.java | 29 ++++++++-- 3 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/database/EarlyReceiptCache.java diff --git a/src/org/thoughtcrime/securesms/database/EarlyReceiptCache.java b/src/org/thoughtcrime/securesms/database/EarlyReceiptCache.java new file mode 100644 index 000000000..b23301e0c --- /dev/null +++ b/src/org/thoughtcrime/securesms/database/EarlyReceiptCache.java @@ -0,0 +1,56 @@ +package org.thoughtcrime.securesms.database; + +import android.support.annotation.NonNull; +import android.util.Log; + +import org.thoughtcrime.securesms.util.LRUCache; + +public class EarlyReceiptCache { + + private static final String TAG = EarlyReceiptCache.class.getSimpleName(); + + private final LRUCache cache = new LRUCache<>(100); + + public synchronized void increment(long timestamp, String address) { + Log.w(TAG, this+""); + Log.w(TAG, String.format("Early receipt: %d,%s", timestamp, address)); + Placeholder tuple = new Placeholder(timestamp, address); + Long count = cache.get(tuple); + + if (count != null) { + cache.put(tuple, ++count); + } else { + cache.put(tuple, 1L); + } + } + + public synchronized long remove(long timestamp, String address) { + Long count = cache.remove(new Placeholder(timestamp, address)); + Log.w(TAG, this+""); + Log.w(TAG, String.format("Checking early receipts (%d, %s): %d", timestamp, address, count)); + return count != null ? count : 0; + } + + private class Placeholder { + + private final long timestamp; + private final @NonNull String address; + + private Placeholder(long timestamp, @NonNull String address) { + this.timestamp = timestamp; + this.address = address; + } + + @Override + public boolean equals(Object other) { + return other != null && other instanceof Placeholder && + ((Placeholder)other).timestamp == this.timestamp && + ((Placeholder)other).address.equals(this.address); + } + + @Override + public int hashCode() { + return (int)timestamp ^ address.hashCode(); + } + } +} diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 6939dd47f..6b4a983fe 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -144,6 +144,7 @@ public class MmsDatabase extends MessagingDatabase { private static final String RAW_ID_WHERE = TABLE_NAME + "._id = ?"; + private final EarlyReceiptCache earlyReceiptCache = new EarlyReceiptCache(); private final JobManager jobManager; public MmsDatabase(Context context, SQLiteOpenHelper databaseHelper) { @@ -193,6 +194,7 @@ public class MmsDatabase extends MessagingDatabase { MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context); SQLiteDatabase database = databaseHelper.getWritableDatabase(); Cursor cursor = null; + boolean found = false; try { cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID, MESSAGE_BOX}, DATE_SENT + " = ?", new String[] {String.valueOf(timestamp)}, null, null, null, null); @@ -210,6 +212,8 @@ public class MmsDatabase extends MessagingDatabase { long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID)); long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID)); + found = true; + database.execSQL("UPDATE " + TABLE_NAME + " SET " + RECEIPT_COUNT + " = " + RECEIPT_COUNT + " + 1 WHERE " + ID + " = ?", new String[] {String.valueOf(id)}); @@ -223,6 +227,14 @@ public class MmsDatabase extends MessagingDatabase { } } } + + if (!found) { + try { + earlyReceiptCache.increment(timestamp, canonicalizeNumber(context, address)); + } catch (InvalidNumberException e) { + Log.w(TAG, e); + } + } } finally { if (cursor != null) cursor.close(); @@ -749,6 +761,16 @@ public class MmsDatabase extends MessagingDatabase { contentValues.put(THREAD_ID, threadId); contentValues.put(READ, 1); contentValues.put(DATE_RECEIVED, System.currentTimeMillis()); + + if (message.getRecipients().isSingleRecipient()) { + try { + contentValues.put(RECEIPT_COUNT, earlyReceiptCache.remove(message.getSentTimeMillis(), + canonicalizeNumber(context, message.getRecipients().getPrimaryRecipient().getNumber()))); + } catch (InvalidNumberException e) { + Log.w(TAG, e); + } + } + contentValues.remove(ADDRESS); long messageId = insertMediaMessage(masterSecret, addresses, message.getBody(), diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index 73cdf56a2..8a6f8c9fd 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -41,6 +41,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.LRUCache; import org.whispersystems.jobqueue.JobManager; import org.whispersystems.textsecure.api.util.InvalidNumberException; @@ -97,6 +98,7 @@ public class SmsDatabase extends MessagingDatabase { MISMATCHED_IDENTITIES }; + private static final EarlyReceiptCache earlyReceiptCache = new EarlyReceiptCache(); private final JobManager jobManager; public SmsDatabase(Context context, SQLiteOpenHelper databaseHelper) { @@ -252,8 +254,9 @@ public class SmsDatabase extends MessagingDatabase { } public void incrementDeliveryReceiptCount(String address, long timestamp) { - SQLiteDatabase database = databaseHelper.getWritableDatabase(); - Cursor cursor = null; + SQLiteDatabase database = databaseHelper.getWritableDatabase(); + Cursor cursor = null; + boolean foundMessage = false; try { cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID, ADDRESS, TYPE}, @@ -276,12 +279,22 @@ public class SmsDatabase extends MessagingDatabase { DatabaseFactory.getThreadDatabase(context).update(threadId, false); notifyConversationListeners(threadId); + foundMessage = true; } } catch (InvalidNumberException e) { - Log.w("SmsDatabase", e); + Log.w(TAG, e); } } } + + if (!foundMessage) { + try { + earlyReceiptCache.increment(timestamp, canonicalizeNumber(context, address)); + } catch (InvalidNumberException e) { + Log.w(TAG, e); + } + } + } finally { if (cursor != null) cursor.close(); @@ -474,8 +487,10 @@ public class SmsDatabase extends MessagingDatabase { else if (message.isEndSession()) type |= Types.END_SESSION_BIT; if (forceSms) type |= Types.MESSAGE_FORCE_SMS_BIT; + String address = message.getRecipients().getPrimaryRecipient().getNumber(); + ContentValues contentValues = new ContentValues(6); - contentValues.put(ADDRESS, PhoneNumberUtils.formatNumber(message.getRecipients().getPrimaryRecipient().getNumber())); + contentValues.put(ADDRESS, PhoneNumberUtils.formatNumber(address)); contentValues.put(THREAD_ID, threadId); contentValues.put(BODY, message.getMessageBody()); contentValues.put(DATE_RECEIVED, System.currentTimeMillis()); @@ -483,6 +498,12 @@ public class SmsDatabase extends MessagingDatabase { contentValues.put(READ, 1); contentValues.put(TYPE, type); + try { + contentValues.put(RECEIPT_COUNT, earlyReceiptCache.remove(date, canonicalizeNumber(context, address))); + } catch (InvalidNumberException e) { + Log.w(TAG, e); + } + SQLiteDatabase db = databaseHelper.getWritableDatabase(); long messageId = db.insert(TABLE_NAME, ADDRESS, contentValues);