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