mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 21:15:48 +00:00
We manually play the ringtone when in-thread notifications are enabled, but we weren't using the sound specified by the channel in the system settings. This fixes that problem by reading the NotificationChannel setting.
363 lines
16 KiB
Java
363 lines
16 KiB
Java
package org.thoughtcrime.securesms.notifications;
|
|
|
|
import android.annotation.TargetApi;
|
|
import android.app.NotificationChannel;
|
|
import android.app.NotificationChannelGroup;
|
|
import android.app.NotificationManager;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.graphics.Color;
|
|
import android.media.AudioAttributes;
|
|
import android.net.Uri;
|
|
import android.os.Build;
|
|
import android.provider.Settings;
|
|
import android.support.annotation.NonNull;
|
|
import android.support.annotation.Nullable;
|
|
import android.support.annotation.WorkerThread;
|
|
import android.text.TextUtils;
|
|
|
|
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;
|
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
import org.whispersystems.libsignal.logging.Log;
|
|
|
|
import java.util.Arrays;
|
|
|
|
public class NotificationChannels {
|
|
|
|
private static final String TAG = NotificationChannels.class.getSimpleName();
|
|
|
|
private static final int VERSION_MESSAGES_CATEGORY = 2;
|
|
|
|
private static final int VERSION = 2;
|
|
|
|
private static final String CATEGORY_MESSAGES = "messages";
|
|
private static final String CONTACT_PREFIX = "contact_";
|
|
private static final String MESSAGES_PREFIX = "messages_";
|
|
|
|
public static final String CALLS = "calls_v2";
|
|
public static final String FAILURES = "failures";
|
|
public static final String APP_UPDATES = "app_updates";
|
|
public static final String BACKUPS = "backups_v2";
|
|
public static final String LOCKED_STATUS = "locked_status_v2";
|
|
public static final String OTHER = "other_v2";
|
|
|
|
/**
|
|
* Ensures all of the notification channels are created. No harm in repeat calls. Call is safely
|
|
* ignored for API < 26.
|
|
*/
|
|
public static void create(@NonNull Context context) {
|
|
if (!supported()) {
|
|
return;
|
|
}
|
|
|
|
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
|
|
if (notificationManager == null) {
|
|
Log.w(TAG, "Unable to retrieve notification manager. Can't setup channels.");
|
|
return;
|
|
}
|
|
|
|
int oldVersion = TextSecurePreferences.getNotificationChannelVersion(context);
|
|
if (oldVersion != VERSION) {
|
|
onUpgrade(notificationManager, oldVersion, VERSION);
|
|
TextSecurePreferences.setNotificationChannelVersion(context, VERSION);
|
|
}
|
|
|
|
onCreate(context, notificationManager);
|
|
}
|
|
|
|
/**
|
|
* @return The channel ID for the default messages channel. Prefer
|
|
* {@link Recipient#getNotificationChannel(Context)} if you know the recipient.
|
|
*/
|
|
public static @NonNull String getMessagesChannel(@NonNull Context context) {
|
|
return getMessagesChannelId(TextSecurePreferences.getNotificationMessagesChannelVersion(context));
|
|
}
|
|
|
|
/**
|
|
* @return Whether or not notification channels are supported.
|
|
*/
|
|
public static boolean supported() {
|
|
return Build.VERSION.SDK_INT >= 26;
|
|
}
|
|
|
|
public static String getChannelDisplayNameFor(@Nullable String systemName, @Nullable String profileName, @NonNull Address address) {
|
|
return TextUtils.isEmpty(systemName) ? (TextUtils.isEmpty(profileName) ? address.serialize() : profileName) : systemName;
|
|
}
|
|
|
|
/**
|
|
* Creates a channel for the specified recipient.
|
|
* @return The channel ID for the newly-created channel.
|
|
*/
|
|
public static String createChannelFor(@NonNull Context context, @NonNull Recipient recipient) {
|
|
VibrateState vibrateState = recipient.getMessageVibrate();
|
|
boolean vibrationEnabled = vibrateState == VibrateState.DEFAULT ? TextSecurePreferences.isNotificationVibrateEnabled(context) : vibrateState == VibrateState.ENABLED;
|
|
String displayName = getChannelDisplayNameFor(recipient.getName(), recipient.getProfileName(), recipient.getAddress());
|
|
|
|
return createChannelFor(context, recipient.getAddress(), displayName, recipient.getMessageRingtone(context), vibrationEnabled);
|
|
}
|
|
|
|
/**
|
|
* More verbose version of {@link #createChannelFor(Context, Recipient)}.
|
|
*/
|
|
public static String createChannelFor(@NonNull Context context,
|
|
@NonNull Address address,
|
|
@NonNull String displayName,
|
|
@Nullable Uri messageSound,
|
|
boolean vibrationEnabled)
|
|
{
|
|
if (!supported()) {
|
|
return getMessagesChannel(context);
|
|
}
|
|
|
|
String channelId = generateChannelIdFor(address);
|
|
NotificationChannel channel = new NotificationChannel(channelId, displayName, NotificationManager.IMPORTANCE_HIGH);
|
|
|
|
setLedPreference(channel, TextSecurePreferences.getNotificationLedColor(context));
|
|
channel.setGroup(CATEGORY_MESSAGES);
|
|
channel.enableVibration(vibrationEnabled);
|
|
channel.setSound(messageSound, new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
|
|
.setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT)
|
|
.build());
|
|
|
|
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
|
|
if (notificationManager == null) {
|
|
Log.w(TAG, "Unable to retrieve notification manager. Cannot create channel for recipient.");
|
|
return channelId;
|
|
}
|
|
|
|
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 void deleteChannelFor(@NonNull Context context, @NonNull Recipient recipient) {
|
|
if (!supported()) {
|
|
return;
|
|
}
|
|
|
|
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
|
|
if (notificationManager == null) {
|
|
Log.w(TAG, "Unable to retrieve notification manager. Cannot delete channel.");
|
|
return;
|
|
}
|
|
|
|
String channel = recipient.getNotificationChannel(context);
|
|
|
|
if (!TextUtils.isEmpty(channel) && !getMessagesChannel(context).equals(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) {
|
|
if (!supported()) {
|
|
return;
|
|
}
|
|
|
|
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
|
|
intent.putExtra(Settings.EXTRA_CHANNEL_ID, channelId);
|
|
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
|
|
context.startActivity(intent);
|
|
}
|
|
|
|
/**
|
|
* 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 void updateMessagesLedColor(@NonNull Context context, @NonNull String color) {
|
|
if (!supported()) {
|
|
return;
|
|
}
|
|
|
|
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
|
|
if (notificationManager == null) {
|
|
Log.w(TAG, "Unable to retrieve notification manager. Cannot update led color.");
|
|
return;
|
|
}
|
|
|
|
updateMessageChannelLedColor(context, notificationManager, color);
|
|
updateAllRecipientChannelLedColors(context, notificationManager, color);
|
|
}
|
|
|
|
/**
|
|
* 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 void updateContactChannelName(@NonNull Context context, @NonNull Recipient recipient) {
|
|
if (!supported() || !recipient.hasCustomNotifications()) {
|
|
return;
|
|
}
|
|
|
|
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
|
|
if (notificationManager == null) {
|
|
Log.w(TAG, "Unable to retrieve notification manager. Cannot update channel name.");
|
|
return;
|
|
}
|
|
|
|
if (notificationManager.getNotificationChannel(recipient.getNotificationChannel(context)) == null) {
|
|
Log.w(TAG, "Tried to update the name of a channel, but that channel doesn't exist.");
|
|
return;
|
|
}
|
|
|
|
NotificationChannel channel = new NotificationChannel(recipient.getNotificationChannel(context),
|
|
getChannelDisplayNameFor(recipient.getName(), recipient.getProfileName(), recipient.getAddress()),
|
|
NotificationManager.IMPORTANCE_HIGH);
|
|
channel.setGroup(CATEGORY_MESSAGES);
|
|
notificationManager.createNotificationChannel(channel);
|
|
}
|
|
|
|
public static @Nullable Uri getMessageRingtone(@NonNull Context context, @NonNull Recipient recipient) {
|
|
if (!supported()) {
|
|
return null;
|
|
}
|
|
|
|
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
|
|
if (notificationManager == null) {
|
|
Log.w(TAG, "Unable to retrieve notification manager. Cannot update channel name.");
|
|
return null;
|
|
}
|
|
|
|
NotificationChannel channel = notificationManager.getNotificationChannel(recipient.getNotificationChannel(context));
|
|
if (channel == null) {
|
|
Log.w(TAG, "Recipient had an invalid channel. Returning null.");
|
|
return null;
|
|
}
|
|
|
|
return channel.getSound();
|
|
}
|
|
|
|
@TargetApi(26)
|
|
private static void onCreate(@NonNull Context context, @NonNull NotificationManager notificationManager) {
|
|
NotificationChannelGroup messagesGroup = new NotificationChannelGroup(CATEGORY_MESSAGES, context.getResources().getString(R.string.NotificationChannel_group_messages));
|
|
notificationManager.createNotificationChannelGroup(messagesGroup);
|
|
|
|
NotificationChannel messages = new NotificationChannel(getMessagesChannel(context), context.getString(R.string.NotificationChannel_messages), NotificationManager.IMPORTANCE_HIGH);
|
|
NotificationChannel calls = new NotificationChannel(CALLS, context.getString(R.string.NotificationChannel_calls), NotificationManager.IMPORTANCE_LOW);
|
|
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);
|
|
NotificationChannel lockedStatus = new NotificationChannel(LOCKED_STATUS, context.getString(R.string.NotificationChannel_locked_status), NotificationManager.IMPORTANCE_LOW);
|
|
NotificationChannel other = new NotificationChannel(OTHER, context.getString(R.string.NotificationChannel_other), NotificationManager.IMPORTANCE_LOW);
|
|
|
|
messages.setGroup(CATEGORY_MESSAGES);
|
|
messages.enableVibration(TextSecurePreferences.isNotificationVibrateEnabled(context));
|
|
messages.setSound(TextSecurePreferences.getNotificationRingtone(context), new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
|
|
.setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT)
|
|
.build());
|
|
setLedPreference(messages, TextSecurePreferences.getNotificationLedColor(context));
|
|
|
|
calls.setShowBadge(false);
|
|
backups.setShowBadge(false);
|
|
lockedStatus.setShowBadge(false);
|
|
other.setShowBadge(false);
|
|
|
|
notificationManager.createNotificationChannels(Arrays.asList(messages, calls, failures, backups, lockedStatus, other));
|
|
|
|
if (BuildConfig.PLAY_STORE_DISABLED) {
|
|
NotificationChannel appUpdates = new NotificationChannel(APP_UPDATES, context.getString(R.string.NotificationChannel_app_updates), NotificationManager.IMPORTANCE_HIGH);
|
|
notificationManager.createNotificationChannel(appUpdates);
|
|
} else {
|
|
notificationManager.deleteNotificationChannel(APP_UPDATES);
|
|
}
|
|
}
|
|
|
|
@TargetApi(26)
|
|
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) {
|
|
notificationManager.deleteNotificationChannel("messages");
|
|
notificationManager.deleteNotificationChannel("calls");
|
|
notificationManager.deleteNotificationChannel("locked_status");
|
|
notificationManager.deleteNotificationChannel("backups");
|
|
notificationManager.deleteNotificationChannel("other");
|
|
}
|
|
}
|
|
|
|
@TargetApi(26)
|
|
private static void setLedPreference(@NonNull NotificationChannel channel, @NonNull String ledColor) {
|
|
if ("none".equals(ledColor)) {
|
|
channel.enableLights(false);
|
|
} else {
|
|
channel.enableLights(true);
|
|
channel.setLightColor(Color.parseColor(ledColor));
|
|
}
|
|
}
|
|
|
|
|
|
private static @NonNull String generateChannelIdFor(@NonNull Address address) {
|
|
return CONTACT_PREFIX + address.serialize() + "_" + System.currentTimeMillis();
|
|
}
|
|
|
|
@TargetApi(26)
|
|
private static @NonNull NotificationChannel copyChannel(@NonNull NotificationChannel original, @NonNull String id) {
|
|
NotificationChannel copy = new NotificationChannel(id, original.getName(), original.getImportance());
|
|
|
|
copy.setGroup(original.getGroup());
|
|
copy.setSound(original.getSound(), original.getAudioAttributes());
|
|
copy.setBypassDnd(original.canBypassDnd());
|
|
copy.enableVibration(original.shouldVibrate());
|
|
copy.setVibrationPattern(original.getVibrationPattern());
|
|
copy.setLockscreenVisibility(original.getLockscreenVisibility());
|
|
copy.setShowBadge(original.canShowBadge());
|
|
copy.setLightColor(original.getLightColor());
|
|
copy.enableLights(original.shouldShowLights());
|
|
|
|
return copy;
|
|
}
|
|
|
|
private static String getMessagesChannelId(int version) {
|
|
return MESSAGES_PREFIX + version;
|
|
}
|
|
|
|
@TargetApi(26)
|
|
private static void updateMessageChannelLedColor(@NonNull Context context, @NonNull NotificationManager notificationManager, @NonNull String color) {
|
|
int existingVersion = TextSecurePreferences.getNotificationMessagesChannelVersion(context);
|
|
NotificationChannel existingChannel = notificationManager.getNotificationChannel(getMessagesChannelId(existingVersion));
|
|
|
|
notificationManager.deleteNotificationChannel(existingChannel.getId());
|
|
|
|
int newVersion = existingVersion + 1;
|
|
NotificationChannel newChannel = copyChannel(existingChannel, getMessagesChannelId(newVersion));
|
|
|
|
setLedPreference(newChannel, color);
|
|
notificationManager.createNotificationChannel(newChannel);
|
|
|
|
TextSecurePreferences.setNotificationMessagesChannelVersion(context, newVersion);
|
|
}
|
|
|
|
@WorkerThread
|
|
@TargetApi(26)
|
|
private static void updateAllRecipientChannelLedColors(@NonNull Context context, @NonNull NotificationManager notificationManager, @NonNull String color) {
|
|
RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context);
|
|
|
|
try (RecipientDatabase.RecipientReader recipients = database.getRecipientsWithNotificationChannels()) {
|
|
Recipient recipient;
|
|
while ((recipient = recipients.getNext()) != null) {
|
|
NotificationChannel existingChannel = notificationManager.getNotificationChannel(recipient.getNotificationChannel(context));
|
|
notificationManager.deleteNotificationChannel(existingChannel.getId());
|
|
|
|
NotificationChannel newChannel = copyChannel(existingChannel, generateChannelIdFor(recipient.getAddress()));
|
|
newChannel.setGroup(CATEGORY_MESSAGES);
|
|
setLedPreference(newChannel, color);
|
|
|
|
database.setNotificationChannel(recipient, newChannel.getId());
|
|
|
|
notificationManager.createNotificationChannel(newChannel);
|
|
}
|
|
}
|
|
}
|
|
}
|