1 /* 2 * Copyright (C) 2016 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 package com.android.dialer.calllog; 17 18 import android.app.Notification; 19 import android.app.NotificationManager; 20 import android.app.PendingIntent; 21 import android.content.ContentValues; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.graphics.Bitmap; 25 import android.os.AsyncTask; 26 import android.provider.CallLog.Calls; 27 import android.text.TextUtils; 28 import android.util.Log; 29 30 import com.android.contacts.common.ContactsUtils; 31 import com.android.contacts.common.util.PhoneNumberHelper; 32 import com.android.dialer.DialtactsActivity; 33 import com.android.dialer.R; 34 import com.android.dialer.calllog.CallLogNotificationsHelper.NewCall; 35 import com.android.dialer.contactinfo.ContactPhotoLoader; 36 import com.android.dialer.compat.UserManagerCompat; 37 import com.android.dialer.list.ListsFragment; 38 import com.android.dialer.util.DialerUtils; 39 import com.android.dialer.util.IntentUtil; 40 import com.android.dialer.util.IntentUtil.CallIntentBuilder; 41 42 import java.util.List; 43 44 /** 45 * Creates a notification for calls that the user missed (neither answered nor rejected). 46 * 47 */ 48 public class MissedCallNotifier { 49 public static final String TAG = "MissedCallNotifier"; 50 51 /** The tag used to identify notifications from this class. */ 52 private static final String NOTIFICATION_TAG = "MissedCallNotifier"; 53 /** The identifier of the notification of new missed calls. */ 54 private static final int NOTIFICATION_ID = 1; 55 /** Preference file key for number of missed calls. */ 56 private static final String MISSED_CALL_COUNT = "missed_call_count"; 57 58 private static MissedCallNotifier sInstance; 59 private Context mContext; 60 61 /** Returns the singleton instance of the {@link MissedCallNotifier}. */ getInstance(Context context)62 public static MissedCallNotifier getInstance(Context context) { 63 if (sInstance == null) { 64 sInstance = new MissedCallNotifier(context); 65 } 66 return sInstance; 67 } 68 MissedCallNotifier(Context context)69 private MissedCallNotifier(Context context) { 70 mContext = context; 71 } 72 updateMissedCallNotification(int count, String number)73 public void updateMissedCallNotification(int count, String number) { 74 final int titleResId; 75 final String expandedText; // The text in the notification's line 1 and 2. 76 77 final List<NewCall> newCalls = 78 CallLogNotificationsHelper.getInstance(mContext).getNewMissedCalls(); 79 80 if (count == CallLogNotificationsService.UNKNOWN_MISSED_CALL_COUNT) { 81 if (newCalls == null) { 82 // If the intent did not contain a count, and we are unable to get a count from the 83 // call log, then no notification can be shown. 84 return; 85 } 86 count = newCalls.size(); 87 } 88 89 if (count == 0) { 90 // No voicemails to notify about: clear the notification. 91 clearMissedCalls(); 92 return; 93 } 94 95 // The call log has been updated, use that information preferentially. 96 boolean useCallLog = newCalls != null && newCalls.size() == count; 97 NewCall newestCall = useCallLog ? newCalls.get(0) : null; 98 long timeMs = useCallLog ? newestCall.dateMs : System.currentTimeMillis(); 99 100 Notification.Builder builder = new Notification.Builder(mContext); 101 // Display the first line of the notification: 102 // 1 missed call: <caller name || handle> 103 // More than 1 missed call: <number of calls> + "missed calls" 104 if (count == 1) { 105 //TODO: look up caller ID that is not in contacts. 106 ContactInfo contactInfo = CallLogNotificationsHelper.getInstance(mContext) 107 .getContactInfo(useCallLog ? newestCall.number : number, 108 useCallLog ? newestCall.numberPresentation 109 : Calls.PRESENTATION_ALLOWED, 110 useCallLog ? newestCall.countryIso : null); 111 112 titleResId = contactInfo.userType == ContactsUtils.USER_TYPE_WORK 113 ? R.string.notification_missedWorkCallTitle 114 : R.string.notification_missedCallTitle; 115 116 expandedText = contactInfo.name; 117 ContactPhotoLoader loader = new ContactPhotoLoader(mContext, contactInfo); 118 Bitmap photoIcon = loader.loadPhotoIcon(); 119 if (photoIcon != null) { 120 builder.setLargeIcon(photoIcon); 121 } 122 } else { 123 titleResId = R.string.notification_missedCallsTitle; 124 expandedText = 125 mContext.getString(R.string.notification_missedCallsMsg, count); 126 } 127 128 // Create a public viewable version of the notification, suitable for display when sensitive 129 // notification content is hidden. 130 Notification.Builder publicBuilder = new Notification.Builder(mContext); 131 publicBuilder.setSmallIcon(android.R.drawable.stat_notify_missed_call) 132 .setColor(mContext.getResources().getColor(R.color.dialer_theme_color)) 133 // Show "Phone" for notification title. 134 .setContentTitle(mContext.getText(R.string.userCallActivityLabel)) 135 // Notification details shows that there are missed call(s), but does not reveal 136 // the missed caller information. 137 .setContentText(mContext.getText(titleResId)) 138 .setContentIntent(createCallLogPendingIntent()) 139 .setAutoCancel(true) 140 .setWhen(timeMs) 141 .setDeleteIntent(createClearMissedCallsPendingIntent()); 142 143 // Create the notification suitable for display when sensitive information is showing. 144 builder.setSmallIcon(android.R.drawable.stat_notify_missed_call) 145 .setColor(mContext.getResources().getColor(R.color.dialer_theme_color)) 146 .setContentTitle(mContext.getText(titleResId)) 147 .setContentText(expandedText) 148 .setContentIntent(createCallLogPendingIntent()) 149 .setAutoCancel(true) 150 .setWhen(timeMs) 151 .setDeleteIntent(createClearMissedCallsPendingIntent()) 152 // Include a public version of the notification to be shown when the missed call 153 // notification is shown on the user's lock screen and they have chosen to hide 154 // sensitive notification information. 155 .setPublicVersion(publicBuilder.build()); 156 157 // Add additional actions when there is only 1 missed call and the user isn't locked 158 if (UserManagerCompat.isUserUnlocked(mContext) && count == 1) { 159 if (!TextUtils.isEmpty(number) 160 && !TextUtils.equals( 161 number, mContext.getString(R.string.handle_restricted))) { 162 builder.addAction(R.drawable.ic_phone_24dp, 163 mContext.getString(R.string.notification_missedCall_call_back), 164 createCallBackPendingIntent(number)); 165 166 if (!PhoneNumberHelper.isUriNumber(number)) { 167 builder.addAction(R.drawable.ic_message_24dp, 168 mContext.getString(R.string.notification_missedCall_message), 169 createSendSmsFromNotificationPendingIntent(number)); 170 } 171 } 172 } 173 174 Notification notification = builder.build(); 175 configureLedOnNotification(notification); 176 177 Log.i(TAG, "Adding missed call notification."); 178 getNotificationMgr().notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification); 179 } 180 clearMissedCalls()181 private void clearMissedCalls() { 182 AsyncTask.execute(new Runnable() { 183 @Override 184 public void run() { 185 // Call log is only accessible when unlocked. If that's the case, clear the list of 186 // new missed calls from the call log. 187 if (UserManagerCompat.isUserUnlocked(mContext)) { 188 ContentValues values = new ContentValues(); 189 values.put(Calls.NEW, 0); 190 values.put(Calls.IS_READ, 1); 191 StringBuilder where = new StringBuilder(); 192 where.append(Calls.NEW); 193 where.append(" = 1 AND "); 194 where.append(Calls.TYPE); 195 where.append(" = ?"); 196 try { 197 mContext.getContentResolver().update(Calls.CONTENT_URI, values, 198 where.toString(), new String[]{Integer.toString(Calls. 199 MISSED_TYPE)}); 200 } catch (IllegalArgumentException e) { 201 Log.w(TAG, "ContactsProvider update command failed", e); 202 } 203 } 204 getNotificationMgr().cancel(NOTIFICATION_TAG, NOTIFICATION_ID); 205 } 206 }); 207 } 208 209 /** 210 * Trigger an intent to make a call from a missed call number. 211 */ callBackFromMissedCall(String number)212 public void callBackFromMissedCall(String number) { 213 closeSystemDialogs(mContext); 214 CallLogNotificationsHelper.removeMissedCallNotifications(mContext); 215 DialerUtils.startActivityWithErrorToast( 216 mContext, 217 new CallIntentBuilder(number) 218 .build() 219 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 220 } 221 222 /** 223 * Trigger an intent to send an sms from a missed call number. 224 */ sendSmsFromMissedCall(String number)225 public void sendSmsFromMissedCall(String number) { 226 closeSystemDialogs(mContext); 227 CallLogNotificationsHelper.removeMissedCallNotifications(mContext); 228 DialerUtils.startActivityWithErrorToast( 229 mContext, 230 IntentUtil.getSendSmsIntent(number).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 231 } 232 233 /** 234 * Creates a new pending intent that sends the user to the call log. 235 * 236 * @return The pending intent. 237 */ createCallLogPendingIntent()238 private PendingIntent createCallLogPendingIntent() { 239 Intent contentIntent = new Intent(mContext, DialtactsActivity.class); 240 contentIntent.putExtra(DialtactsActivity.EXTRA_SHOW_TAB, ListsFragment.TAB_INDEX_HISTORY); 241 return PendingIntent.getActivity( 242 mContext, 0, contentIntent,PendingIntent.FLAG_UPDATE_CURRENT); 243 } 244 245 /** Creates a pending intent that marks all new missed calls as old. */ createClearMissedCallsPendingIntent()246 private PendingIntent createClearMissedCallsPendingIntent() { 247 Intent intent = new Intent(mContext, CallLogNotificationsService.class); 248 intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_MISSED_CALLS_AS_OLD); 249 return PendingIntent.getService(mContext, 0, intent, 0); 250 } 251 createCallBackPendingIntent(String number)252 private PendingIntent createCallBackPendingIntent(String number) { 253 Intent intent = new Intent(mContext, CallLogNotificationsService.class); 254 intent.setAction( 255 CallLogNotificationsService.ACTION_CALL_BACK_FROM_MISSED_CALL_NOTIFICATION); 256 intent.putExtra(CallLogNotificationsService.EXTRA_MISSED_CALL_NUMBER, number); 257 // Use FLAG_UPDATE_CURRENT to make sure any previous pending intent is updated with the new 258 // extra. 259 return PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); 260 } 261 createSendSmsFromNotificationPendingIntent(String number)262 private PendingIntent createSendSmsFromNotificationPendingIntent(String number) { 263 Intent intent = new Intent(mContext, CallLogNotificationsService.class); 264 intent.setAction( 265 CallLogNotificationsService.ACTION_SEND_SMS_FROM_MISSED_CALL_NOTIFICATION); 266 intent.putExtra(CallLogNotificationsService.EXTRA_MISSED_CALL_NUMBER, number); 267 // Use FLAG_UPDATE_CURRENT to make sure any previous pending intent is updated with the new 268 // extra. 269 return PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); 270 } 271 272 /** 273 * Configures a notification to emit the blinky notification light. 274 */ configureLedOnNotification(Notification notification)275 private void configureLedOnNotification(Notification notification) { 276 notification.flags |= Notification.FLAG_SHOW_LIGHTS; 277 notification.defaults |= Notification.DEFAULT_LIGHTS; 278 } 279 280 /** 281 * Closes open system dialogs and the notification shade. 282 */ closeSystemDialogs(Context context)283 private void closeSystemDialogs(Context context) { 284 context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); 285 } 286 getNotificationMgr()287 private NotificationManager getNotificationMgr() { 288 return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); 289 } 290 } 291