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