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