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