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