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