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 17 package com.android.dialer.calllog; 18 19 import com.google.common.collect.Maps; 20 21 import android.app.Notification; 22 import android.app.NotificationManager; 23 import android.app.PendingIntent; 24 import android.content.ComponentName; 25 import android.content.ContentResolver; 26 import android.content.ContentUris; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.res.Resources; 30 import android.net.Uri; 31 import android.support.annotation.Nullable; 32 import android.support.v4.util.Pair; 33 import android.telecom.PhoneAccount; 34 import android.telecom.PhoneAccountHandle; 35 import android.telephony.TelephonyManager; 36 import android.text.TextUtils; 37 import android.util.Log; 38 39 import com.android.contacts.common.ContactsUtils; 40 import com.android.contacts.common.compat.TelephonyManagerCompat; 41 import com.android.contacts.common.util.ContactDisplayUtils; 42 import com.android.dialer.DialtactsActivity; 43 import com.android.dialer.R; 44 import com.android.dialer.calllog.CallLogNotificationsHelper.NewCall; 45 import com.android.dialer.filterednumber.FilteredNumbersUtil; 46 import com.android.dialer.list.ListsFragment; 47 import com.android.dialer.util.TelecomUtil; 48 49 import java.util.Iterator; 50 import java.util.List; 51 import java.util.Map; 52 53 /** 54 * Shows a voicemail notification in the status bar. 55 */ 56 public class DefaultVoicemailNotifier { 57 public static final String TAG = "VoicemailNotifier"; 58 59 /** The tag used to identify notifications from this class. */ 60 private static final String NOTIFICATION_TAG = "DefaultVoicemailNotifier"; 61 /** The identifier of the notification of new voicemails. */ 62 private static final int NOTIFICATION_ID = 1; 63 64 /** The singleton instance of {@link DefaultVoicemailNotifier}. */ 65 private static DefaultVoicemailNotifier sInstance; 66 67 private final Context mContext; 68 69 /** Returns the singleton instance of the {@link DefaultVoicemailNotifier}. */ getInstance(Context context)70 public static DefaultVoicemailNotifier getInstance(Context context) { 71 if (sInstance == null) { 72 ContentResolver contentResolver = context.getContentResolver(); 73 sInstance = new DefaultVoicemailNotifier(context); 74 } 75 return sInstance; 76 } 77 DefaultVoicemailNotifier(Context context)78 private DefaultVoicemailNotifier(Context context) { 79 mContext = context; 80 } 81 82 /** 83 * Updates the notification and notifies of the call with the given URI. 84 * 85 * Clears the notification if there are no new voicemails, and notifies if the given URI 86 * corresponds to a new voicemail. 87 * 88 * It is not safe to call this method from the main thread. 89 */ updateNotification(Uri newCallUri)90 public void updateNotification(Uri newCallUri) { 91 // Lookup the list of new voicemails to include in the notification. 92 // TODO: Move this into a service, to avoid holding the receiver up. 93 final List<NewCall> newCalls = 94 CallLogNotificationsHelper.getInstance(mContext).getNewVoicemails(); 95 96 if (newCalls == null) { 97 // Query failed, just return. 98 return; 99 } 100 101 if (newCalls.isEmpty()) { 102 // No voicemails to notify about: clear the notification. 103 getNotificationManager().cancel(NOTIFICATION_TAG, NOTIFICATION_ID); 104 return; 105 } 106 107 Resources resources = mContext.getResources(); 108 109 // This represents a list of names to include in the notification. 110 String callers = null; 111 112 // Maps each number into a name: if a number is in the map, it has already left a more 113 // recent voicemail. 114 final Map<String, String> names = Maps.newHashMap(); 115 116 // Determine the call corresponding to the new voicemail we have to notify about. 117 NewCall callToNotify = null; 118 119 // Iterate over the new voicemails to determine all the information above. 120 Iterator<NewCall> itr = newCalls.iterator(); 121 while (itr.hasNext()) { 122 NewCall newCall = itr.next(); 123 124 // Skip notifying for numbers which are blocked. 125 if (FilteredNumbersUtil.shouldBlockVoicemail( 126 mContext, newCall.number, newCall.countryIso, newCall.dateMs)) { 127 itr.remove(); 128 129 // Delete the voicemail. 130 mContext.getContentResolver().delete(newCall.voicemailUri, null, null); 131 continue; 132 } 133 134 // Check if we already know the name associated with this number. 135 String name = names.get(newCall.number); 136 if (name == null) { 137 name = CallLogNotificationsHelper.getInstance(mContext).getName(newCall.number, 138 newCall.numberPresentation, newCall.countryIso); 139 names.put(newCall.number, name); 140 // This is a new caller. Add it to the back of the list of callers. 141 if (TextUtils.isEmpty(callers)) { 142 callers = name; 143 } else { 144 callers = resources.getString( 145 R.string.notification_voicemail_callers_list, callers, name); 146 } 147 } 148 // Check if this is the new call we need to notify about. 149 if (newCallUri != null && newCall.voicemailUri != null && 150 ContentUris.parseId(newCallUri) == ContentUris.parseId(newCall.voicemailUri)) { 151 callToNotify = newCall; 152 } 153 } 154 155 // All the potential new voicemails have been removed, e.g. if they were spam. 156 if (newCalls.isEmpty()) { 157 return; 158 } 159 160 // If there is only one voicemail, set its transcription as the "long text". 161 String transcription = null; 162 if (newCalls.size() == 1) { 163 transcription = newCalls.get(0).transcription; 164 } 165 166 if (newCallUri != null && callToNotify == null) { 167 Log.e(TAG, "The new call could not be found in the call log: " + newCallUri); 168 } 169 170 // Determine the title of the notification and the icon for it. 171 final String title = resources.getQuantityString( 172 R.plurals.notification_voicemail_title, newCalls.size(), newCalls.size()); 173 // TODO: Use the photo of contact if all calls are from the same person. 174 final int icon = android.R.drawable.stat_notify_voicemail; 175 176 Pair<Uri, Integer> info = getNotificationInfo(callToNotify); 177 178 Notification.Builder notificationBuilder = new Notification.Builder(mContext) 179 .setSmallIcon(icon) 180 .setContentTitle(title) 181 .setContentText(callers) 182 .setStyle(new Notification.BigTextStyle().bigText(transcription)) 183 .setColor(resources.getColor(R.color.dialer_theme_color)) 184 .setSound(info.first) 185 .setDefaults(info.second) 186 .setDeleteIntent(createMarkNewVoicemailsAsOldIntent()) 187 .setAutoCancel(true); 188 189 // Determine the intent to fire when the notification is clicked on. 190 final Intent contentIntent; 191 // Open the call log. 192 contentIntent = new Intent(mContext, DialtactsActivity.class); 193 contentIntent.putExtra(DialtactsActivity.EXTRA_SHOW_TAB, ListsFragment.TAB_INDEX_VOICEMAIL); 194 notificationBuilder.setContentIntent(PendingIntent.getActivity( 195 mContext, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT)); 196 197 // The text to show in the ticker, describing the new event. 198 if (callToNotify != null) { 199 CharSequence msg = ContactDisplayUtils.getTtsSpannedPhoneNumber( 200 resources, 201 R.string.notification_new_voicemail_ticker, 202 names.get(callToNotify.number)); 203 notificationBuilder.setTicker(msg); 204 } 205 Log.i(TAG, "Creating voicemail notification"); 206 getNotificationManager().notify(NOTIFICATION_TAG, NOTIFICATION_ID, 207 notificationBuilder.build()); 208 } 209 210 /** 211 * Determines which ringtone Uri and Notification defaults to use when updating the notification 212 * for the given call. 213 */ getNotificationInfo(@ullable NewCall callToNotify)214 private Pair<Uri, Integer> getNotificationInfo(@Nullable NewCall callToNotify) { 215 Log.v(TAG, "getNotificationInfo"); 216 if (callToNotify == null) { 217 Log.i(TAG, "callToNotify == null"); 218 return new Pair<>(null, 0); 219 } 220 PhoneAccountHandle accountHandle = null; 221 if (callToNotify.accountComponentName == null || callToNotify.accountId == null) { 222 Log.v(TAG, "accountComponentName == null || callToNotify.accountId == null"); 223 accountHandle = TelecomUtil 224 .getDefaultOutgoingPhoneAccount(mContext, PhoneAccount.SCHEME_TEL); 225 if (accountHandle == null) { 226 Log.i(TAG, "No default phone account found, using default notification ringtone"); 227 return new Pair<>(null, Notification.DEFAULT_ALL); 228 } 229 230 } else { 231 accountHandle = new PhoneAccountHandle( 232 ComponentName.unflattenFromString(callToNotify.accountComponentName), 233 callToNotify.accountId); 234 } 235 if (accountHandle.getComponentName() != null) { 236 Log.v(TAG, "PhoneAccountHandle.ComponentInfo:" + accountHandle.getComponentName()); 237 } else { 238 Log.i(TAG, "PhoneAccountHandle.ComponentInfo: null"); 239 } 240 return new Pair<>( 241 TelephonyManagerCompat.getVoicemailRingtoneUri( 242 getTelephonyManager(), accountHandle), 243 getNotificationDefaults(accountHandle)); 244 } 245 getNotificationDefaults(PhoneAccountHandle accountHandle)246 private int getNotificationDefaults(PhoneAccountHandle accountHandle) { 247 if (ContactsUtils.FLAG_N_FEATURE) { 248 return TelephonyManagerCompat.isVoicemailVibrationEnabled(getTelephonyManager(), 249 accountHandle) ? Notification.DEFAULT_VIBRATE : 0; 250 } 251 return Notification.DEFAULT_ALL; 252 } 253 254 /** Creates a pending intent that marks all new voicemails as old. */ createMarkNewVoicemailsAsOldIntent()255 private PendingIntent createMarkNewVoicemailsAsOldIntent() { 256 Intent intent = new Intent(mContext, CallLogNotificationsService.class); 257 intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_VOICEMAILS_AS_OLD); 258 return PendingIntent.getService(mContext, 0, intent, 0); 259 } 260 getNotificationManager()261 private NotificationManager getNotificationManager() { 262 return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); 263 } 264 getTelephonyManager()265 private TelephonyManager getTelephonyManager() { 266 return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); 267 } 268 269 } 270