• 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 PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
137     private UserHandle mCurrentUserHandle;
138 
139     // Used to track the number of missed calls.
140     private ConcurrentMap<UserHandle, AtomicInteger> mMissedCallCounts;
141 
MissedCallNotifierImpl(Context context, PhoneAccountRegistrar phoneAccountRegistrar, PhoneNumberUtilsAdapter phoneNumberUtilsAdapter)142     public MissedCallNotifierImpl(Context context, PhoneAccountRegistrar phoneAccountRegistrar,
143             PhoneNumberUtilsAdapter phoneNumberUtilsAdapter) {
144         this(context, phoneAccountRegistrar, phoneNumberUtilsAdapter,
145                 new DefaultNotificationBuilderFactory());
146     }
147 
MissedCallNotifierImpl(Context context, PhoneAccountRegistrar phoneAccountRegistrar, PhoneNumberUtilsAdapter phoneNumberUtilsAdapter, NotificationBuilderFactory notificationBuilderFactory)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
clearMissedCalls(UserHandle userHandle)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 
markMissedCallsAsRead(final UserHandle userHandle)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      */
getShowMissedCallIntentForDefaultDialer(UserHandle userHandle)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 
shouldManageNotificationThroughDefaultDialer(UserHandle userHandle)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 
sendNotificationThroughDefaultDialer(Call call, UserHandle userHandle)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
showMissedCallNotification(Call call)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 
showMissedCallNotification(Call call, UserHandle userHandle)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. */
cancelMissedCallNotification(UserHandle userHandle)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      */
getNameForCall(Call call)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      */
getCurrentCountryIso(Context context)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      */
createCallLogPendingIntent(UserHandle userHandle)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      */
createClearMissedCallsPendingIntent(UserHandle userHandle)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      */
createCallBackPendingIntent(Uri handle, UserHandle userHandle)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      */
createSendSmsFromNotificationPendingIntent(Uri handle, UserHandle userHandle)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      */
createTelecomPendingIntent(String action, Uri data, UserHandle userHandle)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      */
configureLedOnNotification(Notification notification)521     private void configureLedOnNotification(Notification notification) {
522         notification.flags |= Notification.FLAG_SHOW_LIGHTS;
523         notification.defaults |= Notification.DEFAULT_LIGHTS;
524     }
525 
canRespondViaSms(Call call)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
reloadFromDatabase( final TelecomSystem.SyncRoot lock, final CallsManager callsManager, final ContactsAsyncHelper contactsAsyncHelper, final CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory, final UserHandle userHandle)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
setCurrentUserHandle(UserHandle currentUserHandle)620     public void setCurrentUserHandle(UserHandle currentUserHandle) {
621         mCurrentUserHandle = currentUserHandle;
622     }
623 
getContextForUser(UserHandle user)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