Lazily initialize NotificationChannels.

This commit is contained in:
Greyson Parrelli 2022-11-16 19:08:50 -05:00 committed by Cody Henthorne
parent 3e8b5ca91d
commit 81c10a1eae
24 changed files with 251 additions and 227 deletions

View file

@ -146,6 +146,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
SignalDatabase.init(this,
DatabaseSecretProvider.getOrCreateDatabaseSecret(this),
AttachmentSecretProvider.getInstance(this).getOrCreateAttachmentSecret());
SignalDatabase.triggerDatabaseAccess();
})
.addBlocking("logging", () -> {
initializeLogging();
@ -155,7 +156,6 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
.addBlocking("rx-init", this::initializeRx)
.addBlocking("event-bus", () -> EventBus.builder().logNoSubscriberMessages(false).installDefaultEventBus())
.addBlocking("app-dependencies", this::initializeAppDependencies)
.addBlocking("notification-channels", () -> NotificationChannels.create(this))
.addBlocking("first-launch", this::initializeFirstEverAppLaunch)
.addBlocking("app-migrations", this::initializeApplicationMigrations)
.addBlocking("ring-rtc", this::initializeRingRtc)

View file

@ -104,7 +104,7 @@ class NotificationsSettingsFragment : DSLSettingsFragment(R.string.preferences__
summary = DSLSettingsText.from(R.string.preferences__change_sound_and_vibration),
isEnabled = state.messageNotificationsState.notificationsEnabled,
onClick = {
NotificationChannels.openChannelSettings(requireContext(), NotificationChannels.getMessagesChannel(requireContext()), null)
NotificationChannels.getInstance().openChannelSettings(NotificationChannels.getInstance().messagesChannel, null)
}
)
} else {
@ -301,7 +301,7 @@ class NotificationsSettingsFragment : DSLSettingsFragment(R.string.preferences__
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
intent.putExtra(
Settings.EXTRA_CHANNEL_ID,
NotificationChannels.getMessagesChannel(requireContext())
NotificationChannels.getInstance().messagesChannel
)
intent.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
startActivity(intent)

View file

@ -16,8 +16,8 @@ class NotificationsSettingsViewModel(private val sharedPreferences: SharedPrefer
init {
if (NotificationChannels.supported()) {
SignalStore.settings().messageNotificationSound = NotificationChannels.getMessageRingtone(ApplicationDependencies.getApplication())
SignalStore.settings().isMessageVibrateEnabled = NotificationChannels.getMessageVibrate(ApplicationDependencies.getApplication())
SignalStore.settings().messageNotificationSound = NotificationChannels.getInstance().messageRingtone
SignalStore.settings().isMessageVibrateEnabled = NotificationChannels.getInstance().messageVibrate
}
}
@ -33,19 +33,19 @@ class NotificationsSettingsViewModel(private val sharedPreferences: SharedPrefer
fun setMessageNotificationsSound(sound: Uri?) {
val messageSound = sound ?: Uri.EMPTY
SignalStore.settings().messageNotificationSound = messageSound
NotificationChannels.updateMessageRingtone(ApplicationDependencies.getApplication(), messageSound)
NotificationChannels.getInstance().updateMessageRingtone(messageSound)
store.update { getState() }
}
fun setMessageNotificationVibration(enabled: Boolean) {
SignalStore.settings().isMessageVibrateEnabled = enabled
NotificationChannels.updateMessageVibrate(ApplicationDependencies.getApplication(), enabled)
NotificationChannels.getInstance().updateMessageVibrate(enabled)
store.update { getState() }
}
fun setMessageNotificationLedColor(color: String) {
SignalStore.settings().messageLedColor = color
NotificationChannels.updateMessagesLedColor(ApplicationDependencies.getApplication(), color)
NotificationChannels.getInstance().updateMessagesLedColor(color)
store.update { getState() }
}

View file

@ -13,7 +13,7 @@ class SoundsAndNotificationsSettingsRepository(private val context: Context) {
fun ensureCustomChannelConsistency(complete: () -> Unit) {
SignalExecutors.BOUNDED.execute {
if (NotificationChannels.supported()) {
NotificationChannels.ensureCustomChannelConsistency(context)
NotificationChannels.getInstance().ensureCustomChannelConsistency()
}
complete()
}
@ -38,7 +38,7 @@ class SoundsAndNotificationsSettingsRepository(private val context: Context) {
if (recipient.notificationChannel != null || !NotificationChannels.supported()) {
true
} else {
NotificationChannels.updateWithShortcutBasedChannel(context, recipient)
NotificationChannels.getInstance().updateWithShortcutBasedChannel(recipient)
}
)
}

View file

@ -91,7 +91,7 @@ class CustomNotificationsSettingsFragment : DSLSettingsFragment(R.string.CustomN
title = DSLSettingsText.from(R.string.CustomNotificationsDialogFragment__customize),
summary = DSLSettingsText.from(R.string.CustomNotificationsDialogFragment__change_sound_and_vibration),
isEnabled = state.controlsEnabled,
onClick = { NotificationChannels.openChannelSettings(requireContext(), state.recipient!!.notificationChannel!!, ConversationUtil.getShortcutId(state.recipient)) }
onClick = { NotificationChannels.getInstance().openChannelSettings(state.recipient!!.notificationChannel!!, ConversationUtil.getShortcutId(state.recipient)) }
)
} else {
clickPref(

View file

@ -20,14 +20,14 @@ class CustomNotificationsSettingsRepository(context: Context) {
fun ensureCustomChannelConsistency(recipientId: RecipientId, onComplete: () -> Unit) {
executor.execute {
if (NotificationChannels.supported()) {
NotificationChannels.ensureCustomChannelConsistency(context)
NotificationChannels.getInstance().ensureCustomChannelConsistency()
val recipient = Recipient.resolved(recipientId)
val database = SignalDatabase.recipients
if (recipient.notificationChannel != null) {
val ringtoneUri: Uri? = NotificationChannels.getMessageRingtone(context, recipient)
val ringtoneUri: Uri? = NotificationChannels.getInstance().getMessageRingtone(recipient)
database.setMessageRingtone(recipient.id, if (ringtoneUri == Uri.EMPTY) null else ringtoneUri)
database.setMessageVibrate(recipient.id, RecipientDatabase.VibrateState.fromBoolean(NotificationChannels.getMessageVibrate(context, recipient)))
database.setMessageVibrate(recipient.id, RecipientDatabase.VibrateState.fromBoolean(NotificationChannels.getInstance().getMessageVibrate(recipient)))
}
}
@ -50,7 +50,7 @@ class CustomNotificationsSettingsRepository(context: Context) {
val recipient: Recipient = Recipient.resolved(recipientId)
SignalDatabase.recipients.setMessageVibrate(recipient.id, vibrateState)
NotificationChannels.updateMessageVibrate(context, recipient, vibrateState)
NotificationChannels.getInstance().updateMessageVibrate(recipient, vibrateState)
}
}
@ -67,7 +67,7 @@ class CustomNotificationsSettingsRepository(context: Context) {
val newValue: Uri? = if (defaultValue == sound) null else sound ?: Uri.EMPTY
SignalDatabase.recipients.setMessageRingtone(recipient.id, newValue)
NotificationChannels.updateMessageRingtone(context, recipient, newValue)
NotificationChannels.getInstance().updateMessageRingtone(recipient, newValue)
}
}
@ -83,7 +83,7 @@ class CustomNotificationsSettingsRepository(context: Context) {
@WorkerThread
private fun createCustomNotificationChannel(recipientId: RecipientId) {
val recipient: Recipient = Recipient.resolved(recipientId)
val channelId = NotificationChannels.createChannelFor(context, recipient)
val channelId = NotificationChannels.getInstance().createChannelFor(recipient)
SignalDatabase.recipients.setNotificationChannel(recipient.id, channelId)
}
@ -91,6 +91,6 @@ class CustomNotificationsSettingsRepository(context: Context) {
private fun deleteCustomNotificationChannel(recipientId: RecipientId) {
val recipient: Recipient = Recipient.resolved(recipientId)
SignalDatabase.recipients.setNotificationChannel(recipient.id, null)
NotificationChannels.deleteChannelFor(context, recipient)
NotificationChannels.getInstance().deleteChannelFor(recipient)
}
}

View file

@ -263,7 +263,7 @@ object ContactDiscovery {
var recipient: Recipient? = reader.getNext()
while (recipient != null) {
NotificationChannels.updateContactChannelName(context, recipient)
NotificationChannels.getInstance().updateContactChannelName(recipient)
recipient = reader.getNext()
}
}

View file

@ -705,7 +705,7 @@ public class ConversationParentFragment extends Fragment
onRecipientChanged(recipientSnapshot);
titleView.setTitle(glideRequests, recipientSnapshot);
NotificationChannels.updateContactChannelName(requireContext(), recipientSnapshot);
NotificationChannels.getInstance().updateContactChannelName(recipientSnapshot);
setBlockedUserState(recipientSnapshot, viewModel.getConversationStateSnapshot().getSecurityInfo());
invalidateOptionsMenu();
break;

View file

@ -184,7 +184,7 @@ public final class EnableCallNotificationSettingsDialog extends DialogFragment {
}
private void showNotificationChannelSettings() {
NotificationChannels.openChannelSettings(requireContext(), NotificationChannels.CALLS, null);
NotificationChannels.getInstance().openChannelSettings(NotificationChannels.CALLS, null);
}
private void showAppSettings() {
@ -194,7 +194,7 @@ public final class EnableCallNotificationSettingsDialog extends DialogFragment {
}
private static boolean areNotificationsDisabled(@NonNull Context context) {
return !NotificationChannels.areNotificationsEnabled(context);
return !NotificationChannels.getInstance().areNotificationsEnabled();
}
private static boolean areCallNotificationsDisabled(Context context) {
@ -202,7 +202,7 @@ public final class EnableCallNotificationSettingsDialog extends DialogFragment {
}
private static boolean isCallChannelInvalid(Context context) {
return !NotificationChannels.isCallsChannelValid(context);
return !NotificationChannels.getInstance().isCallsChannelValid();
}
private static boolean isBackgroundRestricted(Context context) {

View file

@ -371,7 +371,7 @@ object V149_LegacyMigrations : SignalDatabaseMigration {
// Note: The column only being checked due to upgrade issues as described in #8184
if (oldVersion < NOTIFICATION_CHANNELS && !SqlUtil.columnExists(db, "recipient_preferences", "notification_channel")) {
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN notification_channel TEXT DEFAULT NULL")
NotificationChannels.create(context)
NotificationChannels.getInstance()
db.rawQuery("SELECT recipient_ids, system_display_name, signal_profile_name, notification, vibrate FROM recipient_preferences WHERE notification NOT NULL OR vibrate != 0", null).use { cursor ->
while (cursor != null && cursor.moveToNext()) {
@ -382,7 +382,7 @@ object V149_LegacyMigrations : SignalDatabaseMigration {
val messageSound: String? = cursor.getString(cursor.getColumnIndexOrThrow("notification"))
val messageSoundUri: Uri? = if (messageSound != null) Uri.parse(messageSound) else null
val vibrateState: Int = cursor.getInt(cursor.getColumnIndexOrThrow("vibrate"))
var displayName: String? = NotificationChannels.getChannelDisplayNameFor(context, systemName, profileName, null, address)
var displayName: String? = NotificationChannels.getInstance().getChannelDisplayNameFor(systemName, profileName, null, address)
val vibrateEnabled: Boolean = if (vibrateState == 0) SignalStore.settings().isMessageVibrateEnabled() else vibrateState == 1
if (GroupId.isEncodedGroup(address)) {
db.rawQuery("SELECT title FROM groups WHERE group_id = ?", arrayOf(address)).use { groupCursor ->
@ -394,7 +394,7 @@ object V149_LegacyMigrations : SignalDatabaseMigration {
}
}
}
val channelId: String? = NotificationChannels.createChannelFor(context, "contact_" + address + "_" + System.currentTimeMillis(), (displayName)!!, messageSoundUri, vibrateEnabled, null)
val channelId: String? = NotificationChannels.getInstance().createChannelFor("contact_" + address + "_" + System.currentTimeMillis(), (displayName)!!, messageSoundUri, vibrateEnabled, null)
val values = ContentValues(1).apply {
put("notification_channel", channelId)
}

View file

@ -50,7 +50,7 @@ final class NewDeviceServerTask implements ServerTask {
passphrase);
SignalDatabase.upgradeRestored(database);
NotificationChannels.restoreContactNotificationChannels(context);
NotificationChannels.getInstance().restoreContactNotificationChannels();
AppInitialization.onPostBackupRestore(context);

View file

@ -214,7 +214,7 @@ public final class PushDecryptMessageJob extends BaseJob {
private void postMigrationNotification() {
NotificationManagerCompat.from(context).notify(NotificationIds.LEGACY_SQLCIPHER_MIGRATION,
new NotificationCompat.Builder(context, NotificationChannels.getMessagesChannel(context))
new NotificationCompat.Builder(context, NotificationChannels.getInstance().getMessagesChannel())
.setSmallIcon(R.drawable.ic_notification)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_MESSAGE)

View file

@ -195,7 +195,7 @@ public class SmsReceiveJob extends BaseJob {
private static Notification buildPreRegistrationNotification(@NonNull Context context, @NonNull IncomingTextMessage message) {
Recipient sender = Recipient.resolved(message.getSender());
return new NotificationCompat.Builder(context, NotificationChannels.getMessagesChannel(context))
return new NotificationCompat.Builder(context, NotificationChannels.getInstance().getMessagesChannel())
.setStyle(new NotificationCompat.MessagingStyle(new Person.Builder()
.setName(sender.getE164().orElse(""))
.build())

View file

@ -227,13 +227,13 @@ public final class Megaphones {
.setBody(R.string.NotificationsMegaphone_never_miss_a_message)
.setImage(R.drawable.megaphone_notifications_64)
.setActionButton(R.string.NotificationsMegaphone_turn_on, (megaphone, controller) -> {
if (Build.VERSION.SDK_INT >= 26 && !NotificationChannels.isMessageChannelEnabled(context)) {
if (Build.VERSION.SDK_INT >= 26 && !NotificationChannels.getInstance().isMessageChannelEnabled()) {
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_CHANNEL_ID, NotificationChannels.getMessagesChannel(context));
intent.putExtra(Settings.EXTRA_CHANNEL_ID, NotificationChannels.getInstance().getMessagesChannel());
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
controller.onMegaphoneNavigationRequested(intent);
} else if (Build.VERSION.SDK_INT >= 26 &&
(!NotificationChannels.areNotificationsEnabled(context) || !NotificationChannels.isMessagesChannelGroupEnabled(context)))
(!NotificationChannels.getInstance().areNotificationsEnabled() || !NotificationChannels.getInstance().isMessagesChannelGroupEnabled()))
{
Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
@ -415,9 +415,9 @@ public final class Megaphones {
private static boolean shouldShowNotificationsMegaphone(@NonNull Context context) {
boolean shouldShow = !SignalStore.settings().isMessageNotificationsEnabled() ||
!NotificationChannels.isMessageChannelEnabled(context) ||
!NotificationChannels.isMessagesChannelGroupEnabled(context) ||
!NotificationChannels.areNotificationsEnabled(context);
!NotificationChannels.getInstance().isMessageChannelEnabled() ||
!NotificationChannels.getInstance().isMessagesChannelGroupEnabled() ||
!NotificationChannels.getInstance().areNotificationsEnabled();
if (shouldShow) {
Locale locale = DynamicLanguageContextWrapper.getUsersSelectedLocale(context);
if (!new TranslationDetection(context, locale)

View file

@ -106,7 +106,7 @@ public class UserNotificationMigrationJob extends MigrationJob {
.addNextIntent(newConversationIntent)
.getPendingIntent(0, 0);
Notification notification = new NotificationCompat.Builder(context, NotificationChannels.getMessagesChannel(context))
Notification notification = new NotificationCompat.Builder(context, NotificationChannels.getInstance().getMessagesChannel())
.setSmallIcon(R.drawable.ic_notification)
.setContentText(message)
.setContentIntent(pendingIntent)

View file

@ -10,7 +10,7 @@ public class LocaleChangedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
NotificationChannels.create(context);
NotificationChannels.getInstance().onLocaleChanged();
EmojiSearchIndexDownloadJob.scheduleImmediately();
}
}

View file

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.notifications;
import android.annotation.TargetApi;
import android.app.Application;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
@ -29,6 +30,7 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
@ -76,11 +78,29 @@ public class NotificationChannels {
public static final String CALL_STATUS = "call_status";
public static final String APP_ALERTS = "app_alerts";
private static volatile NotificationChannels instance;
private final Application context;
public static NotificationChannels getInstance() {
if (instance == null) {
synchronized (NotificationChannels.class) {
if (instance == null) {
instance = new NotificationChannels(ApplicationDependencies.getApplication());
}
}
}
return instance;
}
/**
* Ensures all of the notification channels are created. No harm in repeat calls. Call is safely
* ignored for API < 26.
*/
public static synchronized void create(@NonNull Context context) {
private NotificationChannels(@NonNull Application application) {
this.context = application;
if (!supported()) {
return;
}
@ -93,44 +113,9 @@ public class NotificationChannels {
TextSecurePreferences.setNotificationChannelVersion(context, VERSION);
}
onCreate(context, notificationManager);
onCreate(notificationManager);
AsyncTask.SERIAL_EXECUTOR.execute(() -> {
ensureCustomChannelConsistency(context);
});
}
/**
* Recreates all notification channels for contacts with custom notifications enabled. Should be
* safe to call repeatedly. Needs to be executed on a background thread.
*/
@WorkerThread
public static synchronized void restoreContactNotificationChannels(@NonNull Context context) {
if (!NotificationChannels.supported()) {
return;
}
RecipientDatabase db = SignalDatabase.recipients();
try (RecipientDatabase.RecipientReader reader = db.getRecipientsWithNotificationChannels()) {
Recipient recipient;
while ((recipient = reader.getNext()) != null) {
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
if (!channelExists(notificationManager.getNotificationChannel(recipient.getNotificationChannel()))) {
String id = createChannelFor(context, recipient);
db.setNotificationChannel(recipient.getId(), id);
}
}
}
ensureCustomChannelConsistency(context);
}
/**
* @return The channel ID for the default messages channel.
*/
public static synchronized @NonNull String getMessagesChannel(@NonNull Context context) {
return getMessagesChannelId(TextSecurePreferences.getNotificationMessagesChannelVersion(context));
AsyncTask.SERIAL_EXECUTOR.execute(this::ensureCustomChannelConsistency);
}
/**
@ -140,101 +125,10 @@ public class NotificationChannels {
return Build.VERSION.SDK_INT >= 26;
}
/**
* @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,
@Nullable String username,
@NonNull String address)
{
if (!TextUtils.isEmpty(systemName)) {
return systemName;
} else if (!TextUtils.isEmpty(profileName)) {
return profileName;
} else if (!TextUtils.isEmpty(username)) {
return username;
} else if (!TextUtils.isEmpty(address)) {
return address;
} else {
return context.getString(R.string.NotificationChannel_missing_display_name);
}
}
/**
* Creates a channel for the specified recipient.
* @return The channel ID for the newly-created channel.
*/
public static synchronized @Nullable String createChannelFor(@NonNull Context context, @NonNull Recipient recipient) {
if (recipient.getId().isUnknown()) return null;
VibrateState vibrateState = recipient.getMessageVibrate();
boolean vibrationEnabled = vibrateState == VibrateState.DEFAULT ? SignalStore.settings().isMessageVibrateEnabled() : vibrateState == VibrateState.ENABLED;
Uri messageRingtone = recipient.getMessageRingtone() != null ? recipient.getMessageRingtone() : getMessageRingtone(context);
String displayName = recipient.getDisplayName(context);
return createChannelFor(context, generateChannelIdFor(recipient), displayName, messageRingtone, vibrationEnabled, ConversationUtil.getShortcutId(recipient));
}
/**
* More verbose version of {@link #createChannelFor(Context, Recipient)}.
*/
public static synchronized @Nullable String createChannelFor(@NonNull Context context,
@NonNull String channelId,
@NonNull String displayName,
@Nullable Uri messageSound,
boolean vibrationEnabled,
@Nullable String shortcutId)
{
if (!supported()) {
return null;
}
NotificationChannel channel = new NotificationChannel(channelId, displayName, NotificationManager.IMPORTANCE_HIGH);
setLedPreference(channel, SignalStore.settings().getMessageLedColor());
channel.setGroup(CATEGORY_MESSAGES);
setVibrationEnabled(channel, vibrationEnabled);
if (messageSound != null) {
channel.setSound(messageSound, new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
.setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT)
.build());
}
if (Build.VERSION.SDK_INT >= CONVERSATION_SUPPORT_VERSION && shortcutId != null) {
channel.setConversationId(getMessagesChannel(context), shortcutId);
}
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
notificationManager.createNotificationChannel(channel);
return channelId;
}
/**
* Deletes the channel generated for the provided recipient. Safe to call even if there was never
* a channel made for that recipient.
*/
public static synchronized void deleteChannelFor(@NonNull Context context, @NonNull Recipient recipient) {
if (!supported()) {
return;
}
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
String channel = recipient.getNotificationChannel();
if (channel != null) {
Log.i(TAG, "Deleting channel");
notificationManager.deleteNotificationChannel(channel);
}
}
/**
* Navigates the user to the system settings for the desired notification channel.
*/
public static void openChannelSettings(@NonNull Context context, @NonNull String channelId, @Nullable String conversationId) {
public void openChannelSettings(@NonNull String channelId, @Nullable String conversationId) {
if (!supported()) {
return;
}
@ -253,12 +147,146 @@ public class NotificationChannels {
}
}
/**
* @return A name suitable to be displayed as the notification channel title.
*/
public @NonNull String getChannelDisplayNameFor(@Nullable String systemName,
@Nullable String profileName,
@Nullable String username,
@NonNull String address)
{
if (!TextUtils.isEmpty(systemName)) {
return systemName;
} else if (!TextUtils.isEmpty(profileName)) {
return profileName;
} else if (!TextUtils.isEmpty(username)) {
return username;
} else if (!TextUtils.isEmpty(address)) {
return address;
} else {
return context.getString(R.string.NotificationChannel_missing_display_name);
}
}
/**
* Whether or not notifications for the entire app are enabled.
*/
public synchronized boolean areNotificationsEnabled() {
if (Build.VERSION.SDK_INT >= 24) {
return ServiceUtil.getNotificationManager(context).areNotificationsEnabled();
} else {
return true;
}
}
/**
* Recreates all notification channels for contacts with custom notifications enabled. Should be
* safe to call repeatedly. Needs to be executed on a background thread.
*/
@WorkerThread
public synchronized void restoreContactNotificationChannels() {
if (!NotificationChannels.supported()) {
return;
}
RecipientDatabase db = SignalDatabase.recipients();
try (RecipientDatabase.RecipientReader reader = db.getRecipientsWithNotificationChannels()) {
Recipient recipient;
while ((recipient = reader.getNext()) != null) {
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
if (!channelExists(notificationManager.getNotificationChannel(recipient.getNotificationChannel()))) {
String id = createChannelFor(recipient);
db.setNotificationChannel(recipient.getId(), id);
}
}
}
ensureCustomChannelConsistency();
}
/**
* @return The channel ID for the default messages channel.
*/
public synchronized @NonNull String getMessagesChannel() {
return getMessagesChannelId(TextSecurePreferences.getNotificationMessagesChannelVersion(context));
}
/**
* Creates a channel for the specified recipient.
* @return The channel ID for the newly-created channel.
*/
public synchronized @Nullable String createChannelFor(@NonNull Recipient recipient) {
if (recipient.getId().isUnknown()) return null;
VibrateState vibrateState = recipient.getMessageVibrate();
boolean vibrationEnabled = vibrateState == VibrateState.DEFAULT ? SignalStore.settings().isMessageVibrateEnabled() : vibrateState == VibrateState.ENABLED;
Uri messageRingtone = recipient.getMessageRingtone() != null ? recipient.getMessageRingtone() : getMessageRingtone();
String displayName = recipient.getDisplayName(context);
return createChannelFor(generateChannelIdFor(recipient), displayName, messageRingtone, vibrationEnabled, ConversationUtil.getShortcutId(recipient));
}
/**
* More verbose version of {@link #createChannelFor(Recipient)}.
*/
public synchronized @Nullable String createChannelFor(@NonNull String channelId,
@NonNull String displayName,
@Nullable Uri messageSound,
boolean vibrationEnabled,
@Nullable String shortcutId)
{
if (!supported()) {
return null;
}
NotificationChannel channel = new NotificationChannel(channelId, displayName, NotificationManager.IMPORTANCE_HIGH);
setLedPreference(channel, SignalStore.settings().getMessageLedColor());
channel.setGroup(CATEGORY_MESSAGES);
setVibrationEnabled(channel, vibrationEnabled);
if (messageSound != null) {
channel.setSound(messageSound, new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
.setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT)
.build());
}
if (Build.VERSION.SDK_INT >= CONVERSATION_SUPPORT_VERSION && shortcutId != null) {
channel.setConversationId(getMessagesChannel(), shortcutId);
}
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
notificationManager.createNotificationChannel(channel);
return channelId;
}
/**
* Deletes the channel generated for the provided recipient. Safe to call even if there was never
* a channel made for that recipient.
*/
public synchronized void deleteChannelFor(@NonNull Recipient recipient) {
if (!supported()) {
return;
}
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
String channel = recipient.getNotificationChannel();
if (channel != null) {
Log.i(TAG, "Deleting channel");
notificationManager.deleteNotificationChannel(channel);
}
}
/**
* Updates the LED color for message notifications and all contact-specific message notification
* channels. Performs database operations and should therefore be invoked on a background thread.
*/
@WorkerThread
public static synchronized void updateMessagesLedColor(@NonNull Context context, @NonNull String color) {
public synchronized void updateMessagesLedColor(@NonNull String color) {
if (!supported()) {
return;
}
@ -266,25 +294,25 @@ public class NotificationChannels {
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
updateMessageChannel(context, channel -> setLedPreference(channel, color));
updateAllRecipientChannelLedColors(context, notificationManager, color);
updateMessageChannel(channel -> setLedPreference(channel, color));
updateAllRecipientChannelLedColors(notificationManager, color);
ensureCustomChannelConsistency(context);
ensureCustomChannelConsistency();
}
/**
* @return The message ringtone set for the default message channel.
*/
public static synchronized @NonNull Uri getMessageRingtone(@NonNull Context context) {
public synchronized @NonNull Uri getMessageRingtone() {
if (!supported()) {
return Uri.EMPTY;
}
Uri sound = ServiceUtil.getNotificationManager(context).getNotificationChannel(getMessagesChannel(context)).getSound();
Uri sound = ServiceUtil.getNotificationManager(context).getNotificationChannel(getMessagesChannel()).getSound();
return sound == null ? Uri.EMPTY : sound;
}
public static synchronized @Nullable Uri getMessageRingtone(@NonNull Context context, @NonNull Recipient recipient) {
public synchronized @Nullable Uri getMessageRingtone(@NonNull Recipient recipient) {
if (!supported() || recipient.resolve().getNotificationChannel() == null) {
return null;
}
@ -304,13 +332,13 @@ public class NotificationChannels {
/**
* Update the message ringtone for the default message channel.
*/
public static synchronized void updateMessageRingtone(@NonNull Context context, @Nullable Uri uri) {
public synchronized void updateMessageRingtone(@Nullable Uri uri) {
if (!supported()) {
return;
}
Log.i(TAG, "Updating default message ringtone with URI: " + String.valueOf(uri));
Log.i(TAG, "Updating default message ringtone with URI: " + uri);
updateMessageChannel(context, channel -> {
updateMessageChannel(channel -> {
channel.setSound(uri == null ? Settings.System.DEFAULT_NOTIFICATION_URI : uri, getRingtoneAudioAttributes());
});
}
@ -322,11 +350,11 @@ public class NotificationChannels {
* This has to update the database, and therefore should be run on a background thread.
*/
@WorkerThread
public static synchronized void updateMessageRingtone(@NonNull Context context, @NonNull Recipient recipient, @Nullable Uri uri) {
public synchronized void updateMessageRingtone(@NonNull Recipient recipient, @Nullable Uri uri) {
if (!supported() || recipient.getNotificationChannel() == null) {
return;
}
Log.i(TAG, "Updating recipient message ringtone with URI: " + String.valueOf(uri));
Log.i(TAG, "Updating recipient message ringtone with URI: " + uri);
String newChannelId = generateChannelIdFor(recipient);
boolean success = updateExistingChannel(ServiceUtil.getNotificationManager(context),
@ -335,27 +363,27 @@ public class NotificationChannels {
channel -> channel.setSound(uri == null ? Settings.System.DEFAULT_NOTIFICATION_URI : uri, getRingtoneAudioAttributes()));
SignalDatabase.recipients().setNotificationChannel(recipient.getId(), success ? newChannelId : null);
ensureCustomChannelConsistency(context);
ensureCustomChannelConsistency();
}
/**
* @return The vibrate settings for the default message channel.
*/
public static synchronized boolean getMessageVibrate(@NonNull Context context) {
public synchronized boolean getMessageVibrate() {
if (!supported()) {
return false;
}
return ServiceUtil.getNotificationManager(context).getNotificationChannel(getMessagesChannel(context)).shouldVibrate();
return ServiceUtil.getNotificationManager(context).getNotificationChannel(getMessagesChannel()).shouldVibrate();
}
/**
* @return The vibrate setting for a specific recipient. If that recipient has no channel, this
* will return the setting for the default message channel.
*/
public static synchronized boolean getMessageVibrate(@NonNull Context context, @NonNull Recipient recipient) {
public synchronized boolean getMessageVibrate(@NonNull Recipient recipient) {
if (!supported()) {
return getMessageVibrate(context);
return getMessageVibrate();
}
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
@ -363,7 +391,7 @@ public class NotificationChannels {
if (!channelExists(channel)) {
Log.w(TAG, "Recipient didn't have a channel. Returning message default.");
return getMessageVibrate(context);
return getMessageVibrate();
}
return channel.shouldVibrate() && !Arrays.equals(channel.getVibrationPattern(), EMPTY_VIBRATION_PATTERN);
@ -372,13 +400,13 @@ public class NotificationChannels {
/**
* Sets the vibrate property for the default message channel.
*/
public static synchronized void updateMessageVibrate(@NonNull Context context, boolean enabled) {
public synchronized void updateMessageVibrate(boolean enabled) {
if (!supported()) {
return;
}
Log.i(TAG, "Updating default vibrate with value: " + enabled);
updateMessageChannel(context, channel -> setVibrationEnabled(channel, enabled));
updateMessageChannel(channel -> setVibrationEnabled(channel, enabled));
}
/**
@ -388,13 +416,13 @@ public class NotificationChannels {
* This has to update the database and should therefore be run on a background thread.
*/
@WorkerThread
public static synchronized void updateMessageVibrate(@NonNull Context context, @NonNull Recipient recipient, VibrateState vibrateState) {
public synchronized void updateMessageVibrate(@NonNull Recipient recipient, VibrateState vibrateState) {
if (!supported() || recipient.getNotificationChannel() == null) {
return ;
}
Log.i(TAG, "Updating recipient vibrate with value: " + vibrateState);
boolean enabled = vibrateState == VibrateState.DEFAULT ? getMessageVibrate(context) : vibrateState == VibrateState.ENABLED;
boolean enabled = vibrateState == VibrateState.DEFAULT ? getMessageVibrate() : vibrateState == VibrateState.ENABLED;
String newChannelId = generateChannelIdFor(recipient);
boolean success = updateExistingChannel(ServiceUtil.getNotificationManager(context),
@ -403,7 +431,7 @@ public class NotificationChannels {
channel -> setVibrationEnabled(channel, enabled));
SignalDatabase.recipients().setNotificationChannel(recipient.getId(), success ? newChannelId : null);
ensureCustomChannelConsistency(context);
ensureCustomChannelConsistency();
}
/**
@ -415,7 +443,7 @@ public class NotificationChannels {
* Likewise, setting the pattern to any non-zero length array will set enable vibration flag to true.
*/
@TargetApi(26)
private static void setVibrationEnabled(@NonNull NotificationChannel channel, boolean enabled) {
private void setVibrationEnabled(@NonNull NotificationChannel channel, boolean enabled) {
if (enabled) {
channel.setVibrationPattern(null);
channel.enableVibration(true);
@ -431,16 +459,16 @@ public class NotificationChannels {
*
* This could also return true if the specific channnel is enabled, but notifications *overall*
* are disabled, or the messages category is disabled. Check
* {@link #areNotificationsEnabled(Context)} and {@link #isMessagesChannelGroupEnabled(Context)}
* {@link #areNotificationsEnabled()} and {@link #isMessagesChannelGroupEnabled()}
* to be safe.
*/
public static synchronized boolean isMessageChannelEnabled(@NonNull Context context) {
public synchronized boolean isMessageChannelEnabled() {
if (!supported()) {
return true;
}
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
NotificationChannel channel = notificationManager.getNotificationChannel(getMessagesChannel(context));
NotificationChannel channel = notificationManager.getNotificationChannel(getMessagesChannel());
return channel != null && channel.getImportance() != NotificationManager.IMPORTANCE_NONE;
}
@ -448,9 +476,9 @@ public class NotificationChannels {
/**
* Whether or not the notification category for messages is enabled. Note that even if it is,
* a user could have blocked the specific channel, or notifications overall, and it'd still be
* true. See {@link #isMessageChannelEnabled(Context)} and {@link #areNotificationsEnabled(Context)}.
* true. See {@link #isMessageChannelEnabled()} and {@link #areNotificationsEnabled()}.
*/
public static synchronized boolean isMessagesChannelGroupEnabled(@NonNull Context context) {
public synchronized boolean isMessagesChannelGroupEnabled() {
if (Build.VERSION.SDK_INT < 28) {
return true;
}
@ -461,7 +489,7 @@ public class NotificationChannels {
return group != null && !group.isBlocked();
}
public static boolean isCallsChannelValid(@NonNull Context context) {
public synchronized boolean isCallsChannelValid() {
if (!supported()) {
return true;
}
@ -472,17 +500,6 @@ public class NotificationChannels {
return channel != null && channel.getImportance() == NotificationManager.IMPORTANCE_HIGH;
}
/**
* Whether or not notifications for the entire app are enabled.
*/
public static synchronized boolean areNotificationsEnabled(@NonNull Context context) {
if (Build.VERSION.SDK_INT >= 24) {
return ServiceUtil.getNotificationManager(context).areNotificationsEnabled();
} else {
return true;
}
}
/**
* Attempt to update a recipient with shortcut based notification channel if the system made one for us and we don't
* have a channel set yet.
@ -490,7 +507,7 @@ public class NotificationChannels {
* @return true if a shortcut based notification channel was found and then associated with the recipient, false otherwise
*/
@WorkerThread
public static boolean updateWithShortcutBasedChannel(@NonNull Context context, @NonNull Recipient recipient) {
public boolean updateWithShortcutBasedChannel(@NonNull Recipient recipient) {
if (Build.VERSION.SDK_INT >= CONVERSATION_SUPPORT_VERSION && TextUtils.isEmpty(recipient.getNotificationChannel())) {
String shortcutId = ConversationUtil.getShortcutId(recipient);
@ -513,7 +530,7 @@ public class NotificationChannels {
* Updates the name of an existing channel to match the recipient's current name. Will have no
* effect if the recipient doesn't have an existing valid channel.
*/
public static synchronized void updateContactChannelName(@NonNull Context context, @NonNull Recipient recipient) {
public synchronized void updateContactChannelName(@NonNull Recipient recipient) {
if (!supported() || recipient.getNotificationChannel() == null) {
return;
}
@ -535,7 +552,7 @@ public class NotificationChannels {
@TargetApi(26)
@WorkerThread
public static synchronized void ensureCustomChannelConsistency(@NonNull Context context) {
public synchronized void ensureCustomChannelConsistency() {
if (!supported()) {
return;
}
@ -576,7 +593,7 @@ public class NotificationChannels {
} else 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.");
notificationManager.deleteNotificationChannel(existingChannel.getId());
} else if (existingChannel.getId().startsWith(MESSAGES_PREFIX) && !existingChannel.getId().equals(getMessagesChannel(context))) {
} else if (existingChannel.getId().startsWith(MESSAGES_PREFIX) && !existingChannel.getId().equals(getMessagesChannel())) {
Log.i(TAG, "Consistency: Deleting channel '" + existingChannel.getId() + "' because it's out of date.");
notificationManager.deleteNotificationChannel(existingChannel.getId());
}
@ -590,12 +607,19 @@ public class NotificationChannels {
}
}
public synchronized void onLocaleChanged() {
if (!supported()) {
return;
}
onCreate(ServiceUtil.getNotificationManager(context));
}
@TargetApi(26)
private static void onCreate(@NonNull Context context, @NonNull NotificationManager notificationManager) {
private void onCreate(@NonNull NotificationManager notificationManager) {
NotificationChannelGroup messagesGroup = new NotificationChannelGroup(CATEGORY_MESSAGES, context.getResources().getString(R.string.NotificationChannel_group_chats));
notificationManager.createNotificationChannelGroup(messagesGroup);
NotificationChannel messages = new NotificationChannel(getMessagesChannel(context), context.getString(R.string.NotificationChannel_channel_messages), NotificationManager.IMPORTANCE_HIGH);
NotificationChannel messages = new NotificationChannel(getMessagesChannel(), context.getString(R.string.NotificationChannel_channel_messages), NotificationManager.IMPORTANCE_HIGH);
NotificationChannel calls = new NotificationChannel(CALLS, context.getString(R.string.NotificationChannel_calls), NotificationManager.IMPORTANCE_HIGH);
NotificationChannel failures = new NotificationChannel(FAILURES, context.getString(R.string.NotificationChannel_failures), NotificationManager.IMPORTANCE_HIGH);
NotificationChannel backups = new NotificationChannel(BACKUPS, context.getString(R.string.NotificationChannel_backups), NotificationManager.IMPORTANCE_LOW);
@ -708,7 +732,7 @@ public class NotificationChannels {
@WorkerThread
@TargetApi(26)
private static void updateAllRecipientChannelLedColors(@NonNull Context context, @NonNull NotificationManager notificationManager, @NonNull String color) {
private void updateAllRecipientChannelLedColors(@NonNull NotificationManager notificationManager, @NonNull String color) {
RecipientDatabase database = SignalDatabase.recipients();
try (RecipientDatabase.RecipientReader recipients = database.getRecipientsWithNotificationChannels()) {
@ -723,11 +747,11 @@ public class NotificationChannels {
}
}
ensureCustomChannelConsistency(context);
ensureCustomChannelConsistency();
}
@TargetApi(26)
private static void updateMessageChannel(@NonNull Context context, @NonNull ChannelUpdater updater) {
private void updateMessageChannel(@NonNull ChannelUpdater updater) {
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
int existingVersion = TextSecurePreferences.getNotificationMessagesChannelVersion(context);
int newVersion = existingVersion + 1;
@ -736,7 +760,7 @@ public class NotificationChannels {
if (updateExistingChannel(notificationManager, getMessagesChannelId(existingVersion), getMessagesChannelId(newVersion), updater)) {
TextSecurePreferences.setNotificationMessagesChannelVersion(context, newVersion);
} else {
onCreate(context, notificationManager);
onCreate(notificationManager);
}
}

View file

@ -120,7 +120,7 @@ class DefaultMessageNotifier(context: Application) : MessageNotifier {
reminderCount: Int,
defaultBubbleState: BubbleState
) {
NotificationChannels.ensureCustomChannelConsistency(context)
NotificationChannels.getInstance().ensureCustomChannelConsistency()
val currentLockStatus: Boolean = KeyCachingService.isLocked(context)
val currentPrivacyPreference: NotificationPrivacyPreference = SignalStore.settings().messageNotificationsPrivacy

View file

@ -186,7 +186,7 @@ sealed class NotificationBuilder(protected val context: Context) {
* Notification builder using solely androidx/compat libraries.
*/
private class NotificationBuilderCompat(context: Context) : NotificationBuilder(context) {
val builder: NotificationCompat.Builder = NotificationCompat.Builder(context, NotificationChannels.getMessagesChannel(context))
val builder: NotificationCompat.Builder = NotificationCompat.Builder(context, NotificationChannels.getInstance().messagesChannel)
override fun addActions(replyMethod: ReplyMethod, conversation: NotificationConversation) {
val extender: NotificationCompat.WearableExtender = NotificationCompat.WearableExtender()

View file

@ -103,7 +103,7 @@ data class NotificationConversation(
return if (isOnlyContactJoinedEvent) {
NotificationChannels.JOIN_EVENTS
} else {
recipient.notificationChannel ?: NotificationChannels.getMessagesChannel(context)
recipient.notificationChannel ?: NotificationChannels.getInstance().messagesChannel
}
}

View file

@ -237,7 +237,7 @@ object NotificationFactory {
setCategory(NotificationCompat.CATEGORY_MESSAGE)
setGroup(DefaultMessageNotifier.NOTIFICATION_GROUP)
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
setChannelId(NotificationChannels.getMessagesChannel(context))
setChannelId(NotificationChannels.getInstance().messagesChannel)
setContentTitle(context.getString(R.string.app_name))
setContentIntent(NotificationPendingIntentHelper.getActivity(context, 0, MainActivity.clearTop(context), PendingIntentFlags.mutable()))
setGroupSummary(true)
@ -269,7 +269,7 @@ object NotificationFactory {
}
val uri: Uri = if (NotificationChannels.supported()) {
NotificationChannels.getMessageRingtone(context, recipient) ?: NotificationChannels.getMessageRingtone(context)
NotificationChannels.getInstance().getMessageRingtone(recipient) ?: NotificationChannels.getInstance().messageRingtone
} else {
recipient.messageRingtone ?: SignalStore.settings().messageNotificationSound
}
@ -402,7 +402,7 @@ object NotificationFactory {
if (threadRecipient != null) {
SignalExecutors.BOUNDED.execute {
SignalDatabase.recipients.setMessageRingtone(threadRecipient.id, null)
NotificationChannels.updateMessageRingtone(context, threadRecipient, null)
NotificationChannels.getInstance().updateMessageRingtone(threadRecipient, null)
}
}
} catch (runtimeException: RuntimeException) {

View file

@ -292,7 +292,7 @@ public final class RestoreBackupFragment extends LoggingFragment {
passphrase);
SignalDatabase.upgradeRestored(database);
NotificationChannels.restoreContactNotificationChannels(context);
NotificationChannels.getInstance().restoreContactNotificationChannels();
enableBackups(context);

View file

@ -53,7 +53,7 @@ public final class ConversationUtil {
public static @NonNull String getChannelId(@NonNull Context context, @NonNull Recipient recipient) {
Recipient resolved = recipient.resolve();
return resolved.getNotificationChannel() != null ? resolved.getNotificationChannel() : NotificationChannels.getMessagesChannel(context);
return resolved.getNotificationChannel() != null ? resolved.getNotificationChannel() : NotificationChannels.getInstance().getMessagesChannel();
}
/**

View file

@ -288,7 +288,7 @@ public class TextSecurePreferences {
public static void onPostBackupRestore(@NonNull Context context) {
if (NotificationChannels.supported()) {
NotificationChannels.updateMessageVibrate(context, SignalStore.settings().isMessageVibrateEnabled());
NotificationChannels.getInstance().updateMessageVibrate(SignalStore.settings().isMessageVibrateEnabled());
}
}