• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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