1 /* 2 * Copyright 2014, 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.server.telecom.ui; 18 19 import static android.Manifest.permission.READ_PHONE_STATE; 20 21 import android.annotation.NonNull; 22 import android.app.BroadcastOptions; 23 import android.content.ContentProvider; 24 import android.content.pm.PackageManager.NameNotFoundException; 25 import android.os.Bundle; 26 import android.telecom.Logging.Runnable; 27 import android.telecom.PhoneAccountHandle; 28 import android.telecom.TelecomManager; 29 30 import com.android.server.telecom.CallerInfoLookupHelper; 31 import com.android.server.telecom.CallsManagerListenerBase; 32 import com.android.server.telecom.Constants; 33 import com.android.server.telecom.DefaultDialerCache; 34 import com.android.server.telecom.DeviceIdleControllerAdapter; 35 import com.android.server.telecom.MissedCallNotifier; 36 import com.android.server.telecom.PhoneAccountRegistrar; 37 import com.android.server.telecom.R; 38 import com.android.server.telecom.TelecomBroadcastIntentProcessor; 39 import com.android.server.telecom.TelecomSystem; 40 import com.android.server.telecom.Timeouts; 41 import com.android.server.telecom.components.TelecomBroadcastReceiver; 42 43 import android.app.Notification; 44 import android.app.NotificationManager; 45 import android.app.PendingIntent; 46 import android.app.TaskStackBuilder; 47 import android.content.AsyncQueryHandler; 48 import android.content.ContentValues; 49 import android.content.Context; 50 import android.content.Intent; 51 import android.content.pm.ResolveInfo; 52 import android.database.Cursor; 53 import android.graphics.Bitmap; 54 import android.graphics.drawable.BitmapDrawable; 55 import android.graphics.drawable.Drawable; 56 import android.net.Uri; 57 import android.os.AsyncTask; 58 import android.os.Binder; 59 import android.os.UserHandle; 60 import android.provider.CallLog.Calls; 61 import android.telecom.Log; 62 import android.telecom.PhoneAccount; 63 import android.telephony.PhoneNumberUtils; 64 import android.telephony.TelephonyManager; 65 import android.text.BidiFormatter; 66 import android.text.TextDirectionHeuristics; 67 import android.text.TextUtils; 68 69 import android.telecom.CallerInfo; 70 import android.util.ArrayMap; 71 72 import java.lang.Override; 73 import java.lang.String; 74 import java.util.ArrayList; 75 import java.util.List; 76 import java.util.Locale; 77 import java.util.Map; 78 import java.util.Objects; 79 80 /** 81 * Creates a notification for calls that the user missed (neither answered nor rejected). 82 * 83 * TODO: Make TelephonyManager.clearMissedCalls call into this class. 84 */ 85 public class MissedCallNotifierImpl extends CallsManagerListenerBase implements MissedCallNotifier { 86 87 public interface MissedCallNotifierImplFactory { makeMissedCallNotifierImpl(Context context, PhoneAccountRegistrar phoneAccountRegistrar, DefaultDialerCache defaultDialerCache, DeviceIdleControllerAdapter deviceIdleControllerAdapter)88 MissedCallNotifier makeMissedCallNotifierImpl(Context context, 89 PhoneAccountRegistrar phoneAccountRegistrar, 90 DefaultDialerCache defaultDialerCache, 91 DeviceIdleControllerAdapter deviceIdleControllerAdapter); 92 } 93 94 public interface NotificationBuilderFactory { getBuilder(Context context)95 Notification.Builder getBuilder(Context context); 96 } 97 98 private static class DefaultNotificationBuilderFactory implements NotificationBuilderFactory { DefaultNotificationBuilderFactory()99 public DefaultNotificationBuilderFactory() {} 100 101 @Override getBuilder(Context context)102 public Notification.Builder getBuilder(Context context) { 103 return new Notification.Builder(context); 104 } 105 } 106 107 private static final String[] CALL_LOG_PROJECTION = new String[] { 108 Calls._ID, 109 Calls.NUMBER, 110 Calls.NUMBER_PRESENTATION, 111 Calls.DATE, 112 Calls.DURATION, 113 Calls.TYPE, 114 }; 115 116 private static final String CALL_LOG_WHERE_CLAUSE = "type=" + Calls.MISSED_TYPE + 117 " AND new=1" + 118 " AND is_read=0"; 119 120 public static final int CALL_LOG_COLUMN_ID = 0; 121 public static final int CALL_LOG_COLUMN_NUMBER = 1; 122 public static final int CALL_LOG_COLUMN_NUMBER_PRESENTATION = 2; 123 public static final int CALL_LOG_COLUMN_DATE = 3; 124 public static final int CALL_LOG_COLUMN_DURATION = 4; 125 public static final int CALL_LOG_COLUMN_TYPE = 5; 126 127 private static final int MISSED_CALL_NOTIFICATION_ID = 1; 128 private static final String NOTIFICATION_TAG = MissedCallNotifierImpl.class.getSimpleName(); 129 private static final String MISSED_CALL_POWER_SAVE_REASON = "missed-call"; 130 131 private final Context mContext; 132 private final PhoneAccountRegistrar mPhoneAccountRegistrar; 133 private final NotificationManager mNotificationManager; 134 private final NotificationBuilderFactory mNotificationBuilderFactory; 135 private final DefaultDialerCache mDefaultDialerCache; 136 private final DeviceIdleControllerAdapter mDeviceIdleControllerAdapter; 137 private UserHandle mCurrentUserHandle; 138 139 // Used to guard access to mMissedCallCounts 140 private final Object mMissedCallCountsLock = new Object(); 141 // Used to track the number of missed calls. 142 private final Map<UserHandle, Integer> mMissedCallCounts; 143 144 private List<UserHandle> mUsersToLoadAfterBootComplete = new ArrayList<>(); 145 MissedCallNotifierImpl(Context context, PhoneAccountRegistrar phoneAccountRegistrar, DefaultDialerCache defaultDialerCache, DeviceIdleControllerAdapter deviceIdleControllerAdapter)146 public MissedCallNotifierImpl(Context context, PhoneAccountRegistrar phoneAccountRegistrar, 147 DefaultDialerCache defaultDialerCache, 148 DeviceIdleControllerAdapter deviceIdleControllerAdapter) { 149 this(context, phoneAccountRegistrar, defaultDialerCache, 150 new DefaultNotificationBuilderFactory(), deviceIdleControllerAdapter); 151 } 152 MissedCallNotifierImpl(Context context, PhoneAccountRegistrar phoneAccountRegistrar, DefaultDialerCache defaultDialerCache, NotificationBuilderFactory notificationBuilderFactory, DeviceIdleControllerAdapter deviceIdleControllerAdapter)153 public MissedCallNotifierImpl(Context context, 154 PhoneAccountRegistrar phoneAccountRegistrar, 155 DefaultDialerCache defaultDialerCache, 156 NotificationBuilderFactory notificationBuilderFactory, 157 DeviceIdleControllerAdapter deviceIdleControllerAdapter) { 158 mContext = context; 159 mPhoneAccountRegistrar = phoneAccountRegistrar; 160 mNotificationManager = 161 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); 162 mDeviceIdleControllerAdapter = deviceIdleControllerAdapter; 163 mDefaultDialerCache = defaultDialerCache; 164 165 mNotificationBuilderFactory = notificationBuilderFactory; 166 mMissedCallCounts = new ArrayMap<>(); 167 } 168 169 /** Clears missed call notification and marks the call log's missed calls as read. */ 170 @Override clearMissedCalls(UserHandle userHandle)171 public void clearMissedCalls(UserHandle userHandle) { 172 // If the default dialer is showing the missed call notification then it will modify the 173 // call log and we don't have to do anything here. 174 String dialerPackage = getDefaultDialerPackage(userHandle); 175 if (!shouldManageNotificationThroughDefaultDialer(dialerPackage, userHandle)) { 176 markMissedCallsAsRead(userHandle); 177 } 178 cancelMissedCallNotification(userHandle); 179 } 180 markMissedCallsAsRead(final UserHandle userHandle)181 private void markMissedCallsAsRead(final UserHandle userHandle) { 182 AsyncTask.execute(new Runnable("MCNI.mMCAR", null /*lock*/) { 183 @Override 184 public void loggedRun() { 185 // Clear the list of new missed calls from the call log. 186 ContentValues values = new ContentValues(); 187 values.put(Calls.NEW, 0); 188 values.put(Calls.IS_READ, 1); 189 StringBuilder where = new StringBuilder(); 190 where.append(Calls.NEW); 191 where.append(" = 1 AND "); 192 where.append(Calls.TYPE); 193 where.append(" = ?"); 194 try { 195 Uri callsUri = ContentProvider 196 .maybeAddUserId(Calls.CONTENT_URI, userHandle.getIdentifier()); 197 mContext.getContentResolver().update(callsUri, values, 198 where.toString(), new String[]{ Integer.toString(Calls. 199 MISSED_TYPE) }); 200 } catch (IllegalArgumentException e) { 201 Log.w(this, "ContactsProvider update command failed", e); 202 } 203 } 204 }.prepare()); 205 } 206 getDefaultDialerPackage(UserHandle userHandle)207 private String getDefaultDialerPackage(UserHandle userHandle) { 208 String dialerPackage = mDefaultDialerCache.getDefaultDialerApplication( 209 userHandle.getIdentifier()); 210 if (TextUtils.isEmpty(dialerPackage)) { 211 return null; 212 } 213 return dialerPackage; 214 } 215 216 /** 217 * Returns the missed-call notification intent to send to the default dialer for the given user. 218 * Note, the passed in userHandle is always the non-managed user for SIM calls (multi-user 219 * calls). In this case we return the default dialer for the logged in user. This is never the 220 * managed (work profile) dialer. 221 * 222 * For non-multi-user calls (3rd party phone accounts), the passed in userHandle is the user 223 * handle of the phone account. This could be a managed user. In that case we return the default 224 * dialer for the given user which could be a managed (work profile) dialer. 225 */ getShowMissedCallIntentForDefaultDialer(String dialerPackage)226 private Intent getShowMissedCallIntentForDefaultDialer(String dialerPackage) { 227 return new Intent(TelecomManager.ACTION_SHOW_MISSED_CALLS_NOTIFICATION) 228 .setPackage(dialerPackage); 229 } 230 shouldManageNotificationThroughDefaultDialer(String dialerPackage, UserHandle userHandle)231 private boolean shouldManageNotificationThroughDefaultDialer(String dialerPackage, 232 UserHandle userHandle) { 233 if (TextUtils.isEmpty(dialerPackage)) return false; 234 235 Intent intent = getShowMissedCallIntentForDefaultDialer(dialerPackage); 236 if (intent == null) { 237 return false; 238 } 239 240 List<ResolveInfo> receivers = mContext.getPackageManager() 241 .queryBroadcastReceiversAsUser(intent, 0, userHandle.getIdentifier()); 242 return receivers.size() > 0; 243 } 244 245 /** 246 * For dialers that manage missed call handling themselves, we must temporarily add them to the 247 * power save exemption list, as they must perform operations such as modifying the call log and 248 * power save restrictions can cause these types of operations to not complete (sometimes 249 * causing ANRs). 250 */ exemptFromPowerSavingTemporarily(String dialerPackage, UserHandle handle)251 private Bundle exemptFromPowerSavingTemporarily(String dialerPackage, UserHandle handle) { 252 if (TextUtils.isEmpty(dialerPackage)) { 253 return null; 254 } 255 BroadcastOptions bopts = BroadcastOptions.makeBasic(); 256 long duration = Timeouts.getDialerMissedCallPowerSaveExemptionTimeMillis( 257 mContext.getContentResolver()); 258 mDeviceIdleControllerAdapter.exemptAppTemporarilyForEvent(dialerPackage, duration, 259 handle.getIdentifier(), MISSED_CALL_POWER_SAVE_REASON); 260 bopts.setTemporaryAppWhitelistDuration(duration); 261 return bopts.toBundle(); 262 } 263 sendNotificationThroughDefaultDialer(String dialerPackage, CallInfo callInfo, UserHandle userHandle, int missedCallCount)264 private void sendNotificationThroughDefaultDialer(String dialerPackage, CallInfo callInfo, 265 UserHandle userHandle, int missedCallCount) { 266 Intent intent = getShowMissedCallIntentForDefaultDialer(dialerPackage) 267 .setFlags(Intent.FLAG_RECEIVER_FOREGROUND) 268 .putExtra(TelecomManager.EXTRA_CLEAR_MISSED_CALLS_INTENT, 269 createClearMissedCallsPendingIntent(userHandle)) 270 .putExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, missedCallCount) 271 .putExtra(TelecomManager.EXTRA_NOTIFICATION_PHONE_NUMBER, 272 callInfo == null ? null : callInfo.getPhoneNumber()); 273 274 if (missedCallCount == 1 && callInfo != null) { 275 final Uri handleUri = callInfo.getHandle(); 276 String handle = handleUri == null ? null : handleUri.getSchemeSpecificPart(); 277 278 if (!TextUtils.isEmpty(handle) && !TextUtils.equals(handle, 279 mContext.getString(R.string.handle_restricted))) { 280 intent.putExtra(TelecomManager.EXTRA_CALL_BACK_INTENT, 281 createCallBackPendingIntent(handleUri, userHandle)); 282 } 283 } 284 285 Log.i(this, "sendNotificationThroughDefaultDialer; count=%d, dialerPackage=%s", 286 missedCallCount, intent.getPackage()); 287 Bundle options = exemptFromPowerSavingTemporarily(dialerPackage, userHandle); 288 mContext.sendBroadcastAsUser(intent, userHandle, READ_PHONE_STATE, options); 289 } 290 291 /** 292 * Create a system notification for the missed call. 293 * 294 * @param callInfo The missed call. 295 */ 296 @Override showMissedCallNotification(@onNull CallInfo callInfo)297 public void showMissedCallNotification(@NonNull CallInfo callInfo) { 298 final PhoneAccountHandle phoneAccountHandle = callInfo.getPhoneAccountHandle(); 299 final PhoneAccount phoneAccount = 300 mPhoneAccountRegistrar.getPhoneAccountUnchecked(phoneAccountHandle); 301 UserHandle userHandle; 302 if (phoneAccount != null && 303 phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) { 304 userHandle = mCurrentUserHandle; 305 } else { 306 userHandle = phoneAccountHandle.getUserHandle(); 307 } 308 showMissedCallNotification(callInfo, userHandle); 309 } 310 showMissedCallNotification(@onNull CallInfo callInfo, UserHandle userHandle)311 private void showMissedCallNotification(@NonNull CallInfo callInfo, UserHandle userHandle) { 312 int missedCallCounts; 313 synchronized (mMissedCallCountsLock) { 314 Integer currentCount = mMissedCallCounts.get(userHandle); 315 missedCallCounts = currentCount == null ? 0 : currentCount; 316 missedCallCounts++; 317 mMissedCallCounts.put(userHandle, missedCallCounts); 318 } 319 320 Log.i(this, "showMissedCallNotification: userHandle=%d, missedCallCount=%d", 321 userHandle.getIdentifier(), missedCallCounts); 322 323 String dialerPackage = getDefaultDialerPackage(userHandle); 324 if (shouldManageNotificationThroughDefaultDialer(dialerPackage, userHandle)) { 325 sendNotificationThroughDefaultDialer(dialerPackage, callInfo, userHandle, 326 missedCallCounts); 327 return; 328 } 329 330 final int titleResId; 331 final String expandedText; // The text in the notification's line 1 and 2. 332 333 // Display the first line of the notification: 334 // 1 missed call: <caller name || handle> 335 // More than 1 missed call: <number of calls> + "missed calls" 336 if (missedCallCounts == 1) { 337 expandedText = getNameForMissedCallNotification(callInfo); 338 339 CallerInfo ci = callInfo.getCallerInfo(); 340 if (ci != null && ci.userType == CallerInfo.USER_TYPE_WORK) { 341 titleResId = R.string.notification_missedWorkCallTitle; 342 } else { 343 titleResId = R.string.notification_missedCallTitle; 344 } 345 } else { 346 titleResId = R.string.notification_missedCallsTitle; 347 expandedText = 348 mContext.getString(R.string.notification_missedCallsMsg, missedCallCounts); 349 } 350 351 // Create a public viewable version of the notification, suitable for display when sensitive 352 // notification content is hidden. 353 // We use user's context here to make sure notification is badged if it is a managed user. 354 Context contextForUser = getContextForUser(userHandle); 355 Notification.Builder publicBuilder = mNotificationBuilderFactory.getBuilder(contextForUser); 356 publicBuilder.setSmallIcon(android.R.drawable.stat_notify_missed_call) 357 .setColor(mContext.getResources().getColor(R.color.theme_color)) 358 .setWhen(callInfo.getCreationTimeMillis()) 359 .setShowWhen(true) 360 // Show "Phone" for notification title. 361 .setContentTitle(mContext.getText(R.string.userCallActivityLabel)) 362 // Notification details shows that there are missed call(s), but does not reveal 363 // the missed caller information. 364 .setContentText(mContext.getText(titleResId)) 365 .setContentIntent(createCallLogPendingIntent(userHandle)) 366 .setAutoCancel(true) 367 .setDeleteIntent(createClearMissedCallsPendingIntent(userHandle)); 368 369 // Create the notification suitable for display when sensitive information is showing. 370 Notification.Builder builder = mNotificationBuilderFactory.getBuilder(contextForUser); 371 builder.setSmallIcon(android.R.drawable.stat_notify_missed_call) 372 .setColor(mContext.getResources().getColor(R.color.theme_color)) 373 .setWhen(callInfo.getCreationTimeMillis()) 374 .setShowWhen(true) 375 .setContentTitle(mContext.getText(titleResId)) 376 .setContentText(expandedText) 377 .setContentIntent(createCallLogPendingIntent(userHandle)) 378 .setAutoCancel(true) 379 .setDeleteIntent(createClearMissedCallsPendingIntent(userHandle)) 380 // Include a public version of the notification to be shown when the missed call 381 // notification is shown on the user's lock screen and they have chosen to hide 382 // sensitive notification information. 383 .setPublicVersion(publicBuilder.build()) 384 .setChannelId(NotificationChannelManager.CHANNEL_ID_MISSED_CALLS); 385 386 Uri handleUri = callInfo.getHandle(); 387 String handle = callInfo.getHandleSchemeSpecificPart(); 388 389 // Add additional actions when there is only 1 missed call, like call-back and SMS. 390 if (missedCallCounts == 1) { 391 Log.d(this, "Add actions with number %s.", Log.piiHandle(handle)); 392 393 if (!TextUtils.isEmpty(handle) 394 && !TextUtils.equals(handle, mContext.getString(R.string.handle_restricted))) { 395 builder.addAction(R.drawable.ic_phone_24dp, 396 mContext.getString(R.string.notification_missedCall_call_back), 397 createCallBackPendingIntent(handleUri, userHandle)); 398 399 if (canRespondViaSms(callInfo)) { 400 builder.addAction(R.drawable.ic_message_24dp, 401 mContext.getString(R.string.notification_missedCall_message), 402 createSendSmsFromNotificationPendingIntent(handleUri, userHandle)); 403 } 404 } 405 406 Bitmap photoIcon = callInfo.getCallerInfo() == null ? 407 null : callInfo.getCallerInfo().cachedPhotoIcon; 408 if (photoIcon != null) { 409 builder.setLargeIcon(photoIcon); 410 } else { 411 Drawable photo = callInfo.getCallerInfo() == null ? 412 null : callInfo.getCallerInfo().cachedPhoto; 413 if (photo != null && photo instanceof BitmapDrawable) { 414 builder.setLargeIcon(((BitmapDrawable) photo).getBitmap()); 415 } 416 } 417 } else { 418 Log.d(this, "Suppress actions. handle: %s, missedCalls: %d.", Log.piiHandle(handle), 419 missedCallCounts); 420 } 421 422 Notification notification = builder.build(); 423 configureLedOnNotification(notification); 424 425 Log.i(this, "Adding missed call notification for %s.", Log.pii(callInfo.getHandle())); 426 long token = Binder.clearCallingIdentity(); 427 try { 428 mNotificationManager.notifyAsUser( 429 NOTIFICATION_TAG, MISSED_CALL_NOTIFICATION_ID, notification, userHandle); 430 } finally { 431 Binder.restoreCallingIdentity(token); 432 } 433 } 434 435 436 /** Cancels the "missed call" notification. */ cancelMissedCallNotification(UserHandle userHandle)437 private void cancelMissedCallNotification(UserHandle userHandle) { 438 // Reset the number of missed calls to 0. 439 synchronized(mMissedCallCountsLock) { 440 mMissedCallCounts.put(userHandle, 0); 441 } 442 443 String dialerPackage = getDefaultDialerPackage(userHandle); 444 if (shouldManageNotificationThroughDefaultDialer(dialerPackage, userHandle)) { 445 sendNotificationThroughDefaultDialer(dialerPackage, null, userHandle, 446 0 /* missedCallCount */); 447 return; 448 } 449 450 long token = Binder.clearCallingIdentity(); 451 try { 452 mNotificationManager.cancelAsUser(NOTIFICATION_TAG, MISSED_CALL_NOTIFICATION_ID, 453 userHandle); 454 } finally { 455 Binder.restoreCallingIdentity(token); 456 } 457 } 458 459 /** 460 * Returns the name to use in the missed call notification. 461 */ getNameForMissedCallNotification(@onNull CallInfo callInfo)462 private String getNameForMissedCallNotification(@NonNull CallInfo callInfo) { 463 String handle = callInfo.getHandleSchemeSpecificPart(); 464 String name = callInfo.getName(); 465 466 if (!TextUtils.isEmpty(handle)) { 467 String formattedNumber = PhoneNumberUtils.formatNumber(handle, 468 getCurrentCountryIso(mContext)); 469 470 // The formatted number will be null if there was a problem formatting it, but we can 471 // default to using the unformatted number instead (e.g. a SIP URI may not be able to 472 // be formatted. 473 if (!TextUtils.isEmpty(formattedNumber)) { 474 handle = formattedNumber; 475 } 476 } 477 478 if (!TextUtils.isEmpty(name) && TextUtils.isGraphic(name)) { 479 return name; 480 } else if (!TextUtils.isEmpty(handle)) { 481 // A handle should always be displayed LTR using {@link BidiFormatter} regardless of the 482 // content of the rest of the notification. 483 // TODO: Does this apply to SIP addresses? 484 BidiFormatter bidiFormatter = BidiFormatter.getInstance(); 485 return bidiFormatter.unicodeWrap(handle, TextDirectionHeuristics.LTR); 486 } else { 487 // Use "unknown" if the call is unidentifiable. 488 return mContext.getString(R.string.unknown); 489 } 490 } 491 492 /** 493 * @return The ISO 3166-1 two letters country code of the country the user is in based on the 494 * network location. If the network location does not exist, fall back to the locale 495 * setting. 496 */ getCurrentCountryIso(Context context)497 private String getCurrentCountryIso(Context context) { 498 // Without framework function calls, this seems to be the most accurate location service 499 // we can rely on. 500 final TelephonyManager telephonyManager = 501 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 502 String countryIso = telephonyManager.getNetworkCountryIso().toUpperCase(); 503 504 if (countryIso == null) { 505 countryIso = Locale.getDefault().getCountry(); 506 Log.w(this, "No CountryDetector; falling back to countryIso based on locale: " 507 + countryIso); 508 } 509 return countryIso; 510 } 511 512 /** 513 * Creates a new pending intent that sends the user to the call log. 514 * 515 * @return The pending intent. 516 */ createCallLogPendingIntent(UserHandle userHandle)517 private PendingIntent createCallLogPendingIntent(UserHandle userHandle) { 518 Intent intent = new Intent(Intent.ACTION_VIEW, null); 519 intent.setType(Calls.CONTENT_TYPE); 520 521 TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(mContext); 522 taskStackBuilder.addNextIntent(intent); 523 524 return taskStackBuilder.getPendingIntent(0, PendingIntent.FLAG_IMMUTABLE, null, userHandle); 525 } 526 527 /** 528 * Creates an intent to be invoked when the missed call notification is cleared. 529 */ createClearMissedCallsPendingIntent(UserHandle userHandle)530 private PendingIntent createClearMissedCallsPendingIntent(UserHandle userHandle) { 531 return createTelecomPendingIntent( 532 TelecomBroadcastIntentProcessor.ACTION_CLEAR_MISSED_CALLS, null, userHandle); 533 } 534 535 /** 536 * Creates an intent to be invoked when the user opts to "call back" from the missed call 537 * notification. 538 * 539 * @param handle The handle to call back. 540 */ createCallBackPendingIntent(Uri handle, UserHandle userHandle)541 private PendingIntent createCallBackPendingIntent(Uri handle, UserHandle userHandle) { 542 return createTelecomPendingIntent( 543 TelecomBroadcastIntentProcessor.ACTION_CALL_BACK_FROM_NOTIFICATION, handle, 544 userHandle); 545 } 546 547 /** 548 * Creates an intent to be invoked when the user opts to "send sms" from the missed call 549 * notification. 550 */ createSendSmsFromNotificationPendingIntent(Uri handle, UserHandle userHandle)551 private PendingIntent createSendSmsFromNotificationPendingIntent(Uri handle, 552 UserHandle userHandle) { 553 return createTelecomPendingIntent( 554 TelecomBroadcastIntentProcessor.ACTION_SEND_SMS_FROM_NOTIFICATION, 555 Uri.fromParts(Constants.SCHEME_SMSTO, handle.getSchemeSpecificPart(), null), 556 userHandle); 557 } 558 559 /** 560 * Creates generic pending intent from the specified parameters to be received by 561 * {@link TelecomBroadcastIntentProcessor}. 562 * 563 * @param action The intent action. 564 * @param data The intent data. 565 */ createTelecomPendingIntent(String action, Uri data, UserHandle userHandle)566 private PendingIntent createTelecomPendingIntent(String action, Uri data, 567 UserHandle userHandle) { 568 Intent intent = new Intent(action, data, mContext, TelecomBroadcastReceiver.class); 569 intent.putExtra(TelecomBroadcastIntentProcessor.EXTRA_USERHANDLE, userHandle); 570 return PendingIntent.getBroadcast(mContext, 0, intent, 571 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); 572 } 573 574 /** 575 * Configures a notification to emit the blinky notification light. 576 */ configureLedOnNotification(Notification notification)577 private void configureLedOnNotification(Notification notification) { 578 notification.flags |= Notification.FLAG_SHOW_LIGHTS; 579 notification.defaults |= Notification.DEFAULT_LIGHTS; 580 } 581 canRespondViaSms(@onNull CallInfo callInfo)582 private boolean canRespondViaSms(@NonNull CallInfo callInfo) { 583 // Only allow respond-via-sms for "tel:" calls. 584 return callInfo.getHandle() != null && 585 PhoneAccount.SCHEME_TEL.equals(callInfo.getHandle().getScheme()); 586 } 587 588 @Override reloadAfterBootComplete(final CallerInfoLookupHelper callerInfoLookupHelper, CallInfoFactory callInfoFactory)589 public void reloadAfterBootComplete(final CallerInfoLookupHelper callerInfoLookupHelper, 590 CallInfoFactory callInfoFactory) { 591 if (!mUsersToLoadAfterBootComplete.isEmpty()) { 592 for (UserHandle handle : mUsersToLoadAfterBootComplete) { 593 Log.i(this, "reloadAfterBootComplete: user=%d", handle.getIdentifier()); 594 reloadFromDatabase(callerInfoLookupHelper, callInfoFactory, handle); 595 } 596 mUsersToLoadAfterBootComplete.clear(); 597 } else { 598 Log.i(this, "reloadAfterBootComplete: no user(s) to check; skipping reload."); 599 } 600 } 601 /** 602 * Adds the missed call notification on startup if there are unread missed calls. 603 */ 604 @Override reloadFromDatabase(final CallerInfoLookupHelper callerInfoLookupHelper, CallInfoFactory callInfoFactory, final UserHandle userHandle)605 public void reloadFromDatabase(final CallerInfoLookupHelper callerInfoLookupHelper, 606 CallInfoFactory callInfoFactory, final UserHandle userHandle) { 607 Log.d(this, "reloadFromDatabase: user=%d", userHandle.getIdentifier()); 608 if (TelecomSystem.getInstance() == null || !TelecomSystem.getInstance().isBootComplete()) { 609 Log.i(this, "reloadFromDatabase: Boot not yet complete -- call log db may not be " 610 + "available. Deferring loading until boot complete for user %d", 611 userHandle.getIdentifier()); 612 mUsersToLoadAfterBootComplete.add(userHandle); 613 return; 614 } 615 616 // instantiate query handler 617 AsyncQueryHandler queryHandler = new AsyncQueryHandler(mContext.getContentResolver()) { 618 @Override 619 protected void onQueryComplete(int token, Object cookie, Cursor cursor) { 620 Log.d(MissedCallNotifierImpl.this, "onQueryComplete()..."); 621 if (cursor != null) { 622 try { 623 synchronized(mMissedCallCountsLock) { 624 mMissedCallCounts.remove(userHandle); 625 } 626 while (cursor.moveToNext()) { 627 // Get data about the missed call from the cursor 628 final String handleString = cursor.getString(CALL_LOG_COLUMN_NUMBER); 629 final int presentation = 630 cursor.getInt(CALL_LOG_COLUMN_NUMBER_PRESENTATION); 631 final long date = cursor.getLong(CALL_LOG_COLUMN_DATE); 632 633 final Uri handle; 634 if (presentation != Calls.PRESENTATION_ALLOWED 635 || TextUtils.isEmpty(handleString)) { 636 handle = null; 637 } else { 638 // TODO: Remove the assumption that numbers are SIP or TEL only. 639 handle = Uri.fromParts(PhoneNumberUtils.isUriNumber(handleString) ? 640 PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL, 641 handleString, null); 642 } 643 644 callerInfoLookupHelper.startLookup(handle, 645 new CallerInfoLookupHelper.OnQueryCompleteListener() { 646 @Override 647 public void onCallerInfoQueryComplete(Uri queryHandle, 648 CallerInfo info) { 649 if (!Objects.equals(queryHandle, handle)) { 650 Log.w(MissedCallNotifierImpl.this, 651 "CallerInfo query returned with " + 652 "different handle."); 653 return; 654 } 655 if (info == null || 656 info.getContactDisplayPhotoUri() == null) { 657 // If there is no photo or if the caller info is 658 // null, just show the notification. 659 CallInfo callInfo = callInfoFactory.makeCallInfo( 660 info, null, handle, date); 661 showMissedCallNotification(callInfo, userHandle); 662 } 663 } 664 665 @Override 666 public void onContactPhotoQueryComplete(Uri queryHandle, 667 CallerInfo info) { 668 if (!Objects.equals(queryHandle, handle)) { 669 Log.w(MissedCallNotifierImpl.this, 670 "CallerInfo query for photo returned " + 671 "with different handle."); 672 return; 673 } 674 CallInfo callInfo = callInfoFactory.makeCallInfo( 675 info, null, handle, date); 676 showMissedCallNotification(callInfo, userHandle); 677 } 678 } 679 ); 680 } 681 } finally { 682 cursor.close(); 683 } 684 } 685 } 686 }; 687 688 // setup query spec, look for all Missed calls that are new. 689 Uri callsUri = 690 ContentProvider.maybeAddUserId(Calls.CONTENT_URI, userHandle.getIdentifier()); 691 // start the query 692 queryHandler.startQuery(0, null, callsUri, CALL_LOG_PROJECTION, 693 CALL_LOG_WHERE_CLAUSE, null, Calls.DEFAULT_SORT_ORDER); 694 } 695 696 @Override setCurrentUserHandle(UserHandle currentUserHandle)697 public void setCurrentUserHandle(UserHandle currentUserHandle) { 698 mCurrentUserHandle = currentUserHandle; 699 } 700 getContextForUser(UserHandle user)701 private Context getContextForUser(UserHandle user) { 702 try { 703 return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user); 704 } catch (NameNotFoundException e) { 705 // Default to mContext, not finding the package system is running as is unlikely. 706 return mContext; 707 } 708 } 709 } 710