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