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