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