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