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