1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.dialer.notification; 18 19 import android.Manifest.permission; 20 import android.annotation.TargetApi; 21 import android.app.NotificationChannel; 22 import android.app.NotificationManager; 23 import android.content.Context; 24 import android.media.AudioAttributes; 25 import android.os.Build.VERSION_CODES; 26 import android.provider.Settings; 27 import android.support.annotation.NonNull; 28 import android.support.annotation.Nullable; 29 import android.support.annotation.RequiresPermission; 30 import android.support.annotation.VisibleForTesting; 31 import android.support.v4.os.BuildCompat; 32 import android.telecom.PhoneAccount; 33 import android.telecom.PhoneAccountHandle; 34 import android.telecom.TelecomManager; 35 import android.telephony.TelephonyManager; 36 import android.text.TextUtils; 37 import android.util.ArraySet; 38 import com.android.dialer.common.Assert; 39 import com.android.dialer.common.LogUtil; 40 import com.android.dialer.util.PermissionsUtil; 41 import java.util.ArrayList; 42 import java.util.List; 43 import java.util.Set; 44 45 /** Utilities for working with voicemail channels. */ 46 @TargetApi(VERSION_CODES.O) 47 /* package */ final class VoicemailChannelUtils { 48 @VisibleForTesting static final String GLOBAL_VOICEMAIL_CHANNEL_ID = "phone_voicemail"; 49 private static final String PER_ACCOUNT_VOICEMAIL_CHANNEL_ID_PREFIX = "phone_voicemail_account_"; 50 51 @SuppressWarnings("MissingPermission") // isSingleSimDevice() returns true if no permission getAllChannelIds(@onNull Context context)52 static Set<String> getAllChannelIds(@NonNull Context context) { 53 Assert.checkArgument(BuildCompat.isAtLeastO()); 54 Assert.isNotNull(context); 55 56 Set<String> result = new ArraySet<>(); 57 if (isSingleSimDevice(context)) { 58 result.add(GLOBAL_VOICEMAIL_CHANNEL_ID); 59 } else { 60 for (PhoneAccountHandle handle : getAllEligableAccounts(context)) { 61 result.add(getChannelIdForAccount(handle)); 62 } 63 } 64 return result; 65 } 66 67 @SuppressWarnings("MissingPermission") // isSingleSimDevice() returns true if no permission createAllChannels(@onNull Context context)68 static void createAllChannels(@NonNull Context context) { 69 Assert.checkArgument(BuildCompat.isAtLeastO()); 70 Assert.isNotNull(context); 71 72 if (isSingleSimDevice(context)) { 73 createGlobalVoicemailChannel(context); 74 } else { 75 for (PhoneAccountHandle handle : getAllEligableAccounts(context)) { 76 createVoicemailChannelForAccount(context, handle); 77 } 78 } 79 } 80 81 @NonNull getChannelId(@onNull Context context, @Nullable PhoneAccountHandle handle)82 static String getChannelId(@NonNull Context context, @Nullable PhoneAccountHandle handle) { 83 Assert.checkArgument(BuildCompat.isAtLeastO()); 84 Assert.isNotNull(context); 85 86 // Most devices we deal with have a single SIM slot. No need to distinguish between phone 87 // accounts. 88 if (isSingleSimDevice(context)) { 89 return GLOBAL_VOICEMAIL_CHANNEL_ID; 90 } 91 92 // We can get a null phone account at random points (modem reboot, etc...). Gracefully degrade 93 // by using the default channel. 94 if (handle == null) { 95 LogUtil.i( 96 "VoicemailChannelUtils.getChannelId", 97 "no phone account on a multi-SIM device, using default channel"); 98 return NotificationChannelId.DEFAULT; 99 } 100 101 // Voicemail notifications should always be associated with a SIM based phone account. 102 if (!isChannelAllowedForAccount(context, handle)) { 103 LogUtil.i( 104 "VoicemailChannelUtils.getChannelId", 105 "phone account is not for a SIM, using default channel"); 106 return NotificationChannelId.DEFAULT; 107 } 108 109 // Now we're in the multi-SIM case. 110 String channelId = getChannelIdForAccount(handle); 111 if (!doesChannelExist(context, channelId)) { 112 LogUtil.i( 113 "VoicemailChannelUtils.getChannelId", 114 "voicemail channel not found for phone account (possible SIM swap?), creating a new one"); 115 createVoicemailChannelForAccount(context, handle); 116 } 117 return channelId; 118 } 119 doesChannelExist(@onNull Context context, @NonNull String channelId)120 private static boolean doesChannelExist(@NonNull Context context, @NonNull String channelId) { 121 return context.getSystemService(NotificationManager.class).getNotificationChannel(channelId) 122 != null; 123 } 124 getChannelIdForAccount(@onNull PhoneAccountHandle handle)125 private static String getChannelIdForAccount(@NonNull PhoneAccountHandle handle) { 126 Assert.isNotNull(handle); 127 return PER_ACCOUNT_VOICEMAIL_CHANNEL_ID_PREFIX + ":" + handle.getId(); 128 } 129 130 /** 131 * Creates a voicemail channel but doesn't associate it with a SIM. For devices with only one SIM 132 * slot this is ideal because there won't be duplication in the settings UI. 133 */ createGlobalVoicemailChannel(@onNull Context context)134 private static void createGlobalVoicemailChannel(@NonNull Context context) { 135 NotificationChannel channel = newChannel(context, GLOBAL_VOICEMAIL_CHANNEL_ID, null); 136 migrateGlobalVoicemailSoundSettings(context, channel); 137 context.getSystemService(NotificationManager.class).createNotificationChannel(channel); 138 } 139 140 @SuppressWarnings("MissingPermission") // checked with PermissionsUtil migrateGlobalVoicemailSoundSettings( Context context, NotificationChannel channel)141 private static void migrateGlobalVoicemailSoundSettings( 142 Context context, NotificationChannel channel) { 143 if (!PermissionsUtil.hasReadPhoneStatePermissions(context)) { 144 LogUtil.i( 145 "VoicemailChannelUtils.migrateGlobalVoicemailSoundSettings", 146 "missing phone permission, not migrating sound settings"); 147 return; 148 } 149 TelecomManager telecomManager = context.getSystemService(TelecomManager.class); 150 PhoneAccountHandle handle = 151 telecomManager.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL); 152 if (handle == null) { 153 LogUtil.i( 154 "VoicemailChannelUtils.migrateGlobalVoicemailSoundSettings", 155 "phone account is null, not migrating sound settings"); 156 return; 157 } 158 if (!isChannelAllowedForAccount(context, handle)) { 159 LogUtil.i( 160 "VoicemailChannelUtils.migrateGlobalVoicemailSoundSettings", 161 "phone account is not eligable, not migrating sound settings"); 162 return; 163 } 164 migrateVoicemailSoundSettings(context, channel, handle); 165 } 166 167 @RequiresPermission(permission.READ_PHONE_STATE) getAllEligableAccounts(@onNull Context context)168 private static List<PhoneAccountHandle> getAllEligableAccounts(@NonNull Context context) { 169 List<PhoneAccountHandle> handles = new ArrayList<>(); 170 TelecomManager telecomManager = context.getSystemService(TelecomManager.class); 171 for (PhoneAccountHandle handle : telecomManager.getCallCapablePhoneAccounts()) { 172 if (isChannelAllowedForAccount(context, handle)) { 173 handles.add(handle); 174 } 175 } 176 return handles; 177 } 178 createVoicemailChannelForAccount( @onNull Context context, @NonNull PhoneAccountHandle handle)179 private static void createVoicemailChannelForAccount( 180 @NonNull Context context, @NonNull PhoneAccountHandle handle) { 181 PhoneAccount phoneAccount = 182 context.getSystemService(TelecomManager.class).getPhoneAccount(handle); 183 if (phoneAccount == null) { 184 return; 185 } 186 NotificationChannel channel = 187 newChannel(context, getChannelIdForAccount(handle), phoneAccount.getLabel()); 188 migrateVoicemailSoundSettings(context, channel, handle); 189 context.getSystemService(NotificationManager.class).createNotificationChannel(channel); 190 } 191 migrateVoicemailSoundSettings( @onNull Context context, @NonNull NotificationChannel channel, @NonNull PhoneAccountHandle handle)192 private static void migrateVoicemailSoundSettings( 193 @NonNull Context context, 194 @NonNull NotificationChannel channel, 195 @NonNull PhoneAccountHandle handle) { 196 TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class); 197 channel.enableVibration(telephonyManager.isVoicemailVibrationEnabled(handle)); 198 channel.setSound( 199 telephonyManager.getVoicemailRingtoneUri(handle), 200 new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build()); 201 } 202 isChannelAllowedForAccount( @onNull Context context, @NonNull PhoneAccountHandle handle)203 private static boolean isChannelAllowedForAccount( 204 @NonNull Context context, @NonNull PhoneAccountHandle handle) { 205 PhoneAccount phoneAccount = 206 context.getSystemService(TelecomManager.class).getPhoneAccount(handle); 207 if (phoneAccount == null) { 208 return false; 209 } 210 if (!phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { 211 return false; 212 } 213 return true; 214 } 215 newChannel( @onNull Context context, @NonNull String channelId, @Nullable CharSequence nameSuffix)216 private static NotificationChannel newChannel( 217 @NonNull Context context, @NonNull String channelId, @Nullable CharSequence nameSuffix) { 218 CharSequence name = context.getText(R.string.notification_channel_voicemail); 219 // TODO(sail): Use a string resource template after v10. 220 if (!TextUtils.isEmpty(nameSuffix)) { 221 name = TextUtils.concat(name, ": ", nameSuffix); 222 } 223 224 NotificationChannel channel = 225 new NotificationChannel(channelId, name, NotificationManager.IMPORTANCE_DEFAULT); 226 channel.setShowBadge(true); 227 channel.enableLights(true); 228 channel.enableVibration(true); 229 channel.setSound( 230 Settings.System.DEFAULT_NOTIFICATION_URI, 231 new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build()); 232 return channel; 233 } 234 isSingleSimDevice(@onNull Context context)235 private static boolean isSingleSimDevice(@NonNull Context context) { 236 if (!PermissionsUtil.hasReadPhoneStatePermissions(context)) { 237 return true; 238 } 239 return context.getSystemService(TelephonyManager.class).getPhoneCount() <= 1; 240 } 241 VoicemailChannelUtils()242 private VoicemailChannelUtils() {} 243 } 244