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