From b0b8377a8ef307c8b999d254f2aba2d88aa657ca Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Wed, 25 Sep 2019 08:26:30 -0400 Subject: [PATCH] Migrate notification channels to recipientId's. --- .../database/helpers/SQLCipherOpenHelper.java | 65 +++++++++++++++++-- .../notifications/NotificationChannels.java | 43 ++++++------ 2 files changed, 83 insertions(+), 25 deletions(-) diff --git a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 5e8d4790c3..a17d81b833 100644 --- a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -1,16 +1,20 @@ package org.thoughtcrime.securesms.database.helpers; +import android.app.NotificationChannel; +import android.app.NotificationManager; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.net.Uri; +import android.os.Build; import android.os.SystemClock; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import android.text.TextUtils; +import com.annimon.stream.Stream; + import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteDatabaseHook; import net.sqlcipher.database.SQLiteOpenHelper; @@ -18,7 +22,6 @@ import net.sqlcipher.database.SQLiteOpenHelper; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.crypto.DatabaseSecret; import org.thoughtcrime.securesms.crypto.MasterSecret; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.DraftDatabase; import org.thoughtcrime.securesms.database.GroupDatabase; @@ -41,9 +44,11 @@ import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.util.GroupUtil; +import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import java.io.File; +import java.util.List; public class SQLCipherOpenHelper extends SQLiteOpenHelper { @@ -77,8 +82,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int RECIPIENT_CLEANUP = 26; private static final int MMS_RECIPIENT_CLEANUP = 27; private static final int ATTACHMENT_HASHING = 28; + private static final int NOTIFICATION_RECIPIENT_IDS = 29; - private static final int DATABASE_VERSION = 28; + private static final int DATABASE_VERSION = 29; private static final String DATABASE_NAME = "signal.db"; private final Context context; @@ -303,7 +309,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { String messageSound = cursor.getString(cursor.getColumnIndexOrThrow("notification")); Uri messageSoundUri = messageSound != null ? Uri.parse(messageSound) : null; int vibrateState = cursor.getInt(cursor.getColumnIndexOrThrow("vibrate")); - String displayName = NotificationChannels.getChannelDisplayNameFor(context, systemName, profileName, Address.fromSerialized(address)); + String displayName = NotificationChannels.getChannelDisplayNameFor(context, systemName, profileName, address); boolean vibrateEnabled = vibrateState == 0 ? TextSecurePreferences.isNotificationVibrateEnabled(context) : vibrateState == 1; if (GroupUtil.isEncodedGroup(address)) { @@ -318,7 +324,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { } } - String channelId = NotificationChannels.createChannelFor(context, Address.fromSerialized(address), displayName, messageSoundUri, vibrateEnabled); + String channelId = NotificationChannels.createChannelFor(context, "contact_" + address + "_" + System.currentTimeMillis(), displayName, messageSoundUri, vibrateEnabled); ContentValues values = new ContentValues(1); values.put("notification_channel", channelId); @@ -528,6 +534,55 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL("CREATE INDEX IF NOT EXISTS part_data_hash_index ON part (data_hash)"); } + if (oldVersion < NOTIFICATION_RECIPIENT_IDS && Build.VERSION.SDK_INT >= 26) { + NotificationManager notificationManager = ServiceUtil.getNotificationManager(context); + List channels = Stream.of(notificationManager.getNotificationChannels()) + .filter(c -> c.getId().startsWith("contact_")) + .toList(); + + Log.i(TAG, "Migrating " + channels.size() + " channels to use RecipientId's."); + + for (NotificationChannel oldChannel : channels) { + notificationManager.deleteNotificationChannel(oldChannel.getId()); + + int startIndex = "contact_".length(); + int endIndex = oldChannel.getId().lastIndexOf("_"); + String address = oldChannel.getId().substring(startIndex, endIndex); + + String recipientId; + + try (Cursor cursor = db.query("recipient", new String[] { "_id" }, "phone = ? OR email = ? OR group_id = ?", new String[] { address, address, address}, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + recipientId = cursor.getString(0); + } else { + Log.w(TAG, "Couldn't find recipient for address: " + address); + continue; + } + } + + String newId = "contact_" + recipientId + "_" + System.currentTimeMillis(); + NotificationChannel newChannel = new NotificationChannel(newId, oldChannel.getName(), oldChannel.getImportance()); + + Log.i(TAG, "Updating channel ID from '" + oldChannel.getId() + "' to '" + newChannel.getId() + "'."); + + newChannel.setGroup(oldChannel.getGroup()); + newChannel.setSound(oldChannel.getSound(), oldChannel.getAudioAttributes()); + newChannel.setBypassDnd(oldChannel.canBypassDnd()); + newChannel.enableVibration(oldChannel.shouldVibrate()); + newChannel.setVibrationPattern(oldChannel.getVibrationPattern()); + newChannel.setLockscreenVisibility(oldChannel.getLockscreenVisibility()); + newChannel.setShowBadge(oldChannel.canShowBadge()); + newChannel.setLightColor(oldChannel.getLightColor()); + newChannel.enableLights(oldChannel.shouldShowLights()); + + notificationManager.createNotificationChannel(newChannel); + + ContentValues contentValues = new ContentValues(1); + contentValues.put("notification_channel", newChannel.getId()); + db.update("recipient", contentValues, "_id = ?", new String[] { recipientId }); + } + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/src/org/thoughtcrime/securesms/notifications/NotificationChannels.java b/src/org/thoughtcrime/securesms/notifications/NotificationChannels.java index e30b0073a5..4ea5deaa1f 100644 --- a/src/org/thoughtcrime/securesms/notifications/NotificationChannels.java +++ b/src/org/thoughtcrime/securesms/notifications/NotificationChannels.java @@ -22,7 +22,6 @@ import com.annimon.stream.Stream; import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState; @@ -41,8 +40,10 @@ public class NotificationChannels { private static final String TAG = NotificationChannels.class.getSimpleName(); - private static final int VERSION_MESSAGES_CATEGORY = 2; - private static final int VERSION_CALLS_PRIORITY_BUMP = 3; + private static class Version { + static final int MESSAGES_CATEGORY = 2; + static final int CALLS_PRIORITY_BUMP = 3; + } private static final int VERSION = 3; @@ -124,13 +125,13 @@ public class NotificationChannels { /** * @return A name suitable to be displayed as the notification channel title. */ - public static @NonNull String getChannelDisplayNameFor(@NonNull Context context, @Nullable String systemName, @Nullable String profileName, @NonNull Address address) { + public static @NonNull String getChannelDisplayNameFor(@NonNull Context context, @Nullable String systemName, @Nullable String profileName, @NonNull String address) { if (!TextUtils.isEmpty(systemName)) { return systemName; } else if (!TextUtils.isEmpty(profileName)) { return profileName; - } else if (!TextUtils.isEmpty(address.serialize())) { - return address.serialize(); + } else if (!TextUtils.isEmpty(address)) { + return address; } else { return context.getString(R.string.NotificationChannel_missing_display_name); } @@ -146,16 +147,16 @@ public class NotificationChannels { VibrateState vibrateState = recipient.getMessageVibrate(); boolean vibrationEnabled = vibrateState == VibrateState.DEFAULT ? TextSecurePreferences.isNotificationVibrateEnabled(context) : vibrateState == VibrateState.ENABLED; Uri messageRingtone = recipient.getMessageRingtone() != null ? recipient.getMessageRingtone() : getMessageRingtone(context); - String displayName = getChannelDisplayNameFor(context, recipient.getName(), recipient.getProfileName(), recipient.requireAddress()); + String displayName = getChannelDisplayNameFor(context, recipient.getName(), recipient.getProfileName(), recipient.requireAddress().serialize()); - return createChannelFor(context, recipient.requireAddress(), displayName, messageRingtone, vibrationEnabled); + return createChannelFor(context, generateChannelIdFor(recipient), displayName, messageRingtone, vibrationEnabled); } /** * More verbose version of {@link #createChannelFor(Context, Recipient)}. */ public static synchronized @Nullable String createChannelFor(@NonNull Context context, - @NonNull Address address, + @NonNull String channelId, @NonNull String displayName, @Nullable Uri messageSound, boolean vibrationEnabled) @@ -164,7 +165,6 @@ public class NotificationChannels { return null; } - String channelId = generateChannelIdFor(address); NotificationChannel channel = new NotificationChannel(channelId, displayName, NotificationManager.IMPORTANCE_HIGH); setLedPreference(channel, TextSecurePreferences.getNotificationLedColor(context)); @@ -289,10 +289,10 @@ public class NotificationChannels { } Log.i(TAG, "Updating recipient message ringtone with URI: " + String.valueOf(uri)); - String newChannelId = generateChannelIdFor(recipient.requireAddress()); + String newChannelId = generateChannelIdFor(recipient); boolean success = updateExistingChannel(ServiceUtil.getNotificationManager(context), recipient.getNotificationChannel(), - generateChannelIdFor(recipient.requireAddress()), + generateChannelIdFor(recipient), channel -> channel.setSound(uri == null ? Settings.System.DEFAULT_NOTIFICATION_URI : uri, getRingtoneAudioAttributes())); DatabaseFactory.getRecipientDatabase(context).setNotificationChannel(recipient.getId(), success ? newChannelId : null); @@ -356,7 +356,7 @@ public class NotificationChannels { Log.i(TAG, "Updating recipient vibrate with value: " + vibrateState); boolean enabled = vibrateState == VibrateState.DEFAULT ? getMessageVibrate(context) : vibrateState == VibrateState.ENABLED; - String newChannelId = generateChannelIdFor(recipient.requireAddress()); + String newChannelId = generateChannelIdFor(recipient); boolean success = updateExistingChannel(ServiceUtil.getNotificationManager(context), recipient.getNotificationChannel(), newChannelId, @@ -384,7 +384,7 @@ public class NotificationChannels { } NotificationChannel channel = new NotificationChannel(recipient.getNotificationChannel(), - getChannelDisplayNameFor(context, recipient.getName(), recipient.getProfileName(), recipient.requireAddress()), + getChannelDisplayNameFor(context, recipient.getName(), recipient.getProfileName(), recipient.requireAddress().serialize()), NotificationManager.IMPORTANCE_HIGH); channel.setGroup(CATEGORY_MESSAGES); notificationManager.createNotificationChannel(channel); @@ -393,11 +393,12 @@ public class NotificationChannels { @TargetApi(26) @WorkerThread public static synchronized void ensureCustomChannelConsistency(@NonNull Context context) { + Log.d(TAG, "ensureCustomChannelConsistency()"); + NotificationManager notificationManager = ServiceUtil.getNotificationManager(context); RecipientDatabase db = DatabaseFactory.getRecipientDatabase(context); List customRecipients = new ArrayList<>(); Set customChannelIds = new HashSet<>(); - Set existingChannelIds = Stream.of(notificationManager.getNotificationChannels()).map(NotificationChannel::getId).collect(Collectors.toSet()); try (RecipientDatabase.RecipientReader reader = db.getRecipientsWithNotificationChannels()) { Recipient recipient; @@ -407,6 +408,8 @@ public class NotificationChannels { } } + Set existingChannelIds = Stream.of(notificationManager.getNotificationChannels()).map(NotificationChannel::getId).collect(Collectors.toSet()); + for (NotificationChannel existingChannel : notificationManager.getNotificationChannels()) { if (existingChannel.getId().startsWith(CONTACT_PREFIX) && !customChannelIds.contains(existingChannel.getId())) { Log.i(TAG, "Consistency: Deleting channel '"+ existingChannel.getId() + "' because the DB has no record of it."); @@ -461,7 +464,7 @@ public class NotificationChannels { private static void onUpgrade(@NonNull NotificationManager notificationManager, int oldVersion, int newVersion) { Log.i(TAG, "Upgrading channels from " + oldVersion + " to " + newVersion); - if (oldVersion < VERSION_MESSAGES_CATEGORY) { + if (oldVersion < Version.MESSAGES_CATEGORY) { notificationManager.deleteNotificationChannel("messages"); notificationManager.deleteNotificationChannel("calls"); notificationManager.deleteNotificationChannel("locked_status"); @@ -469,7 +472,7 @@ public class NotificationChannels { notificationManager.deleteNotificationChannel("other"); } - if (oldVersion < VERSION_CALLS_PRIORITY_BUMP) { + if (oldVersion < Version.CALLS_PRIORITY_BUMP) { notificationManager.deleteNotificationChannel("calls_v2"); } } @@ -485,8 +488,8 @@ public class NotificationChannels { } - private static @NonNull String generateChannelIdFor(@NonNull Address address) { - return CONTACT_PREFIX + address.serialize() + "_" + System.currentTimeMillis(); + private static @NonNull String generateChannelIdFor(@NonNull Recipient recipient) { + return CONTACT_PREFIX + recipient.getId().serialize() + "_" + System.currentTimeMillis(); } @TargetApi(26) @@ -520,7 +523,7 @@ public class NotificationChannels { while ((recipient = recipients.getNext()) != null) { assert recipient.getNotificationChannel() != null; - String newChannelId = generateChannelIdFor(recipient.requireAddress()); + String newChannelId = generateChannelIdFor(recipient); boolean success = updateExistingChannel(notificationManager, recipient.getNotificationChannel(), newChannelId, channel -> setLedPreference(channel, color)); database.setNotificationChannel(recipient.getId(), success ? newChannelId : null);