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