From c9d1fb8533169f241eb331c401d5a01ab516b6f3 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Mon, 26 Apr 2021 15:51:23 -0400 Subject: [PATCH] Fix reaction notification data inconsistencies. Co-authored-by: Greyson Parrelli --- .../securesms/database/MessageDatabase.java | 11 ++- .../securesms/database/MmsDatabase.java | 1 + .../securesms/database/SmsDatabase.java | 1 + .../database/helpers/SQLCipherOpenHelper.java | 68 ++++++++++++++++++- .../DeleteNotificationReceiver.java | 3 + .../notifications/MarkReadReceiver.java | 2 + 6 files changed, 82 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java index e4c22da3ab..6a6a843988 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java @@ -319,6 +319,8 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns setReactions(db, messageId, updatedList); db.setTransactionSuccessful(); + } catch (NoSuchMessageException e) { + Log.w(TAG, "No message for provided id", e); } finally { db.endTransaction(); } @@ -338,6 +340,8 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns setReactions(db, messageId, updatedList); db.setTransactionSuccessful(); + } catch (NoSuchMessageException e) { + Log.w(TAG, "No message for provided id", e); } finally { db.endTransaction(); } @@ -543,14 +547,15 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns return Optional.absent(); } - private void setReactions(@NonNull SQLiteDatabase db, long messageId, @NonNull ReactionList reactionList) { - ContentValues values = new ContentValues(1); + private void setReactions(@NonNull SQLiteDatabase db, long messageId, @NonNull ReactionList reactionList) throws NoSuchMessageException { + ContentValues values = new ContentValues(); + boolean isOutgoing = getMessageRecord(messageId).isOutgoing(); boolean hasReactions = reactionList.getReactionsCount() != 0; values.put(REACTIONS, reactionList.getReactionsList().isEmpty() ? null : reactionList.toByteArray()); values.put(REACTIONS_UNREAD, hasReactions ? 1 : 0); - if (hasReactions) { + if (isOutgoing && hasReactions) { values.put(NOTIFIED, 0); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index ba76f74f83..ec0d60b290 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -889,6 +889,7 @@ public class MmsDatabase extends MessageDatabase { ContentValues contentValues = new ContentValues(); contentValues.put(NOTIFIED, 1); + contentValues.put(REACTIONS_LAST_SEEN, System.currentTimeMillis()); database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(id)}); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 8a8b81ef2b..4366fb07d2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -465,6 +465,7 @@ public class SmsDatabase extends MessageDatabase { ContentValues contentValues = new ContentValues(); contentValues.put(NOTIFIED, 1); + contentValues.put(REACTIONS_LAST_SEEN, System.currentTimeMillis()); database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(id)}); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 55bb979cf8..7991943168 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -16,6 +16,7 @@ import androidx.annotation.NonNull; import com.annimon.stream.Stream; import com.bumptech.glide.Glide; +import com.google.protobuf.InvalidProtocolBufferException; import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteOpenHelper; @@ -45,6 +46,7 @@ import org.thoughtcrime.securesms.database.SqlCipherDatabaseHook; import org.thoughtcrime.securesms.database.StickerDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.UnknownStorageIdDatabase; +import org.thoughtcrime.securesms.database.model.databaseprotos.ReactionList; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob; @@ -69,6 +71,7 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -174,8 +177,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab private static final int MP4_GIF_SUPPORT = 93; private static final int BLUR_AVATARS = 94; private static final int CLEAN_STORAGE_IDS_WITHOUT_INFO = 95; + private static final int CLEAN_REACTION_NOTIFICATIONS = 96; - private static final int DATABASE_VERSION = 95; + private static final int DATABASE_VERSION = 96; private static final String DATABASE_NAME = "signal.db"; private final Context context; @@ -1330,6 +1334,68 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab Log.i(TAG, "There were " + count + " bad rows that had their storageID removed due to not having any other identifier."); } + if (oldVersion < CLEAN_REACTION_NOTIFICATIONS) { + ContentValues values = new ContentValues(1); + values.put("notified", 1); + + int count = 0; + count += db.update("sms", values, "notified = 0 AND read = 1 AND reactions_unread = 1 AND NOT ((type & 31) = 23 AND (type & 10485760) AND (type & 131072 = 0))", null); + count += db.update("mms", values, "notified = 0 AND read = 1 AND reactions_unread = 1 AND NOT ((msg_box & 31) = 23 AND (msg_box & 10485760) AND (msg_box & 131072 = 0))", null); + Log.d(TAG, "Resetting notified for " + count + " read incoming messages that were incorrectly flipped when receiving reactions"); + + List smsIds = new ArrayList<>(); + + try (Cursor cursor = db.query("sms", new String[]{"_id", "reactions", "notified_timestamp"}, "notified = 0 AND reactions_unread = 1", null, null, null, null)) { + while (cursor.moveToNext()) { + byte[] reactions = cursor.getBlob(cursor.getColumnIndexOrThrow("reactions")); + long notifiedTimestamp = cursor.getLong(cursor.getColumnIndexOrThrow("notified_timestamp")); + + try { + boolean hasReceiveLaterThanNotified = ReactionList.parseFrom(reactions) + .getReactionsList() + .stream() + .anyMatch(r -> r.getReceivedTime() > notifiedTimestamp); + if (!hasReceiveLaterThanNotified) { + smsIds.add(cursor.getLong(cursor.getColumnIndexOrThrow("_id"))); + } + } catch (InvalidProtocolBufferException e) { + Log.e(TAG, e); + } + } + } + + if (smsIds.size() > 0) { + Log.d(TAG, "Updating " + smsIds.size() + " records in sms"); + db.execSQL("UPDATE sms SET reactions_last_seen = notified_timestamp WHERE _id in (" + Util.join(smsIds, ",") + ")"); + } + + List mmsIds = new ArrayList<>(); + + try (Cursor cursor = db.query("mms", new String[]{"_id", "reactions", "notified_timestamp"}, "notified = 0 AND reactions_unread = 1", null, null, null, null)) { + while (cursor.moveToNext()) { + byte[] reactions = cursor.getBlob(cursor.getColumnIndexOrThrow("reactions")); + long notifiedTimestamp = cursor.getLong(cursor.getColumnIndexOrThrow("notified_timestamp")); + + try { + boolean hasReceiveLaterThanNotified = ReactionList.parseFrom(reactions) + .getReactionsList() + .stream() + .anyMatch(r -> r.getReceivedTime() > notifiedTimestamp); + if (!hasReceiveLaterThanNotified) { + mmsIds.add(cursor.getLong(cursor.getColumnIndexOrThrow("_id"))); + } + } catch (InvalidProtocolBufferException e) { + Log.e(TAG, e); + } + } + } + + if (mmsIds.size() > 0) { + Log.d(TAG, "Updating " + mmsIds.size() + " records in mms"); + db.execSQL("UPDATE mms SET reactions_last_seen = notified_timestamp WHERE _id in (" + Util.join(mmsIds, ",") + ")"); + } + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DeleteNotificationReceiver.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/DeleteNotificationReceiver.java index e90ac4fca5..e3f60feb7e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DeleteNotificationReceiver.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DeleteNotificationReceiver.java @@ -35,6 +35,8 @@ public class DeleteNotificationReceiver extends BroadcastReceiver { if (ids == null || mms == null || ids.length != mms.length) return; + PendingResult finisher = goAsync(); + SignalExecutors.BOUNDED.execute(() -> { for (int i = 0; i < ids.length; i++) { if (!mms[i]) { @@ -43,6 +45,7 @@ public class DeleteNotificationReceiver extends BroadcastReceiver { DatabaseFactory.getMmsDatabase(context).markAsNotified(ids[i]); } } + finisher.finish(); }); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java index 707bdf3efc..e14bf978ca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java @@ -49,6 +49,7 @@ public class MarkReadReceiver extends BroadcastReceiver { NotificationCancellationHelper.cancelLegacy(context, intent.getIntExtra(NOTIFICATION_ID_EXTRA, -1)); + PendingResult finisher = goAsync(); SignalExecutors.BOUNDED.execute(() -> { List messageIdsCollection = new LinkedList<>(); @@ -61,6 +62,7 @@ public class MarkReadReceiver extends BroadcastReceiver { process(context, messageIdsCollection); ApplicationDependencies.getMessageNotifier().updateNotification(context); + finisher.finish(); }); } }