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