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 com.android.internal.telephony.CallerInfo; 66 67 import java.lang.Override; 68 import java.lang.String; 69 import java.util.List; 70 import java.util.Locale; 71 import java.util.Objects; 72 import java.util.concurrent.ConcurrentHashMap; 73 import java.util.concurrent.ConcurrentMap; 74 import 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 */ 83 public class MissedCallNotifierImpl extends CallsManagerListenerBase implements MissedCallNotifier { 84 85 public interface MissedCallNotifierImplFactory { makeMissedCallNotifierImpl(Context context, PhoneAccountRegistrar phoneAccountRegistrar, DefaultDialerCache defaultDialerCache)86 MissedCallNotifier makeMissedCallNotifierImpl(Context context, 87 PhoneAccountRegistrar phoneAccountRegistrar, 88 DefaultDialerCache defaultDialerCache); 89 } 90 91 public interface NotificationBuilderFactory { getBuilder(Context context)92 Notification.Builder getBuilder(Context context); 93 } 94 95 private static class DefaultNotificationBuilderFactory implements NotificationBuilderFactory { DefaultNotificationBuilderFactory()96 public DefaultNotificationBuilderFactory() {} 97 98 @Override getBuilder(Context context)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 MissedCallNotifierImpl(Context context, PhoneAccountRegistrar phoneAccountRegistrar, DefaultDialerCache defaultDialerCache)139 public MissedCallNotifierImpl(Context context, PhoneAccountRegistrar phoneAccountRegistrar, 140 DefaultDialerCache defaultDialerCache) { 141 this(context, phoneAccountRegistrar, defaultDialerCache, 142 new DefaultNotificationBuilderFactory()); 143 } 144 MissedCallNotifierImpl(Context context, PhoneAccountRegistrar phoneAccountRegistrar, DefaultDialerCache defaultDialerCache, NotificationBuilderFactory notificationBuilderFactory)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 clearMissedCalls(UserHandle userHandle)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 markMissedCallsAsRead(final UserHandle userHandle)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 */ getShowMissedCallIntentForDefaultDialer(UserHandle userHandle)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 shouldManageNotificationThroughDefaultDialer(UserHandle userHandle)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 sendNotificationThroughDefaultDialer(CallInfo callInfo, UserHandle userHandle)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 showMissedCallNotification(@onNull CallInfo callInfo)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 showMissedCallNotification(@onNull CallInfo callInfo, UserHandle userHandle)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 // Show "Phone" for notification title. 313 .setContentTitle(mContext.getText(R.string.userCallActivityLabel)) 314 // Notification details shows that there are missed call(s), but does not reveal 315 // the missed caller information. 316 .setContentText(mContext.getText(titleResId)) 317 .setContentIntent(createCallLogPendingIntent(userHandle)) 318 .setAutoCancel(true) 319 .setDeleteIntent(createClearMissedCallsPendingIntent(userHandle)); 320 321 // Create the notification suitable for display when sensitive information is showing. 322 Notification.Builder builder = mNotificationBuilderFactory.getBuilder(contextForUser); 323 builder.setSmallIcon(android.R.drawable.stat_notify_missed_call) 324 .setColor(mContext.getResources().getColor(R.color.theme_color)) 325 .setWhen(callInfo.getCreationTimeMillis()) 326 .setContentTitle(mContext.getText(titleResId)) 327 .setContentText(expandedText) 328 .setContentIntent(createCallLogPendingIntent(userHandle)) 329 .setAutoCancel(true) 330 .setDeleteIntent(createClearMissedCallsPendingIntent(userHandle)) 331 // Include a public version of the notification to be shown when the missed call 332 // notification is shown on the user's lock screen and they have chosen to hide 333 // sensitive notification information. 334 .setPublicVersion(publicBuilder.build()) 335 .setChannelId(NotificationChannelManager.CHANNEL_ID_MISSED_CALLS); 336 337 Uri handleUri = callInfo.getHandle(); 338 String handle = callInfo.getHandleSchemeSpecificPart(); 339 340 // Add additional actions when there is only 1 missed call, like call-back and SMS. 341 if (missCallCounts == 1) { 342 Log.d(this, "Add actions with number %s.", Log.piiHandle(handle)); 343 344 if (!TextUtils.isEmpty(handle) 345 && !TextUtils.equals(handle, mContext.getString(R.string.handle_restricted))) { 346 builder.addAction(R.drawable.ic_phone_24dp, 347 mContext.getString(R.string.notification_missedCall_call_back), 348 createCallBackPendingIntent(handleUri, userHandle)); 349 350 if (canRespondViaSms(callInfo)) { 351 builder.addAction(R.drawable.ic_message_24dp, 352 mContext.getString(R.string.notification_missedCall_message), 353 createSendSmsFromNotificationPendingIntent(handleUri, userHandle)); 354 } 355 } 356 357 Bitmap photoIcon = callInfo.getCallerInfo() == null ? 358 null : callInfo.getCallerInfo().cachedPhotoIcon; 359 if (photoIcon != null) { 360 builder.setLargeIcon(photoIcon); 361 } else { 362 Drawable photo = callInfo.getCallerInfo() == null ? 363 null : callInfo.getCallerInfo().cachedPhoto; 364 if (photo != null && photo instanceof BitmapDrawable) { 365 builder.setLargeIcon(((BitmapDrawable) photo).getBitmap()); 366 } 367 } 368 } else { 369 Log.d(this, "Suppress actions. handle: %s, missedCalls: %d.", Log.piiHandle(handle), 370 missCallCounts); 371 } 372 373 Notification notification = builder.build(); 374 configureLedOnNotification(notification); 375 376 Log.i(this, "Adding missed call notification for %s.", Log.pii(callInfo.getHandle())); 377 long token = Binder.clearCallingIdentity(); 378 try { 379 mNotificationManager.notifyAsUser( 380 NOTIFICATION_TAG, MISSED_CALL_NOTIFICATION_ID, notification, userHandle); 381 } finally { 382 Binder.restoreCallingIdentity(token); 383 } 384 } 385 386 387 /** Cancels the "missed call" notification. */ cancelMissedCallNotification(UserHandle userHandle)388 private void cancelMissedCallNotification(UserHandle userHandle) { 389 // Reset the number of missed calls to 0. 390 mMissedCallCounts.putIfAbsent(userHandle, new AtomicInteger(0)); 391 mMissedCallCounts.get(userHandle).set(0); 392 393 if (shouldManageNotificationThroughDefaultDialer(userHandle)) { 394 sendNotificationThroughDefaultDialer(null, userHandle); 395 return; 396 } 397 398 long token = Binder.clearCallingIdentity(); 399 try { 400 mNotificationManager.cancelAsUser(NOTIFICATION_TAG, MISSED_CALL_NOTIFICATION_ID, 401 userHandle); 402 } finally { 403 Binder.restoreCallingIdentity(token); 404 } 405 } 406 407 /** 408 * Returns the name to use in the missed call notification. 409 */ getNameForMissedCallNotification(@onNull CallInfo callInfo)410 private String getNameForMissedCallNotification(@NonNull CallInfo callInfo) { 411 String handle = callInfo.getHandleSchemeSpecificPart(); 412 String name = callInfo.getName(); 413 414 if (!TextUtils.isEmpty(handle)) { 415 String formattedNumber = PhoneNumberUtils.formatNumber(handle, 416 getCurrentCountryIso(mContext)); 417 418 // The formatted number will be null if there was a problem formatting it, but we can 419 // default to using the unformatted number instead (e.g. a SIP URI may not be able to 420 // be formatted. 421 if (!TextUtils.isEmpty(formattedNumber)) { 422 handle = formattedNumber; 423 } 424 } 425 426 if (!TextUtils.isEmpty(name) && TextUtils.isGraphic(name)) { 427 return name; 428 } else if (!TextUtils.isEmpty(handle)) { 429 // A handle should always be displayed LTR using {@link BidiFormatter} regardless of the 430 // content of the rest of the notification. 431 // TODO: Does this apply to SIP addresses? 432 BidiFormatter bidiFormatter = BidiFormatter.getInstance(); 433 return bidiFormatter.unicodeWrap(handle, TextDirectionHeuristics.LTR); 434 } else { 435 // Use "unknown" if the call is unidentifiable. 436 return mContext.getString(R.string.unknown); 437 } 438 } 439 440 /** 441 * @return The ISO 3166-1 two letters country code of the country the user is in based on the 442 * network location. If the network location does not exist, fall back to the locale 443 * setting. 444 */ getCurrentCountryIso(Context context)445 private String getCurrentCountryIso(Context context) { 446 // Without framework function calls, this seems to be the most accurate location service 447 // we can rely on. 448 final TelephonyManager telephonyManager = 449 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 450 String countryIso = telephonyManager.getNetworkCountryIso().toUpperCase(); 451 452 if (countryIso == null) { 453 countryIso = Locale.getDefault().getCountry(); 454 Log.w(this, "No CountryDetector; falling back to countryIso based on locale: " 455 + countryIso); 456 } 457 return countryIso; 458 } 459 460 /** 461 * Creates a new pending intent that sends the user to the call log. 462 * 463 * @return The pending intent. 464 */ createCallLogPendingIntent(UserHandle userHandle)465 private PendingIntent createCallLogPendingIntent(UserHandle userHandle) { 466 Intent intent = new Intent(Intent.ACTION_VIEW, null); 467 intent.setType(Calls.CONTENT_TYPE); 468 469 TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(mContext); 470 taskStackBuilder.addNextIntent(intent); 471 472 return taskStackBuilder.getPendingIntent(0, 0, null, userHandle); 473 } 474 475 /** 476 * Creates an intent to be invoked when the missed call notification is cleared. 477 */ createClearMissedCallsPendingIntent(UserHandle userHandle)478 private PendingIntent createClearMissedCallsPendingIntent(UserHandle userHandle) { 479 return createTelecomPendingIntent( 480 TelecomBroadcastIntentProcessor.ACTION_CLEAR_MISSED_CALLS, null, userHandle); 481 } 482 483 /** 484 * Creates an intent to be invoked when the user opts to "call back" from the missed call 485 * notification. 486 * 487 * @param handle The handle to call back. 488 */ createCallBackPendingIntent(Uri handle, UserHandle userHandle)489 private PendingIntent createCallBackPendingIntent(Uri handle, UserHandle userHandle) { 490 return createTelecomPendingIntent( 491 TelecomBroadcastIntentProcessor.ACTION_CALL_BACK_FROM_NOTIFICATION, handle, 492 userHandle); 493 } 494 495 /** 496 * Creates an intent to be invoked when the user opts to "send sms" from the missed call 497 * notification. 498 */ createSendSmsFromNotificationPendingIntent(Uri handle, UserHandle userHandle)499 private PendingIntent createSendSmsFromNotificationPendingIntent(Uri handle, 500 UserHandle userHandle) { 501 return createTelecomPendingIntent( 502 TelecomBroadcastIntentProcessor.ACTION_SEND_SMS_FROM_NOTIFICATION, 503 Uri.fromParts(Constants.SCHEME_SMSTO, handle.getSchemeSpecificPart(), null), 504 userHandle); 505 } 506 507 /** 508 * Creates generic pending intent from the specified parameters to be received by 509 * {@link TelecomBroadcastIntentProcessor}. 510 * 511 * @param action The intent action. 512 * @param data The intent data. 513 */ createTelecomPendingIntent(String action, Uri data, UserHandle userHandle)514 private PendingIntent createTelecomPendingIntent(String action, Uri data, 515 UserHandle userHandle) { 516 Intent intent = new Intent(action, data, mContext, TelecomBroadcastReceiver.class); 517 intent.putExtra(TelecomBroadcastIntentProcessor.EXTRA_USERHANDLE, userHandle); 518 return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); 519 } 520 521 /** 522 * Configures a notification to emit the blinky notification light. 523 */ configureLedOnNotification(Notification notification)524 private void configureLedOnNotification(Notification notification) { 525 notification.flags |= Notification.FLAG_SHOW_LIGHTS; 526 notification.defaults |= Notification.DEFAULT_LIGHTS; 527 } 528 canRespondViaSms(@onNull CallInfo callInfo)529 private boolean canRespondViaSms(@NonNull CallInfo callInfo) { 530 // Only allow respond-via-sms for "tel:" calls. 531 return callInfo.getHandle() != null && 532 PhoneAccount.SCHEME_TEL.equals(callInfo.getHandle().getScheme()); 533 } 534 535 @Override reloadAfterBootComplete(final CallerInfoLookupHelper callerInfoLookupHelper, CallInfoFactory callInfoFactory)536 public void reloadAfterBootComplete(final CallerInfoLookupHelper callerInfoLookupHelper, 537 CallInfoFactory callInfoFactory) { 538 if (userToLoadAfterBootComplete != null) { 539 reloadFromDatabase(callerInfoLookupHelper, 540 callInfoFactory, userToLoadAfterBootComplete); 541 userToLoadAfterBootComplete = null; 542 } 543 } 544 /** 545 * Adds the missed call notification on startup if there are unread missed calls. 546 */ 547 @Override reloadFromDatabase(final CallerInfoLookupHelper callerInfoLookupHelper, CallInfoFactory callInfoFactory, final UserHandle userHandle)548 public void reloadFromDatabase(final CallerInfoLookupHelper callerInfoLookupHelper, 549 CallInfoFactory callInfoFactory, final UserHandle userHandle) { 550 Log.d(this, "reloadFromDatabase()..."); 551 if (TelecomSystem.getInstance() == null || !TelecomSystem.getInstance().isBootComplete()) { 552 Log.i(this, "Boot not yet complete -- call log db may not be available. Deferring " + 553 "loading until boot complete."); 554 userToLoadAfterBootComplete = userHandle; 555 return; 556 } 557 558 // instantiate query handler 559 AsyncQueryHandler queryHandler = new AsyncQueryHandler(mContext.getContentResolver()) { 560 @Override 561 protected void onQueryComplete(int token, Object cookie, Cursor cursor) { 562 Log.d(MissedCallNotifierImpl.this, "onQueryComplete()..."); 563 if (cursor != null) { 564 try { 565 mMissedCallCounts.remove(userHandle); 566 while (cursor.moveToNext()) { 567 // Get data about the missed call from the cursor 568 final String handleString = cursor.getString(CALL_LOG_COLUMN_NUMBER); 569 final int presentation = 570 cursor.getInt(CALL_LOG_COLUMN_NUMBER_PRESENTATION); 571 final long date = cursor.getLong(CALL_LOG_COLUMN_DATE); 572 573 final Uri handle; 574 if (presentation != Calls.PRESENTATION_ALLOWED 575 || TextUtils.isEmpty(handleString)) { 576 handle = null; 577 } else { 578 handle = Uri.fromParts(PhoneNumberUtils.isUriNumber(handleString) ? 579 PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL, 580 handleString, null); 581 } 582 583 callerInfoLookupHelper.startLookup(handle, 584 new CallerInfoLookupHelper.OnQueryCompleteListener() { 585 @Override 586 public void onCallerInfoQueryComplete(Uri queryHandle, 587 CallerInfo info) { 588 if (!Objects.equals(queryHandle, handle)) { 589 Log.w(MissedCallNotifierImpl.this, 590 "CallerInfo query returned with " + 591 "different handle."); 592 return; 593 } 594 if (info == null || 595 info.contactDisplayPhotoUri == null) { 596 // If there is no photo or if the caller info is 597 // null, just show the notification. 598 CallInfo callInfo = callInfoFactory.makeCallInfo( 599 info, null, handle, date); 600 showMissedCallNotification(callInfo, userHandle); 601 } 602 } 603 604 @Override 605 public void onContactPhotoQueryComplete(Uri queryHandle, 606 CallerInfo info) { 607 if (!Objects.equals(queryHandle, handle)) { 608 Log.w(MissedCallNotifierImpl.this, 609 "CallerInfo query for photo returned " + 610 "with different handle."); 611 return; 612 } 613 CallInfo callInfo = callInfoFactory.makeCallInfo( 614 info, null, handle, date); 615 showMissedCallNotification(callInfo, userHandle); 616 } 617 } 618 ); 619 } 620 } finally { 621 cursor.close(); 622 } 623 } 624 } 625 }; 626 627 // setup query spec, look for all Missed calls that are new. 628 Uri callsUri = 629 ContentProvider.maybeAddUserId(Calls.CONTENT_URI, userHandle.getIdentifier()); 630 // start the query 631 queryHandler.startQuery(0, null, callsUri, CALL_LOG_PROJECTION, 632 CALL_LOG_WHERE_CLAUSE, null, Calls.DEFAULT_SORT_ORDER); 633 } 634 635 @Override setCurrentUserHandle(UserHandle currentUserHandle)636 public void setCurrentUserHandle(UserHandle currentUserHandle) { 637 mCurrentUserHandle = currentUserHandle; 638 } 639 getContextForUser(UserHandle user)640 private Context getContextForUser(UserHandle user) { 641 try { 642 return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user); 643 } catch (NameNotFoundException e) { 644 // Default to mContext, not finding the package system is running as is unlikely. 645 return mContext; 646 } 647 } 648 } 649