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