DefaultVoicemailNotifier.java revision d8046e520a866b9948ee9ba47cf642b441ca8e23
1/* 2 * Copyright (C) 2011 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 17package com.android.dialer.app.calllog; 18 19import android.app.Notification; 20import android.app.NotificationManager; 21import android.app.PendingIntent; 22import android.content.Context; 23import android.content.Intent; 24import android.content.res.Resources; 25import android.graphics.Bitmap; 26import android.net.Uri; 27import android.os.Build.VERSION; 28import android.os.Build.VERSION_CODES; 29import android.support.annotation.NonNull; 30import android.support.annotation.Nullable; 31import android.support.annotation.VisibleForTesting; 32import android.support.v4.util.Pair; 33import android.telecom.PhoneAccountHandle; 34import android.telephony.TelephonyManager; 35import android.text.TextUtils; 36import android.util.ArrayMap; 37import com.android.contacts.common.compat.TelephonyManagerCompat; 38import com.android.contacts.common.util.ContactDisplayUtils; 39import com.android.dialer.app.DialtactsActivity; 40import com.android.dialer.app.R; 41import com.android.dialer.app.calllog.CallLogNotificationsQueryHelper.NewCall; 42import com.android.dialer.app.contactinfo.ContactPhotoLoader; 43import com.android.dialer.app.list.DialtactsPagerAdapter; 44import com.android.dialer.blocking.FilteredNumbersUtil; 45import com.android.dialer.common.LogUtil; 46import com.android.dialer.logging.Logger; 47import com.android.dialer.logging.nano.DialerImpression; 48import com.android.dialer.notification.NotificationChannelManager; 49import com.android.dialer.notification.NotificationChannelManager.Channel; 50import com.android.dialer.phonenumbercache.ContactInfo; 51import java.util.Iterator; 52import java.util.List; 53import java.util.Map; 54 55/** Shows a voicemail notification in the status bar. */ 56public class DefaultVoicemailNotifier { 57 58 public static final String TAG = "VoicemailNotifier"; 59 60 /** The tag used to identify notifications from this class. */ 61 static final String NOTIFICATION_TAG = "DefaultVoicemailNotifier"; 62 /** The identifier of the notification of new voicemails. */ 63 private static final int NOTIFICATION_ID = R.id.notification_voicemail; 64 65 private final Context context; 66 private final CallLogNotificationsQueryHelper queryHelper; 67 68 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 69 DefaultVoicemailNotifier(Context context, CallLogNotificationsQueryHelper queryHelper) { 70 this.context = context; 71 this.queryHelper = queryHelper; 72 } 73 74 /** Returns an instance of {@link DefaultVoicemailNotifier}. */ 75 public static DefaultVoicemailNotifier getInstance(Context context) { 76 return new DefaultVoicemailNotifier( 77 context, CallLogNotificationsQueryHelper.getInstance(context)); 78 } 79 80 /** 81 * Updates the notification and notifies of the call with the given URI. 82 * 83 * <p>Clears the notification if there are no new voicemails, and notifies if the given URI 84 * corresponds to a new voicemail. 85 * 86 * <p>It is not safe to call this method from the main thread. 87 */ 88 public void updateNotification() { 89 // Lookup the list of new voicemails to include in the notification. 90 final List<NewCall> newCalls = queryHelper.getNewVoicemails(); 91 92 if (newCalls == null) { 93 // Query failed, just return. 94 return; 95 } 96 97 Resources resources = context.getResources(); 98 99 // This represents a list of names to include in the notification. 100 String callers = null; 101 102 // Maps each number into a name: if a number is in the map, it has already left a more 103 // recent voicemail. 104 final Map<String, ContactInfo> contactInfos = new ArrayMap<>(); 105 106 // Iterate over the new voicemails to determine all the information above. 107 Iterator<NewCall> itr = newCalls.iterator(); 108 while (itr.hasNext()) { 109 NewCall newCall = itr.next(); 110 111 // Skip notifying for numbers which are blocked. 112 if (FilteredNumbersUtil.shouldBlockVoicemail( 113 context, newCall.number, newCall.countryIso, newCall.dateMs)) { 114 itr.remove(); 115 116 if (newCall.voicemailUri != null) { 117 // Delete the voicemail. 118 context.getContentResolver().delete(newCall.voicemailUri, null, null); 119 } 120 continue; 121 } 122 123 // Check if we already know the name associated with this number. 124 ContactInfo contactInfo = contactInfos.get(newCall.number); 125 if (contactInfo == null) { 126 contactInfo = 127 queryHelper.getContactInfo( 128 newCall.number, newCall.numberPresentation, newCall.countryIso); 129 contactInfos.put(newCall.number, contactInfo); 130 // This is a new caller. Add it to the back of the list of callers. 131 if (TextUtils.isEmpty(callers)) { 132 callers = contactInfo.name; 133 } else { 134 callers = 135 resources.getString( 136 R.string.notification_voicemail_callers_list, callers, contactInfo.name); 137 } 138 } 139 } 140 141 if (newCalls.isEmpty()) { 142 // No voicemails to notify about 143 return; 144 } 145 146 Notification.Builder groupSummary = 147 createNotificationBuilder() 148 .setContentTitle( 149 resources.getQuantityString( 150 R.plurals.notification_voicemail_title, newCalls.size(), newCalls.size())) 151 .setContentText(callers) 152 .setDeleteIntent(createMarkNewVoicemailsAsOldIntent(null)) 153 .setGroupSummary(true) 154 .setContentIntent(newVoicemailIntent(null)); 155 156 NotificationChannelManager.applyChannel( 157 groupSummary, 158 context, 159 Channel.VOICEMAIL, 160 PhoneAccountHandles.getAccount(context, newCalls.get(0))); 161 162 LogUtil.i(TAG, "Creating voicemail notification"); 163 getNotificationManager().notify(NOTIFICATION_TAG, NOTIFICATION_ID, groupSummary.build()); 164 165 for (NewCall voicemail : newCalls) { 166 getNotificationManager() 167 .notify( 168 voicemail.callsUri.toString(), 169 NOTIFICATION_ID, 170 createNotificationForVoicemail(voicemail, contactInfos)); 171 } 172 } 173 174 /** 175 * Determines which ringtone Uri and Notification defaults to use when updating the notification 176 * for the given call. 177 */ 178 private Pair<Uri, Integer> getNotificationInfo(@Nullable NewCall callToNotify) { 179 LogUtil.v(TAG, "getNotificationInfo"); 180 if (callToNotify == null) { 181 LogUtil.i(TAG, "callToNotify == null"); 182 return new Pair<>(null, 0); 183 } 184 PhoneAccountHandle accountHandle = PhoneAccountHandles.getAccount(context, callToNotify); 185 if (accountHandle == null) { 186 LogUtil.i(TAG, "No default phone account found, using default notification ringtone"); 187 return new Pair<>(null, Notification.DEFAULT_ALL); 188 } 189 return new Pair<>( 190 TelephonyManagerCompat.getVoicemailRingtoneUri(getTelephonyManager(), accountHandle), 191 getNotificationDefaults(accountHandle)); 192 } 193 194 private int getNotificationDefaults(PhoneAccountHandle accountHandle) { 195 if (VERSION.SDK_INT >= VERSION_CODES.N) { 196 return TelephonyManagerCompat.isVoicemailVibrationEnabled( 197 getTelephonyManager(), accountHandle) 198 ? Notification.DEFAULT_VIBRATE 199 : 0; 200 } 201 return Notification.DEFAULT_ALL; 202 } 203 204 /** Creates a pending intent that marks all new voicemails as old. */ 205 private PendingIntent createMarkNewVoicemailsAsOldIntent(@Nullable Uri voicemailUri) { 206 Intent intent = new Intent(context, CallLogNotificationsService.class); 207 intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_VOICEMAILS_AS_OLD); 208 intent.setData(voicemailUri); 209 return PendingIntent.getService(context, 0, intent, 0); 210 } 211 212 private NotificationManager getNotificationManager() { 213 return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 214 } 215 216 private TelephonyManager getTelephonyManager() { 217 return (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 218 } 219 220 private Notification createNotificationForVoicemail( 221 @NonNull NewCall voicemail, @NonNull Map<String, ContactInfo> contactInfos) { 222 Pair<Uri, Integer> notificationInfo = getNotificationInfo(voicemail); 223 ContactInfo contactInfo = contactInfos.get(voicemail.number); 224 225 Notification.Builder notificationBuilder = 226 createNotificationBuilder() 227 .setContentTitle( 228 context 229 .getResources() 230 .getQuantityString(R.plurals.notification_voicemail_title, 1, 1)) 231 .setContentText( 232 ContactDisplayUtils.getTtsSpannedPhoneNumber( 233 context.getResources(), 234 R.string.notification_new_voicemail_ticker, 235 contactInfo.name)) 236 .setWhen(voicemail.dateMs) 237 .setSound(notificationInfo.first) 238 .setDefaults(notificationInfo.second); 239 240 if (voicemail.voicemailUri != null) { 241 notificationBuilder.setDeleteIntent( 242 createMarkNewVoicemailsAsOldIntent(voicemail.voicemailUri)); 243 } 244 245 NotificationChannelManager.applyChannel( 246 notificationBuilder, 247 context, 248 Channel.VOICEMAIL, 249 PhoneAccountHandles.getAccount(context, voicemail)); 250 251 ContactPhotoLoader loader = new ContactPhotoLoader(context, contactInfo); 252 Bitmap photoIcon = loader.loadPhotoIcon(); 253 if (photoIcon != null) { 254 notificationBuilder.setLargeIcon(photoIcon); 255 } 256 if (!TextUtils.isEmpty(voicemail.transcription)) { 257 Logger.get(context) 258 .logImpression(DialerImpression.Type.VVM_NOTIFICATION_CREATED_WITH_TRANSCRIPTION); 259 notificationBuilder.setStyle( 260 new Notification.BigTextStyle().bigText(voicemail.transcription)); 261 } 262 notificationBuilder.setContentIntent(newVoicemailIntent(voicemail)); 263 Logger.get(context).logImpression(DialerImpression.Type.VVM_NOTIFICATION_CREATED); 264 return notificationBuilder.build(); 265 } 266 267 private Notification.Builder createNotificationBuilder() { 268 return new Notification.Builder(context) 269 .setSmallIcon(android.R.drawable.stat_notify_voicemail) 270 .setColor(context.getColor(R.color.dialer_theme_color)) 271 .setGroup(NOTIFICATION_TAG) 272 .setOnlyAlertOnce(true) 273 .setAutoCancel(true); 274 } 275 276 private PendingIntent newVoicemailIntent(@Nullable NewCall voicemail) { 277 Intent intent = 278 DialtactsActivity.getShowTabIntent(context, DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL); 279 // TODO (b/35486204): scroll to this voicemail 280 if (voicemail != null) { 281 intent.setData(voicemail.voicemailUri); 282 } 283 intent.putExtra(DialtactsActivity.EXTRA_CLEAR_NEW_VOICEMAILS, true); 284 return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); 285 } 286} 287