/*
 * Copyright 2014, The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.telecom.ui;

import static android.Manifest.permission.READ_PHONE_STATE;

import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.pm.PackageManager.NameNotFoundException;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;

import com.android.server.telecom.Call;
import com.android.server.telecom.CallState;
import com.android.server.telecom.CallerInfoAsyncQueryFactory;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.CallsManagerListenerBase;
import com.android.server.telecom.Constants;
import com.android.server.telecom.ContactsAsyncHelper;
import com.android.server.telecom.Log;
import com.android.server.telecom.MissedCallNotifier;
import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.PhoneNumberUtilsAdapter;
import com.android.server.telecom.R;
import com.android.server.telecom.Runnable;
import com.android.server.telecom.TelecomBroadcastIntentProcessor;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.components.TelecomBroadcastReceiver;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.TaskStackBuilder;
import android.content.AsyncQueryHandler;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.UserHandle;
import android.provider.CallLog.Calls;
import android.telecom.DefaultDialerManager;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
import android.text.BidiFormatter;
import android.text.TextDirectionHeuristics;
import android.text.TextUtils;

import com.android.internal.telephony.CallerInfo;

import java.lang.Override;
import java.lang.String;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

// TODO: Needed for move to system service: import com.android.internal.R;

/**
 * Creates a notification for calls that the user missed (neither answered nor rejected).
 *
 * TODO: Make TelephonyManager.clearMissedCalls call into this class.
 *
 * TODO: Reduce dependencies in this implementation; remove the need to create a new Call
 *     simply to look up caller metadata, and if possible, make it unnecessary to get a
 *     direct reference to the CallsManager. Try to make this class simply handle the UI
 *     and Android-framework entanglements of missed call notification.
 */
public class MissedCallNotifierImpl extends CallsManagerListenerBase implements MissedCallNotifier {

    public interface MissedCallNotifierImplFactory {
        MissedCallNotifier makeMissedCallNotifierImpl(Context context,
                PhoneAccountRegistrar phoneAccountRegistrar,
                PhoneNumberUtilsAdapter phoneNumberUtilsAdapter);
    }

    public interface NotificationBuilderFactory {
        Notification.Builder getBuilder(Context context);
    }

    private static class DefaultNotificationBuilderFactory implements NotificationBuilderFactory {
        public DefaultNotificationBuilderFactory() {}

        @Override
        public Notification.Builder getBuilder(Context context) {
            return new Notification.Builder(context);
        }
    }

    private static final String[] CALL_LOG_PROJECTION = new String[] {
        Calls._ID,
        Calls.NUMBER,
        Calls.NUMBER_PRESENTATION,
        Calls.DATE,
        Calls.DURATION,
        Calls.TYPE,
    };

    private static final int CALL_LOG_COLUMN_ID = 0;
    private static final int CALL_LOG_COLUMN_NUMBER = 1;
    private static final int CALL_LOG_COLUMN_NUMBER_PRESENTATION = 2;
    private static final int CALL_LOG_COLUMN_DATE = 3;
    private static final int CALL_LOG_COLUMN_DURATION = 4;
    private static final int CALL_LOG_COLUMN_TYPE = 5;

    private static final int MISSED_CALL_NOTIFICATION_ID = 1;

    private final Context mContext;
    private final PhoneAccountRegistrar mPhoneAccountRegistrar;
    private final NotificationManager mNotificationManager;
    private final NotificationBuilderFactory mNotificationBuilderFactory;
    private final ComponentName mNotificationComponent;
    private final PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
    private UserHandle mCurrentUserHandle;

    // Used to track the number of missed calls.
    private ConcurrentMap<UserHandle, AtomicInteger> mMissedCallCounts;

    public MissedCallNotifierImpl(Context context, PhoneAccountRegistrar phoneAccountRegistrar,
            PhoneNumberUtilsAdapter phoneNumberUtilsAdapter) {
        this(context, phoneAccountRegistrar, phoneNumberUtilsAdapter,
                new DefaultNotificationBuilderFactory());
    }

    public MissedCallNotifierImpl(Context context,
            PhoneAccountRegistrar phoneAccountRegistrar,
            PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
            NotificationBuilderFactory notificationBuilderFactory) {
        mContext = context;
        mPhoneAccountRegistrar = phoneAccountRegistrar;
        mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
        mNotificationManager =
                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
        final String notificationComponent = context.getString(R.string.notification_component);

        mNotificationBuilderFactory = notificationBuilderFactory;
        mNotificationComponent = notificationComponent != null
                ? ComponentName.unflattenFromString(notificationComponent) : null;
        mMissedCallCounts = new ConcurrentHashMap<>();
    }

    /** Clears missed call notification and marks the call log's missed calls as read. */
    @Override
    public void clearMissedCalls(UserHandle userHandle) {
        // If the default dialer is showing the missed call notification then it will modify the
        // call log and we don't have to do anything here.
        if (!shouldManageNotificationThroughDefaultDialer(userHandle)) {
            markMissedCallsAsRead(userHandle);
        }
        cancelMissedCallNotification(userHandle);
    }

    private void markMissedCallsAsRead(final UserHandle userHandle) {
        AsyncTask.execute(new Runnable("MCNI.mMCAR", null /*lock*/) {
            @Override
            public void loggedRun() {
                // Clear the list of new missed calls from the call log.
                ContentValues values = new ContentValues();
                values.put(Calls.NEW, 0);
                values.put(Calls.IS_READ, 1);
                StringBuilder where = new StringBuilder();
                where.append(Calls.NEW);
                where.append(" = 1 AND ");
                where.append(Calls.TYPE);
                where.append(" = ?");
                try {
                    Uri callsUri = ContentProvider
                            .maybeAddUserId(Calls.CONTENT_URI, userHandle.getIdentifier());
                    mContext.getContentResolver().update(callsUri, values,
                            where.toString(), new String[]{ Integer.toString(Calls.
                            MISSED_TYPE) });
                } catch (IllegalArgumentException e) {
                    Log.w(this, "ContactsProvider update command failed", e);
                }
            }
        }.prepare());
    }

    /**
     * Broadcasts missed call notification to custom component if set.
     * Currently the component is set in phone capable android wear device.
     * @param userHandle The user that has the missed call(s).
     * @return {@code true} if the broadcast was sent. {@code false} otherwise.
     */
    private boolean sendNotificationCustomComponent(Call call, UserHandle userHandle) {
        if (mNotificationComponent != null) {
            int count = mMissedCallCounts.get(userHandle).get();
            Intent intent = new Intent();
            intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
            intent.setComponent(mNotificationComponent);
            intent.setAction(TelecomManager.ACTION_SHOW_MISSED_CALLS_NOTIFICATION);
            intent.putExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, count);
            intent.putExtra(TelecomManager.EXTRA_NOTIFICATION_PHONE_NUMBER,
                    call != null ? call.getPhoneNumber() : null);
            intent.putExtra(TelecomManager.EXTRA_CLEAR_MISSED_CALLS_INTENT,
                    createClearMissedCallsPendingIntent(userHandle));


            if (count == 1 && call != null) {
                final Uri handleUri = call.getHandle();
                String handle = handleUri == null ? null : handleUri.getSchemeSpecificPart();

                if (!TextUtils.isEmpty(handle) && !TextUtils.equals(handle,
                        mContext.getString(R.string.handle_restricted))) {
                    intent.putExtra(TelecomManager.EXTRA_CALL_BACK_INTENT,
                            createCallBackPendingIntent(handleUri, userHandle));
                }
            }

            mContext.sendBroadcast(intent);
            return true;
        }

        return false;
    }

    /**
     * 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
     * calls). In this case we return the default dialer for the logged in user. This is never the
     * managed (work profile) dialer.
     *
     * For non-multi-user calls (3rd party phone accounts), the passed in userHandle is the user
     * handle of the phone account. This could be a managed user. In that case we return the default
     * dialer for the given user which could be a managed (work profile) dialer.
     */
    private Intent getShowMissedCallIntentForDefaultDialer(UserHandle userHandle) {
        String dialerPackage = DefaultDialerManager
                .getDefaultDialerApplication(mContext, userHandle.getIdentifier());
        if (TextUtils.isEmpty(dialerPackage)) {
            return null;
        }
        return new Intent(TelecomManager.ACTION_SHOW_MISSED_CALLS_NOTIFICATION)
            .setPackage(dialerPackage);
    }

    private boolean shouldManageNotificationThroughDefaultDialer(UserHandle userHandle) {
        Intent intent = getShowMissedCallIntentForDefaultDialer(userHandle);
        if (intent == null) {
            return false;
        }

        List<ResolveInfo> receivers = mContext.getPackageManager()
                .queryBroadcastReceiversAsUser(intent, 0, userHandle.getIdentifier());
        return receivers.size() > 0;
    }

    private void sendNotificationThroughDefaultDialer(Call call, UserHandle userHandle) {
        int count = mMissedCallCounts.get(userHandle).get();
        Intent intent = getShowMissedCallIntentForDefaultDialer(userHandle)
            .setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
            .putExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, count)
            .putExtra(TelecomManager.EXTRA_NOTIFICATION_PHONE_NUMBER,
                    call != null ? call.getPhoneNumber() : null);

        Log.w(this, "Showing missed calls through default dialer.");
        mContext.sendBroadcastAsUser(intent, userHandle, READ_PHONE_STATE);
    }

    /**
     * Create a system notification for the missed call.
     *
     * @param call The missed call.
     */
    @Override
    public void showMissedCallNotification(Call call) {
        final PhoneAccountHandle phoneAccountHandle = call.getTargetPhoneAccount();
        final PhoneAccount phoneAccount =
                mPhoneAccountRegistrar.getPhoneAccountUnchecked(phoneAccountHandle);
        UserHandle userHandle;
        if (phoneAccount != null &&
                phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
            userHandle = mCurrentUserHandle;
        } else {
            userHandle = phoneAccountHandle.getUserHandle();
        }
        showMissedCallNotification(call, userHandle);
    }

    private void showMissedCallNotification(Call call, UserHandle userHandle) {
        mMissedCallCounts.putIfAbsent(userHandle, new AtomicInteger(0));
        int missCallCounts = mMissedCallCounts.get(userHandle).incrementAndGet();

        if (sendNotificationCustomComponent(call, userHandle)) {
            return;
        }

        if (shouldManageNotificationThroughDefaultDialer(userHandle)) {
            sendNotificationThroughDefaultDialer(call, userHandle);
            return;
        }

        final int titleResId;
        final String expandedText;  // The text in the notification's line 1 and 2.

        // Display the first line of the notification:
        // 1 missed call: <caller name || handle>
        // More than 1 missed call: <number of calls> + "missed calls"
        if (missCallCounts == 1) {
            expandedText = getNameForCall(call);

            CallerInfo ci = call.getCallerInfo();
            if (ci != null && ci.userType == CallerInfo.USER_TYPE_WORK) {
                titleResId = R.string.notification_missedWorkCallTitle;
            } else {
                titleResId = R.string.notification_missedCallTitle;
            }
        } else {
            titleResId = R.string.notification_missedCallsTitle;
            expandedText =
                    mContext.getString(R.string.notification_missedCallsMsg, missCallCounts);
        }

        // Create a public viewable version of the notification, suitable for display when sensitive
        // notification content is hidden.
        // We use user's context here to make sure notification is badged if it is a managed user.
        Context contextForUser = getContextForUser(userHandle);
        Notification.Builder publicBuilder = mNotificationBuilderFactory.getBuilder(contextForUser);
        publicBuilder.setSmallIcon(android.R.drawable.stat_notify_missed_call)
                .setColor(mContext.getResources().getColor(R.color.theme_color))
                .setWhen(call.getCreationTimeMillis())
                // Show "Phone" for notification title.
                .setContentTitle(mContext.getText(R.string.userCallActivityLabel))
                // Notification details shows that there are missed call(s), but does not reveal
                // the missed caller information.
                .setContentText(mContext.getText(titleResId))
                .setContentIntent(createCallLogPendingIntent(userHandle))
                .setAutoCancel(true)
                .setDeleteIntent(createClearMissedCallsPendingIntent(userHandle));

        // Create the notification suitable for display when sensitive information is showing.
        Notification.Builder builder = mNotificationBuilderFactory.getBuilder(contextForUser);
        builder.setSmallIcon(android.R.drawable.stat_notify_missed_call)
                .setColor(mContext.getResources().getColor(R.color.theme_color))
                .setWhen(call.getCreationTimeMillis())
                .setContentTitle(mContext.getText(titleResId))
                .setContentText(expandedText)
                .setContentIntent(createCallLogPendingIntent(userHandle))
                .setAutoCancel(true)
                .setDeleteIntent(createClearMissedCallsPendingIntent(userHandle))
                // Include a public version of the notification to be shown when the missed call
                // notification is shown on the user's lock screen and they have chosen to hide
                // sensitive notification information.
                .setPublicVersion(publicBuilder.build());

        Uri handleUri = call.getHandle();
        String handle = handleUri == null ? null : handleUri.getSchemeSpecificPart();

        // Add additional actions when there is only 1 missed call, like call-back and SMS.
        if (missCallCounts == 1) {
            Log.d(this, "Add actions with number %s.", Log.piiHandle(handle));

            if (!TextUtils.isEmpty(handle)
                    && !TextUtils.equals(handle, mContext.getString(R.string.handle_restricted))) {
                builder.addAction(R.drawable.ic_phone_24dp,
                        mContext.getString(R.string.notification_missedCall_call_back),
                        createCallBackPendingIntent(handleUri, userHandle));

                if (canRespondViaSms(call)) {
                    builder.addAction(R.drawable.ic_message_24dp,
                            mContext.getString(R.string.notification_missedCall_message),
                            createSendSmsFromNotificationPendingIntent(handleUri, userHandle));
                }
            }

            Bitmap photoIcon = call.getPhotoIcon();
            if (photoIcon != null) {
                builder.setLargeIcon(photoIcon);
            } else {
                Drawable photo = call.getPhoto();
                if (photo != null && photo instanceof BitmapDrawable) {
                    builder.setLargeIcon(((BitmapDrawable) photo).getBitmap());
                }
            }
        } else {
            Log.d(this, "Suppress actions. handle: %s, missedCalls: %d.", Log.piiHandle(handle),
                    missCallCounts);
        }

        Notification notification = builder.build();
        configureLedOnNotification(notification);

        Log.i(this, "Adding missed call notification for %s.", call);
        long token = Binder.clearCallingIdentity();
        try {
            mNotificationManager.notifyAsUser(
                    null /* tag */, MISSED_CALL_NOTIFICATION_ID, notification, userHandle);
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }


    /** Cancels the "missed call" notification. */
    private void cancelMissedCallNotification(UserHandle userHandle) {
        // Reset the number of missed calls to 0.
        mMissedCallCounts.putIfAbsent(userHandle, new AtomicInteger(0));
        mMissedCallCounts.get(userHandle).set(0);

        if (sendNotificationCustomComponent(null, userHandle)) {
            return;
        }

        if (shouldManageNotificationThroughDefaultDialer(userHandle)) {
            sendNotificationThroughDefaultDialer(null, userHandle);
            return;
        }

        long token = Binder.clearCallingIdentity();
        try {
            mNotificationManager.cancelAsUser(null, MISSED_CALL_NOTIFICATION_ID, userHandle);
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    /**
     * Returns the name to use in the missed call notification.
     */
    private String getNameForCall(Call call) {
        String handle = call.getHandle() == null ? null : call.getHandle().getSchemeSpecificPart();
        String name = call.getName();

        if (!TextUtils.isEmpty(handle)) {
            String formattedNumber = PhoneNumberUtils.formatNumber(handle,
                    getCurrentCountryIso(mContext));

            // The formatted number will be null if there was a problem formatting it, but we can
            // default to using the unformatted number instead (e.g. a SIP URI may not be able to
            // be formatted.
            if (!TextUtils.isEmpty(formattedNumber)) {
                handle = formattedNumber;
            }
        }

        if (!TextUtils.isEmpty(name) && TextUtils.isGraphic(name)) {
            return name;
        } else if (!TextUtils.isEmpty(handle)) {
            // A handle should always be displayed LTR using {@link BidiFormatter} regardless of the
            // content of the rest of the notification.
            // TODO: Does this apply to SIP addresses?
            BidiFormatter bidiFormatter = BidiFormatter.getInstance();
            return bidiFormatter.unicodeWrap(handle, TextDirectionHeuristics.LTR);
        } else {
            // Use "unknown" if the call is unidentifiable.
            return mContext.getString(R.string.unknown);
        }
    }

    /**
     * @return The ISO 3166-1 two letters country code of the country the user is in based on the
     *      network location.  If the network location does not exist, fall back to the locale
     *      setting.
     */
    private String getCurrentCountryIso(Context context) {
        // Without framework function calls, this seems to be the most accurate location service
        // we can rely on.
        final TelephonyManager telephonyManager =
                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        String countryIso = telephonyManager.getNetworkCountryIso().toUpperCase();

        if (countryIso == null) {
            countryIso = Locale.getDefault().getCountry();
            Log.w(this, "No CountryDetector; falling back to countryIso based on locale: "
                    + countryIso);
        }
        return countryIso;
    }

    /**
     * Creates a new pending intent that sends the user to the call log.
     *
     * @return The pending intent.
     */
    private PendingIntent createCallLogPendingIntent(UserHandle userHandle) {
        Intent intent = new Intent(Intent.ACTION_VIEW, null);
        intent.setType(Calls.CONTENT_TYPE);

        TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(mContext);
        taskStackBuilder.addNextIntent(intent);

        return taskStackBuilder.getPendingIntent(0, 0, null, userHandle);
    }

    /**
     * Creates an intent to be invoked when the missed call notification is cleared.
     */
    private PendingIntent createClearMissedCallsPendingIntent(UserHandle userHandle) {
        return createTelecomPendingIntent(
                TelecomBroadcastIntentProcessor.ACTION_CLEAR_MISSED_CALLS, null, userHandle);
    }

    /**
     * Creates an intent to be invoked when the user opts to "call back" from the missed call
     * notification.
     *
     * @param handle The handle to call back.
     */
    private PendingIntent createCallBackPendingIntent(Uri handle, UserHandle userHandle) {
        return createTelecomPendingIntent(
                TelecomBroadcastIntentProcessor.ACTION_CALL_BACK_FROM_NOTIFICATION, handle,
                userHandle);
    }

    /**
     * Creates an intent to be invoked when the user opts to "send sms" from the missed call
     * notification.
     */
    private PendingIntent createSendSmsFromNotificationPendingIntent(Uri handle,
            UserHandle userHandle) {
        return createTelecomPendingIntent(
                TelecomBroadcastIntentProcessor.ACTION_SEND_SMS_FROM_NOTIFICATION,
                Uri.fromParts(Constants.SCHEME_SMSTO, handle.getSchemeSpecificPart(), null),
                userHandle);
    }

    /**
     * Creates generic pending intent from the specified parameters to be received by
     * {@link TelecomBroadcastIntentProcessor}.
     *
     * @param action The intent action.
     * @param data The intent data.
     */
    private PendingIntent createTelecomPendingIntent(String action, Uri data,
            UserHandle userHandle) {
        Intent intent = new Intent(action, data, mContext, TelecomBroadcastReceiver.class);
        intent.putExtra(TelecomBroadcastIntentProcessor.EXTRA_USERHANDLE, userHandle);
        return PendingIntent.getBroadcast(mContext, 0, intent, 0);
    }

    /**
     * Configures a notification to emit the blinky notification light.
     */
    private void configureLedOnNotification(Notification notification) {
        notification.flags |= Notification.FLAG_SHOW_LIGHTS;
        notification.defaults |= Notification.DEFAULT_LIGHTS;
    }

    private boolean canRespondViaSms(Call call) {
        // Only allow respond-via-sms for "tel:" calls.
        return call.getHandle() != null &&
                PhoneAccount.SCHEME_TEL.equals(call.getHandle().getScheme());
    }

    /**
     * Adds the missed call notification on startup if there are unread missed calls.
     */
    @Override
    public void reloadFromDatabase(
            final TelecomSystem.SyncRoot lock,
            final CallsManager callsManager,
            final ContactsAsyncHelper contactsAsyncHelper,
            final CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
            final UserHandle userHandle) {
        Log.d(this, "reloadFromDatabase()...");

        // instantiate query handler
        AsyncQueryHandler queryHandler = new AsyncQueryHandler(mContext.getContentResolver()) {
            @Override
            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
                Log.d(MissedCallNotifierImpl.this, "onQueryComplete()...");
                if (cursor != null) {
                    try {
                        mMissedCallCounts.remove(userHandle);
                        while (cursor.moveToNext()) {
                            // Get data about the missed call from the cursor
                            final String handleString = cursor.getString(CALL_LOG_COLUMN_NUMBER);
                            final int presentation =
                                    cursor.getInt(CALL_LOG_COLUMN_NUMBER_PRESENTATION);
                            final long date = cursor.getLong(CALL_LOG_COLUMN_DATE);

                            final Uri handle;
                            if (presentation != Calls.PRESENTATION_ALLOWED
                                    || TextUtils.isEmpty(handleString)) {
                                handle = null;
                            } else {
                                handle = Uri.fromParts(PhoneNumberUtils.isUriNumber(handleString) ?
                                        PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL,
                                                handleString, null);
                            }

                            synchronized (lock) {

                                // Convert the data to a call object
                                Call call = new Call(Call.CALL_ID_UNKNOWN, mContext, callsManager,
                                        lock, null, contactsAsyncHelper,
                                        callerInfoAsyncQueryFactory, mPhoneNumberUtilsAdapter, null,
                                        null, null, null, Call.CALL_DIRECTION_INCOMING, false,
                                        false);
                                call.setDisconnectCause(
                                        new DisconnectCause(DisconnectCause.MISSED));
                                call.setState(CallState.DISCONNECTED, "throw away call");
                                call.setCreationTimeMillis(date);

                                // Listen for the update to the caller information before posting
                                // the notification so that we have the contact info and photo.
                                call.addListener(new Call.ListenerBase() {
                                    @Override
                                    public void onCallerInfoChanged(Call call) {
                                        call.removeListener(
                                                this);  // No longer need to listen to call
                                        // changes after the contact info
                                        // is retrieved.
                                        showMissedCallNotification(call, userHandle);
                                    }
                                });
                                // Set the handle here because that is what triggers the contact
                                // info query.
                                call.setHandle(handle, presentation);
                            }
                        }
                    } finally {
                        cursor.close();
                    }
                }
            }
        };

        // setup query spec, look for all Missed calls that are new.
        StringBuilder where = new StringBuilder("type=");
        where.append(Calls.MISSED_TYPE);
        where.append(" AND new=1");
        where.append(" AND is_read=0");

        Uri callsUri =
                ContentProvider.maybeAddUserId(Calls.CONTENT_URI, userHandle.getIdentifier());
        // start the query
        queryHandler.startQuery(0, null, callsUri, CALL_LOG_PROJECTION,
                where.toString(), null, Calls.DEFAULT_SORT_ORDER);
    }

    @Override
    public void setCurrentUserHandle(UserHandle currentUserHandle) {
        mCurrentUserHandle = currentUserHandle;
    }

    private Context getContextForUser(UserHandle user) {
        try {
            return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
        } catch (NameNotFoundException e) {
            // Default to mContext, not finding the package system is running as is unlikely.
            return mContext;
        }
    }
}
