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