summaryrefslogtreecommitdiffstats
path: root/java/com/android/dialer/notification/NotificationChannelManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/dialer/notification/NotificationChannelManager.java')
-rw-r--r--java/com/android/dialer/notification/NotificationChannelManager.java456
1 files changed, 123 insertions, 333 deletions
diff --git a/java/com/android/dialer/notification/NotificationChannelManager.java b/java/com/android/dialer/notification/NotificationChannelManager.java
index 88679066d..790aac36f 100644
--- a/java/com/android/dialer/notification/NotificationChannelManager.java
+++ b/java/com/android/dialer/notification/NotificationChannelManager.java
@@ -17,366 +17,156 @@
package com.android.dialer.notification;
import android.annotation.TargetApi;
-import android.app.Notification;
import android.app.NotificationChannel;
-import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
import android.media.AudioAttributes;
-import android.net.Uri;
import android.os.Build.VERSION_CODES;
-import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.annotation.RequiresApi;
-import android.support.annotation.StringDef;
import android.support.v4.os.BuildCompat;
-import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
-import android.telecom.TelecomManager;
-import android.telephony.TelephonyManager;
-import com.android.contacts.common.compat.TelephonyManagerCompat;
-import com.android.dialer.buildtype.BuildType;
+import android.util.ArraySet;
+import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
-import com.android.dialer.common.concurrent.DialerExecutors;
-import com.android.dialer.telecom.TelecomUtil;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-import java.util.Objects;
+import java.util.Set;
-/** Contains info on how to create {@link NotificationChannel NotificationChannels} */
-public class NotificationChannelManager {
-
- private static final String PREFS_FILENAME = "NotificationChannelManager";
- private static final String PREF_NEED_FIRST_INIT = "needFirstInit";
- private static NotificationChannelManager instance;
-
- public static NotificationChannelManager getInstance() {
- if (instance == null) {
- instance = new NotificationChannelManager();
- }
- return instance;
- }
+/** Creates all notification channels for Dialer. */
+@TargetApi(VERSION_CODES.O)
+public final class NotificationChannelManager {
/**
- * Set the channel of notification appropriately. Will create the channel if it does not already
- * exist. Safe to call pre-O (will no-op).
+ * Creates all the notification channels Dialer will need. This method is called at app startup
+ * and must be fast. Currently it takes between 3 to 7 milliseconds on a Pixel XL.
+ *
+ * <p>An alternative approach would be to lazily create channels when we actualy post a
+ * notification. The advatange to precreating channels is that:
*
- * <p>phoneAccount should only be null if channelName is {@link Channel#DEFAULT} or {@link
- * Channel#MISSED_CALL} since these do not have account-specific settings.
+ * <ul>
+ * <li>channels will be available to user right away. For example, users can customize voicemail
+ * sounds when they first get their device without waiting for a voicemail to arrive first.
+ * <li>code that posts a notification can be simpler
+ * <li>channel management code is simpler and it's easier to ensure that the correct set of
+ * channels are visible.
+ * <ul>
*/
- @TargetApi(26)
- public static void applyChannel(
- @NonNull Notification.Builder notification,
- @NonNull Context context,
- @Channel String channelName,
- @Nullable PhoneAccountHandle phoneAccount) {
- checkNullity(channelName, phoneAccount);
-
- if (BuildCompat.isAtLeastO()) {
- NotificationChannel channel =
- NotificationChannelManager.getInstance().getChannel(context, channelName, phoneAccount);
- notification.setChannelId(channel.getId());
- }
- }
-
- private static void checkNullity(
- @Channel String channelName, @Nullable PhoneAccountHandle phoneAccount) {
- if (phoneAccount != null || channelAllowsNullPhoneAccountHandle(channelName)) {
- return;
- }
-
- // TODO (b/36568553): don't throw an exception once most cases have been identified
- IllegalArgumentException exception =
- new IllegalArgumentException(
- "Phone account handle must not be null on channel " + channelName);
- if (BuildType.get() == BuildType.RELEASE) {
- LogUtil.e("NotificationChannelManager.applyChannel", null, exception);
- } else {
- throw exception;
- }
- }
-
- private static boolean channelAllowsNullPhoneAccountHandle(@Channel String channelName) {
- switch (channelName) {
- case Channel.DEFAULT:
- case Channel.MISSED_CALL:
- return true;
- default:
- return false;
- }
- }
+ public static void initChannels(@NonNull Context context) {
+ Assert.checkArgument(BuildCompat.isAtLeastO());
+ Assert.isNotNull(context);
- /** The base Channel IDs for {@link NotificationChannel} */
- @Retention(RetentionPolicy.SOURCE)
- @StringDef({
- Channel.INCOMING_CALL,
- Channel.ONGOING_CALL,
- Channel.ONGOING_CALL_OLD,
- Channel.MISSED_CALL,
- Channel.VOICEMAIL,
- Channel.EXTERNAL_CALL,
- Channel.DEFAULT
- })
- public @interface Channel {
- @Deprecated String ONGOING_CALL_OLD = "ongoingCall";
- String INCOMING_CALL = "incomingCall";
- String ONGOING_CALL = "ongoingCall2";
- String MISSED_CALL = "missedCall";
- String VOICEMAIL = "voicemail";
- String EXTERNAL_CALL = "externalCall";
- String DEFAULT = "default";
- }
-
- @Channel
- private static final String[] prepopulatedAccountChannels =
- new String[] {Channel.INCOMING_CALL, Channel.ONGOING_CALL, Channel.VOICEMAIL};
-
- @Channel
- private static final String[] prepopulatedGlobalChannels =
- new String[] {Channel.MISSED_CALL, Channel.DEFAULT};
-
- private NotificationChannelManager() {}
-
- public void firstInitIfNeeded(@NonNull Context context) {
- if (BuildCompat.isAtLeastO()) {
- DialerExecutors.createNonUiTaskBuilder(this::firstInitIfNeededSync)
- .build()
- .executeSerial(context);
- }
- }
-
- private boolean firstInitIfNeededSync(@NonNull Context context) {
- if (needsFirstInit(context)) {
- initChannels(context);
- return true;
- }
- return false;
- }
-
- public boolean needsFirstInit(@NonNull Context context) {
- return (BuildCompat.isAtLeastO()
- && getSharedPreferences(context).getBoolean(PREF_NEED_FIRST_INIT, true));
- }
-
- @RequiresApi(VERSION_CODES.N)
- private SharedPreferences getSharedPreferences(@NonNull Context context) {
- // Use device protected storage since in some cases this will need to be accessed while device
- // is locked
- context = context.createDeviceProtectedStorageContext();
- return context.getSharedPreferences(PREFS_FILENAME, Context.MODE_PRIVATE);
- }
-
- @RequiresApi(26)
- public Intent getSettingsIntentForChannel(
- @NonNull Context context, @Channel String channelName, PhoneAccountHandle accountHandle) {
- checkNullity(channelName, accountHandle);
- Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
- intent.putExtra(
- Settings.EXTRA_CHANNEL_ID, getChannel(context, channelName, accountHandle).getId());
- intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
- return intent;
- }
+ NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
+ Set<String> desiredChannelIds = getAllDesiredChannelIds(context);
+ Set<String> existingChannelIds = getAllExistingChannelIds(context);
- @TargetApi(26)
- @SuppressWarnings("AndroidApiChecker")
- public void initChannels(@NonNull Context context) {
- if (!BuildCompat.isAtLeastO()) {
+ if (desiredChannelIds.equals(existingChannelIds)) {
return;
}
- LogUtil.enterBlock("NotificationChannelManager.initChannels");
- List<PhoneAccountHandle> phoneAccounts = TelecomUtil.getCallCapablePhoneAccounts(context);
-
- // Remove notification channels for PhoneAccounts that don't exist anymore
- NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
- List<NotificationChannelGroup> notificationChannelGroups =
- notificationManager.getNotificationChannelGroups();
- notificationChannelGroups
- .stream()
- .filter(group -> !idExists(group.getId(), phoneAccounts))
- .forEach(group -> deleteGroup(notificationManager, group));
-
- for (PhoneAccountHandle phoneAccountHandle : phoneAccounts) {
- for (@Channel String channel : prepopulatedAccountChannels) {
- getChannel(context, channel, phoneAccountHandle);
+ LogUtil.i(
+ "NotificationChannelManager.initChannels",
+ "doing an expensive initialization of all notification channels");
+ LogUtil.i(
+ "NotificationChannelManager.initChannels", "desired channel IDs: " + desiredChannelIds);
+ LogUtil.i(
+ "NotificationChannelManager.initChannels", "existing channel IDs: " + existingChannelIds);
+
+ // Delete any old channels that we don't use any more. This is safe because if we're recreate
+ // this later then any user settings will be restored. An example is SIM specific voicemail
+ // channel that gets deleted when the user removes the SIM and is then restored when the user
+ // re-inserts the SIM.
+ for (String existingChannelId : existingChannelIds) {
+ if (!desiredChannelIds.contains(existingChannelId)) {
+ notificationManager.deleteNotificationChannel(existingChannelId);
}
}
- for (@Channel String channel : prepopulatedGlobalChannels) {
- getChannel(context, channel, null);
- }
- getSharedPreferences(context).edit().putBoolean(PREF_NEED_FIRST_INIT, false).apply();
- }
-
- @TargetApi(26)
- private void deleteGroup(
- @NonNull NotificationManager notificationManager, @NonNull NotificationChannelGroup group) {
- for (NotificationChannel channel : group.getChannels()) {
- notificationManager.deleteNotificationChannel(channel.getId());
- }
- notificationManager.deleteNotificationChannelGroup(group.getId());
- }
-
- private boolean idExists(String id, List<PhoneAccountHandle> phoneAccountHandles) {
- for (PhoneAccountHandle handle : phoneAccountHandles) {
- if (Objects.equals(handle.getId(), id)) {
- return true;
- }
- }
- return false;
+ // Just recreate all desired channels. We won't do this often so it's ok to do this now.
+ createIncomingCallChannel(context);
+ createOngoingCallChannel(context);
+ createMissedCallChannel(context);
+ createDefaultChannel(context);
+ VoicemailChannelUtils.createAllChannels(context);
}
@NonNull
- @RequiresApi(26)
- private NotificationChannel getChannel(
- @NonNull Context context,
- @Channel String channelName,
- @Nullable PhoneAccountHandle phoneAccount) {
- String channelId = channelNameToId(channelName, phoneAccount);
- NotificationChannel channel = getNotificationManager(context).getNotificationChannel(channelId);
- if (channel == null) {
- channel = createChannel(context, channelName, phoneAccount);
- }
- return channel;
+ public static String getVoicemailChannelId(
+ @NonNull Context context, @Nullable PhoneAccountHandle handle) {
+ Assert.checkArgument(BuildCompat.isAtLeastO());
+ Assert.isNotNull(context);
+ return VoicemailChannelUtils.getChannelId(context, handle);
}
- private static String channelNameToId(
- @Channel String name, @Nullable PhoneAccountHandle phoneAccountHandle) {
- if (phoneAccountHandle == null) {
- return name;
- } else {
- return name + ":" + phoneAccountHandle.getId();
- }
- }
-
- @RequiresApi(26)
- private NotificationChannel createChannel(
- Context context,
- @Channel String channelName,
- @Nullable PhoneAccountHandle phoneAccountHandle) {
- String channelId = channelNameToId(channelName, phoneAccountHandle);
-
- if (phoneAccountHandle != null) {
- PhoneAccount account = getTelecomManager(context).getPhoneAccount(phoneAccountHandle);
- NotificationChannelGroup group =
- new NotificationChannelGroup(
- phoneAccountHandle.getId(),
- (account == null) ? phoneAccountHandle.getId() : account.getLabel().toString());
- getNotificationManager(context)
- .createNotificationChannelGroup(group); // No-op if already exists
- } else if (!channelAllowsNullPhoneAccountHandle(channelName)) {
- LogUtil.w(
- "NotificationChannelManager.createChannel",
- "Null PhoneAccountHandle with channel " + channelName);
- }
-
- Uri silentRingtone = Uri.EMPTY;
-
- CharSequence name;
- int importance;
- boolean canShowBadge;
- boolean lights;
- boolean vibration;
- Uri sound;
- switch (channelName) {
- case Channel.INCOMING_CALL:
- name = context.getText(R.string.notification_channel_incoming_call);
- importance = NotificationManager.IMPORTANCE_MAX;
- canShowBadge = false;
- lights = true;
- vibration = false;
- sound = silentRingtone;
- break;
- case Channel.MISSED_CALL:
- name = context.getText(R.string.notification_channel_missed_call);
- importance = NotificationManager.IMPORTANCE_DEFAULT;
- canShowBadge = true;
- lights = true;
- vibration = true;
- sound = silentRingtone;
- break;
- case Channel.ONGOING_CALL:
- name = context.getText(R.string.notification_channel_ongoing_call);
- importance = NotificationManager.IMPORTANCE_DEFAULT;
- canShowBadge = false;
- lights = false;
- vibration = false;
- sound = silentRingtone;
- deleteOldOngoingCallChannelIfNeeded(context, phoneAccountHandle);
- break;
- case Channel.VOICEMAIL:
- name = context.getText(R.string.notification_channel_voicemail);
- importance = NotificationManager.IMPORTANCE_DEFAULT;
- canShowBadge = true;
- lights = true;
- vibration =
- TelephonyManagerCompat.isVoicemailVibrationEnabled(
- getTelephonyManager(context), phoneAccountHandle);
- sound =
- TelephonyManagerCompat.getVoicemailRingtoneUri(
- getTelephonyManager(context), phoneAccountHandle);
- break;
- case Channel.EXTERNAL_CALL:
- name = context.getText(R.string.notification_channel_external_call);
- importance = NotificationManager.IMPORTANCE_HIGH;
- canShowBadge = false;
- lights = true;
- vibration = true;
- sound = null;
- break;
- case Channel.DEFAULT:
- name = context.getText(R.string.notification_channel_misc);
- importance = NotificationManager.IMPORTANCE_DEFAULT;
- canShowBadge = false;
- lights = true;
- vibration = true;
- sound = null;
- break;
- default:
- throw new IllegalArgumentException("Unknown channel: " + channelName);
- }
-
- NotificationChannel channel = new NotificationChannel(channelId, name, importance);
- channel.setShowBadge(canShowBadge);
- if (sound != null) {
- // silentRingtone acts as a sentinel value to indicate that setSound should still be called,
- // but with a null value to indicate no sound.
- channel.setSound(
- sound.equals(silentRingtone) ? null : sound,
- new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build());
- }
- channel.enableLights(lights);
- channel.enableVibration(vibration);
- getNotificationManager(context).createNotificationChannel(channel);
- return channel;
- }
-
- @RequiresApi(26)
- private void deleteOldOngoingCallChannelIfNeeded(
- @NonNull Context context, PhoneAccountHandle phoneAccountHandle) {
- String channelId = channelNameToId(Channel.ONGOING_CALL_OLD, phoneAccountHandle);
- NotificationManager notificationManager = getNotificationManager(context);
- NotificationChannel channel = notificationManager.getNotificationChannel(channelId);
- if (channel != null) {
- LogUtil.i(
- "NotificationManager.deleteOldOngoingCallChannelIfNeeded",
- "Old ongoing channel found. Deleting to create new channel");
- notificationManager.deleteNotificationChannel(channel.getId());
- }
- }
-
- private static NotificationManager getNotificationManager(@NonNull Context context) {
- return context.getSystemService(NotificationManager.class);
- }
-
- private static TelephonyManager getTelephonyManager(@NonNull Context context) {
- return context.getSystemService(TelephonyManager.class);
+ private static Set<String> getAllExistingChannelIds(@NonNull Context context) {
+ Set<String> result = new ArraySet<>();
+ NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
+ for (NotificationChannel channel : notificationManager.getNotificationChannels()) {
+ result.add(channel.getId());
+ }
+ return result;
+ }
+
+ private static Set<String> getAllDesiredChannelIds(@NonNull Context context) {
+ Set<String> result = new ArraySet<>();
+ result.add(NotificationChannelId.INCOMING_CALL);
+ result.add(NotificationChannelId.ONGOING_CALL);
+ result.add(NotificationChannelId.MISSED_CALL);
+ result.add(NotificationChannelId.DEFAULT);
+ result.addAll(VoicemailChannelUtils.getAllChannelIds(context));
+ return result;
+ }
+
+ private static void createIncomingCallChannel(@NonNull Context context) {
+ NotificationChannel channel =
+ new NotificationChannel(
+ NotificationChannelId.INCOMING_CALL,
+ context.getText(R.string.notification_channel_incoming_call),
+ NotificationManager.IMPORTANCE_MAX);
+ channel.setShowBadge(false);
+ channel.enableLights(true);
+ channel.enableVibration(false);
+ channel.setSound(
+ null, new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build());
+ context.getSystemService(NotificationManager.class).createNotificationChannel(channel);
+ }
+
+ private static void createOngoingCallChannel(@NonNull Context context) {
+ NotificationChannel channel =
+ new NotificationChannel(
+ NotificationChannelId.ONGOING_CALL,
+ context.getText(R.string.notification_channel_ongoing_call),
+ NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setShowBadge(false);
+ channel.enableLights(false);
+ channel.enableVibration(false);
+ channel.setSound(
+ null, new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build());
+ context.getSystemService(NotificationManager.class).createNotificationChannel(channel);
+ }
+
+ private static void createMissedCallChannel(@NonNull Context context) {
+ NotificationChannel channel =
+ new NotificationChannel(
+ NotificationChannelId.MISSED_CALL,
+ context.getText(R.string.notification_channel_missed_call),
+ NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setShowBadge(true);
+ channel.enableLights(true);
+ channel.enableVibration(true);
+ channel.setSound(
+ null, new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build());
+ context.getSystemService(NotificationManager.class).createNotificationChannel(channel);
+ }
+
+ private static void createDefaultChannel(@NonNull Context context) {
+ NotificationChannel channel =
+ new NotificationChannel(
+ NotificationChannelId.DEFAULT,
+ context.getText(R.string.notification_channel_misc),
+ NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setShowBadge(false);
+ channel.enableLights(true);
+ channel.enableVibration(true);
+ context.getSystemService(NotificationManager.class).createNotificationChannel(channel);
}
- private static TelecomManager getTelecomManager(@NonNull Context context) {
- return context.getSystemService(TelecomManager.class);
- }
+ private NotificationChannelManager() {}
}