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.content.ComponentName;
22import android.content.ContentProvider;
23import android.content.pm.PackageManager.NameNotFoundException;
24import android.telecom.PhoneAccountHandle;
25import android.telecom.TelecomManager;
26
27import com.android.server.telecom.Call;
28import com.android.server.telecom.CallState;
29import com.android.server.telecom.CallerInfoAsyncQueryFactory;
30import com.android.server.telecom.CallsManager;
31import com.android.server.telecom.CallsManagerListenerBase;
32import com.android.server.telecom.Constants;
33import com.android.server.telecom.ContactsAsyncHelper;
34import com.android.server.telecom.Log;
35import com.android.server.telecom.MissedCallNotifier;
36import com.android.server.telecom.PhoneAccountRegistrar;
37import com.android.server.telecom.PhoneNumberUtilsAdapter;
38import com.android.server.telecom.R;
39import com.android.server.telecom.Runnable;
40import com.android.server.telecom.TelecomBroadcastIntentProcessor;
41import com.android.server.telecom.TelecomSystem;
42import com.android.server.telecom.components.TelecomBroadcastReceiver;
43
44import android.app.Notification;
45import android.app.NotificationManager;
46import android.app.PendingIntent;
47import android.app.TaskStackBuilder;
48import android.content.AsyncQueryHandler;
49import android.content.ContentValues;
50import android.content.Context;
51import android.content.Intent;
52import android.content.pm.ResolveInfo;
53import android.database.Cursor;
54import android.graphics.Bitmap;
55import android.graphics.drawable.BitmapDrawable;
56import android.graphics.drawable.Drawable;
57import android.net.Uri;
58import android.os.AsyncTask;
59import android.os.Binder;
60import android.os.UserHandle;
61import android.provider.CallLog.Calls;
62import android.telecom.DefaultDialerManager;
63import android.telecom.DisconnectCause;
64import android.telecom.PhoneAccount;
65import android.telephony.PhoneNumberUtils;
66import android.telephony.TelephonyManager;
67import android.text.BidiFormatter;
68import android.text.TextDirectionHeuristics;
69import android.text.TextUtils;
70
71import com.android.internal.telephony.CallerInfo;
72
73import java.lang.Override;
74import java.lang.String;
75import java.util.List;
76import java.util.Locale;
77import java.util.concurrent.ConcurrentHashMap;
78import java.util.concurrent.ConcurrentMap;
79import 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 */
93public class MissedCallNotifierImpl extends CallsManagerListenerBase implements MissedCallNotifier {
94
95    public interface MissedCallNotifierImplFactory {
96        MissedCallNotifier makeMissedCallNotifierImpl(Context context,
97                PhoneAccountRegistrar phoneAccountRegistrar,
98                PhoneNumberUtilsAdapter phoneNumberUtilsAdapter);
99    }
100
101    public interface NotificationBuilderFactory {
102        Notification.Builder getBuilder(Context context);
103    }
104
105    private static class DefaultNotificationBuilderFactory implements NotificationBuilderFactory {
106        public DefaultNotificationBuilderFactory() {}
107
108        @Override
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
142    public MissedCallNotifierImpl(Context context, PhoneAccountRegistrar phoneAccountRegistrar,
143            PhoneNumberUtilsAdapter phoneNumberUtilsAdapter) {
144        this(context, phoneAccountRegistrar, phoneNumberUtilsAdapter,
145                new DefaultNotificationBuilderFactory());
146    }
147
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
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
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     */
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
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
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
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
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. */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
521    private void configureLedOnNotification(Notification notification) {
522        notification.flags |= Notification.FLAG_SHOW_LIGHTS;
523        notification.defaults |= Notification.DEFAULT_LIGHTS;
524    }
525
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
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
620    public void setCurrentUserHandle(UserHandle currentUserHandle) {
621        mCurrentUserHandle = currentUserHandle;
622    }
623
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