1 /*
2  * Copyright (C) 2020 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.people.data;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.UserIdInt;
22 import android.annotation.WorkerThread;
23 import android.app.Notification;
24 import android.app.NotificationChannel;
25 import android.app.NotificationManager;
26 import android.app.Person;
27 import android.app.prediction.AppTarget;
28 import android.app.prediction.AppTargetEvent;
29 import android.app.usage.UsageEvents;
30 import android.content.BroadcastReceiver;
31 import android.content.ComponentName;
32 import android.content.ContentResolver;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.IntentFilter;
36 import android.content.pm.LauncherApps;
37 import android.content.pm.LauncherApps.ShortcutQuery;
38 import android.content.pm.PackageManager;
39 import android.content.pm.PackageManagerInternal;
40 import android.content.pm.ShortcutInfo;
41 import android.content.pm.ShortcutManager.ShareShortcutInfo;
42 import android.content.pm.ShortcutServiceInternal;
43 import android.content.pm.UserInfo;
44 import android.database.ContentObserver;
45 import android.net.Uri;
46 import android.os.CancellationSignal;
47 import android.os.Handler;
48 import android.os.Process;
49 import android.os.RemoteException;
50 import android.os.UserHandle;
51 import android.os.UserManager;
52 import android.provider.CallLog;
53 import android.provider.ContactsContract.Contacts;
54 import android.provider.Telephony.MmsSms;
55 import android.service.notification.NotificationListenerService;
56 import android.service.notification.StatusBarNotification;
57 import android.telecom.TelecomManager;
58 import android.text.format.DateUtils;
59 import android.util.ArrayMap;
60 import android.util.ArraySet;
61 import android.util.Pair;
62 import android.util.Slog;
63 import android.util.SparseArray;
64 
65 import com.android.internal.annotations.GuardedBy;
66 import com.android.internal.annotations.VisibleForTesting;
67 import com.android.internal.app.ChooserActivity;
68 import com.android.internal.content.PackageMonitor;
69 import com.android.internal.os.BackgroundThread;
70 import com.android.internal.telephony.SmsApplication;
71 import com.android.server.LocalServices;
72 import com.android.server.notification.NotificationManagerInternal;
73 import com.android.server.notification.ShortcutHelper;
74 
75 import java.util.ArrayList;
76 import java.util.Collections;
77 import java.util.List;
78 import java.util.Map;
79 import java.util.Set;
80 import java.util.concurrent.Executor;
81 import java.util.concurrent.Executors;
82 import java.util.concurrent.ScheduledExecutorService;
83 import java.util.concurrent.ScheduledFuture;
84 import java.util.concurrent.TimeUnit;
85 import java.util.function.BiConsumer;
86 import java.util.function.Consumer;
87 import java.util.function.Function;
88 
89 /**
90  * A class manages the lifecycle of the conversations and associated data, and exposes the methods
91  * to access the data in People Service and other system services.
92  */
93 public class DataManager {
94 
95     private static final String TAG = "DataManager";
96 
97     private static final long QUERY_EVENTS_MAX_AGE_MS = 5L * DateUtils.MINUTE_IN_MILLIS;
98     private static final long USAGE_STATS_QUERY_INTERVAL_SEC = 120L;
99 
100     private final Context mContext;
101     private final Injector mInjector;
102     private final ScheduledExecutorService mScheduledExecutor;
103     private final Object mLock = new Object();
104 
105     private final SparseArray<UserData> mUserDataArray = new SparseArray<>();
106     private final SparseArray<BroadcastReceiver> mBroadcastReceivers = new SparseArray<>();
107     private final SparseArray<ContentObserver> mContactsContentObservers = new SparseArray<>();
108     private final SparseArray<ScheduledFuture<?>> mUsageStatsQueryFutures = new SparseArray<>();
109     private final SparseArray<NotificationListener> mNotificationListeners = new SparseArray<>();
110     private final SparseArray<PackageMonitor> mPackageMonitors = new SparseArray<>();
111     private ContentObserver mCallLogContentObserver;
112     private ContentObserver mMmsSmsContentObserver;
113 
114     private ShortcutServiceInternal mShortcutServiceInternal;
115     private PackageManagerInternal mPackageManagerInternal;
116     private NotificationManagerInternal mNotificationManagerInternal;
117     private UserManager mUserManager;
118 
DataManager(Context context)119     public DataManager(Context context) {
120         this(context, new Injector());
121     }
122 
123     @VisibleForTesting
DataManager(Context context, Injector injector)124     DataManager(Context context, Injector injector) {
125         mContext = context;
126         mInjector = injector;
127         mScheduledExecutor = mInjector.createScheduledExecutor();
128     }
129 
130     /** Initialization. Called when the system services are up running. */
initialize()131     public void initialize() {
132         mShortcutServiceInternal = LocalServices.getService(ShortcutServiceInternal.class);
133         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
134         mNotificationManagerInternal = LocalServices.getService(NotificationManagerInternal.class);
135         mUserManager = mContext.getSystemService(UserManager.class);
136 
137         mShortcutServiceInternal.addShortcutChangeCallback(new ShortcutServiceCallback());
138 
139         IntentFilter shutdownIntentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
140         BroadcastReceiver shutdownBroadcastReceiver = new ShutdownBroadcastReceiver();
141         mContext.registerReceiver(shutdownBroadcastReceiver, shutdownIntentFilter);
142     }
143 
144     /** This method is called when a user is unlocked. */
onUserUnlocked(int userId)145     public void onUserUnlocked(int userId) {
146         synchronized (mLock) {
147             UserData userData = mUserDataArray.get(userId);
148             if (userData == null) {
149                 userData = new UserData(userId, mScheduledExecutor);
150                 mUserDataArray.put(userId, userData);
151             }
152             userData.setUserUnlocked();
153         }
154         mScheduledExecutor.execute(() -> setupUser(userId));
155     }
156 
157     /** This method is called when a user is stopping. */
onUserStopping(int userId)158     public void onUserStopping(int userId) {
159         synchronized (mLock) {
160             UserData userData = mUserDataArray.get(userId);
161             if (userData != null) {
162                 userData.setUserStopped();
163             }
164         }
165         mScheduledExecutor.execute(() -> cleanupUser(userId));
166     }
167 
168     /**
169      * Iterates through all the {@link PackageData}s owned by the unlocked users who are in the
170      * same profile group as the calling user.
171      */
forPackagesInProfile(@serIdInt int callingUserId, Consumer<PackageData> consumer)172     void forPackagesInProfile(@UserIdInt int callingUserId, Consumer<PackageData> consumer) {
173         List<UserInfo> users = mUserManager.getEnabledProfiles(callingUserId);
174         for (UserInfo userInfo : users) {
175             UserData userData = getUnlockedUserData(userInfo.id);
176             if (userData != null) {
177                 userData.forAllPackages(consumer);
178             }
179         }
180     }
181 
182     /** Gets the {@link PackageData} for the given package and user. */
183     @Nullable
getPackage(@onNull String packageName, @UserIdInt int userId)184     public PackageData getPackage(@NonNull String packageName, @UserIdInt int userId) {
185         UserData userData = getUnlockedUserData(userId);
186         return userData != null ? userData.getPackageData(packageName) : null;
187     }
188 
189     /** Gets the {@link ShortcutInfo} for the given shortcut ID. */
190     @Nullable
getShortcut(@onNull String packageName, @UserIdInt int userId, @NonNull String shortcutId)191     public ShortcutInfo getShortcut(@NonNull String packageName, @UserIdInt int userId,
192             @NonNull String shortcutId) {
193         List<ShortcutInfo> shortcuts = getShortcuts(packageName, userId,
194                 Collections.singletonList(shortcutId));
195         if (shortcuts != null && !shortcuts.isEmpty()) {
196             return shortcuts.get(0);
197         }
198         return null;
199     }
200 
201     /**
202      * Gets the {@link ShareShortcutInfo}s from all packages owned by the calling user that match
203      * the specified {@link IntentFilter}.
204      */
getShareShortcuts(@onNull IntentFilter intentFilter, @UserIdInt int callingUserId)205     public List<ShareShortcutInfo> getShareShortcuts(@NonNull IntentFilter intentFilter,
206             @UserIdInt int callingUserId) {
207         return mShortcutServiceInternal.getShareTargets(
208                 mContext.getPackageName(), intentFilter, callingUserId);
209     }
210 
211     /** Reports the sharing related {@link AppTargetEvent} from App Prediction Manager. */
reportShareTargetEvent(@onNull AppTargetEvent event, @NonNull IntentFilter intentFilter)212     public void reportShareTargetEvent(@NonNull AppTargetEvent event,
213             @NonNull IntentFilter intentFilter) {
214         AppTarget appTarget = event.getTarget();
215         if (appTarget == null || event.getAction() != AppTargetEvent.ACTION_LAUNCH) {
216             return;
217         }
218         UserData userData = getUnlockedUserData(appTarget.getUser().getIdentifier());
219         if (userData == null) {
220             return;
221         }
222         PackageData packageData = userData.getOrCreatePackageData(appTarget.getPackageName());
223         @Event.EventType int eventType = mimeTypeToShareEventType(intentFilter.getDataType(0));
224         EventHistoryImpl eventHistory;
225         if (ChooserActivity.LAUNCH_LOCATION_DIRECT_SHARE.equals(event.getLaunchLocation())) {
226             // Direct share event
227             if (appTarget.getShortcutInfo() == null) {
228                 return;
229             }
230             String shortcutId = appTarget.getShortcutInfo().getId();
231             // Skip storing chooserTargets sharing events
232             if (ChooserActivity.CHOOSER_TARGET.equals(shortcutId)) {
233                 return;
234             }
235             if (packageData.getConversationStore().getConversation(shortcutId) == null) {
236                 addOrUpdateConversationInfo(appTarget.getShortcutInfo());
237             }
238             eventHistory = packageData.getEventStore().getOrCreateEventHistory(
239                     EventStore.CATEGORY_SHORTCUT_BASED, shortcutId);
240         } else {
241             // App share event
242             eventHistory = packageData.getEventStore().getOrCreateEventHistory(
243                     EventStore.CATEGORY_CLASS_BASED, appTarget.getClassName());
244         }
245         eventHistory.addEvent(new Event(System.currentTimeMillis(), eventType));
246     }
247 
248     /**
249      * Queries events for moving app to foreground between {@code startTime} and {@code endTime}.
250      */
251     @NonNull
queryAppMovingToForegroundEvents(@serIdInt int callingUserId, long startTime, long endTime)252     public List<UsageEvents.Event> queryAppMovingToForegroundEvents(@UserIdInt int callingUserId,
253             long startTime, long endTime) {
254         return UsageStatsQueryHelper.queryAppMovingToForegroundEvents(callingUserId, startTime,
255                 endTime);
256     }
257 
258     /**
259      * Queries usage stats of apps within {@code packageNameFilter} between {@code startTime} and
260      * {@code endTime}.
261      *
262      * @return a map which keys are package names and values are {@link AppUsageStatsData}.
263      */
264     @NonNull
queryAppUsageStats( @serIdInt int callingUserId, long startTime, long endTime, Set<String> packageNameFilter)265     public Map<String, AppUsageStatsData> queryAppUsageStats(
266             @UserIdInt int callingUserId, long startTime,
267             long endTime, Set<String> packageNameFilter) {
268         return UsageStatsQueryHelper.queryAppUsageStats(callingUserId, startTime, endTime,
269                 packageNameFilter);
270     }
271 
272     /** Prunes the data for the specified user. */
pruneDataForUser(@serIdInt int userId, @NonNull CancellationSignal signal)273     public void pruneDataForUser(@UserIdInt int userId, @NonNull CancellationSignal signal) {
274         UserData userData = getUnlockedUserData(userId);
275         if (userData == null || signal.isCanceled()) {
276             return;
277         }
278         pruneUninstalledPackageData(userData);
279 
280         final NotificationListener notificationListener = mNotificationListeners.get(userId);
281         userData.forAllPackages(packageData -> {
282             if (signal.isCanceled()) {
283                 return;
284             }
285             packageData.getEventStore().pruneOldEvents();
286             if (!packageData.isDefaultDialer()) {
287                 packageData.getEventStore().deleteEventHistories(EventStore.CATEGORY_CALL);
288             }
289             if (!packageData.isDefaultSmsApp()) {
290                 packageData.getEventStore().deleteEventHistories(EventStore.CATEGORY_SMS);
291             }
292             packageData.pruneOrphanEvents();
293             if (notificationListener != null) {
294                 String packageName = packageData.getPackageName();
295                 packageData.forAllConversations(conversationInfo -> {
296                     if (conversationInfo.isShortcutCachedForNotification()
297                             && conversationInfo.getNotificationChannelId() == null
298                             && !notificationListener.hasActiveNotifications(
299                                     packageName, conversationInfo.getShortcutId())) {
300                         mShortcutServiceInternal.uncacheShortcuts(userId,
301                                 mContext.getPackageName(), packageName,
302                                 Collections.singletonList(conversationInfo.getShortcutId()),
303                                 userId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
304                     }
305                 });
306             }
307         });
308     }
309 
310     /** Retrieves a backup payload blob for specified user id. */
311     @Nullable
getBackupPayload(@serIdInt int userId)312     public byte[] getBackupPayload(@UserIdInt int userId) {
313         UserData userData = getUnlockedUserData(userId);
314         if (userData == null) {
315             return null;
316         }
317         return userData.getBackupPayload();
318     }
319 
320     /** Attempts to restore data for the specified user id. */
restore(@serIdInt int userId, @NonNull byte[] payload)321     public void restore(@UserIdInt int userId, @NonNull byte[] payload) {
322         UserData userData = getUnlockedUserData(userId);
323         if (userData == null) {
324             return;
325         }
326         userData.restore(payload);
327     }
328 
setupUser(@serIdInt int userId)329     private void setupUser(@UserIdInt int userId) {
330         synchronized (mLock) {
331             UserData userData = getUnlockedUserData(userId);
332             if (userData == null) {
333                 return;
334             }
335             userData.loadUserData();
336 
337             updateDefaultDialer(userData);
338             updateDefaultSmsApp(userData);
339 
340             ScheduledFuture<?> scheduledFuture = mScheduledExecutor.scheduleAtFixedRate(
341                     new UsageStatsQueryRunnable(userId), 1L, USAGE_STATS_QUERY_INTERVAL_SEC,
342                     TimeUnit.SECONDS);
343             mUsageStatsQueryFutures.put(userId, scheduledFuture);
344 
345             IntentFilter intentFilter = new IntentFilter();
346             intentFilter.addAction(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED);
347             intentFilter.addAction(SmsApplication.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL);
348             BroadcastReceiver broadcastReceiver = new PerUserBroadcastReceiver(userId);
349             mBroadcastReceivers.put(userId, broadcastReceiver);
350             mContext.registerReceiverAsUser(
351                     broadcastReceiver, UserHandle.of(userId), intentFilter, null, null);
352 
353             ContentObserver contactsContentObserver = new ContactsContentObserver(
354                     BackgroundThread.getHandler());
355             mContactsContentObservers.put(userId, contactsContentObserver);
356             mContext.getContentResolver().registerContentObserver(
357                     Contacts.CONTENT_URI, /* notifyForDescendants= */ true,
358                     contactsContentObserver, userId);
359 
360             NotificationListener notificationListener = new NotificationListener(userId);
361             mNotificationListeners.put(userId, notificationListener);
362             try {
363                 notificationListener.registerAsSystemService(mContext,
364                         new ComponentName(mContext, getClass()), userId);
365             } catch (RemoteException e) {
366                 // Should never occur for local calls.
367             }
368 
369             PackageMonitor packageMonitor = new PerUserPackageMonitor();
370             packageMonitor.register(mContext, null, UserHandle.of(userId), true);
371             mPackageMonitors.put(userId, packageMonitor);
372 
373             if (userId == UserHandle.USER_SYSTEM) {
374                 // The call log and MMS/SMS messages are shared across user profiles. So only need
375                 // to register the content observers once for the primary user.
376                 mCallLogContentObserver = new CallLogContentObserver(BackgroundThread.getHandler());
377                 mContext.getContentResolver().registerContentObserver(
378                         CallLog.CONTENT_URI, /* notifyForDescendants= */ true,
379                         mCallLogContentObserver, UserHandle.USER_SYSTEM);
380 
381                 mMmsSmsContentObserver = new MmsSmsContentObserver(BackgroundThread.getHandler());
382                 mContext.getContentResolver().registerContentObserver(
383                         MmsSms.CONTENT_URI, /* notifyForDescendants= */ false,
384                         mMmsSmsContentObserver, UserHandle.USER_SYSTEM);
385             }
386 
387             DataMaintenanceService.scheduleJob(mContext, userId);
388         }
389     }
390 
cleanupUser(@serIdInt int userId)391     private void cleanupUser(@UserIdInt int userId) {
392         synchronized (mLock) {
393             UserData userData = mUserDataArray.get(userId);
394             if (userData == null || userData.isUnlocked()) {
395                 return;
396             }
397             ContentResolver contentResolver = mContext.getContentResolver();
398             if (mUsageStatsQueryFutures.indexOfKey(userId) >= 0) {
399                 mUsageStatsQueryFutures.get(userId).cancel(true);
400             }
401             if (mBroadcastReceivers.indexOfKey(userId) >= 0) {
402                 mContext.unregisterReceiver(mBroadcastReceivers.get(userId));
403             }
404             if (mContactsContentObservers.indexOfKey(userId) >= 0) {
405                 contentResolver.unregisterContentObserver(mContactsContentObservers.get(userId));
406             }
407             if (mNotificationListeners.indexOfKey(userId) >= 0) {
408                 try {
409                     mNotificationListeners.get(userId).unregisterAsSystemService();
410                 } catch (RemoteException e) {
411                     // Should never occur for local calls.
412                 }
413             }
414             if (mPackageMonitors.indexOfKey(userId) >= 0) {
415                 mPackageMonitors.get(userId).unregister();
416             }
417             if (userId == UserHandle.USER_SYSTEM) {
418                 if (mCallLogContentObserver != null) {
419                     contentResolver.unregisterContentObserver(mCallLogContentObserver);
420                     mCallLogContentObserver = null;
421                 }
422                 if (mMmsSmsContentObserver != null) {
423                     contentResolver.unregisterContentObserver(mMmsSmsContentObserver);
424                     mCallLogContentObserver = null;
425                 }
426             }
427 
428             DataMaintenanceService.cancelJob(mContext, userId);
429         }
430     }
431 
432     /**
433      * Converts {@code mimeType} to {@link Event.EventType}.
434      */
mimeTypeToShareEventType(String mimeType)435     public int mimeTypeToShareEventType(String mimeType) {
436         if (mimeType == null) {
437             return Event.TYPE_SHARE_OTHER;
438         }
439         if (mimeType.startsWith("text/")) {
440             return Event.TYPE_SHARE_TEXT;
441         } else if (mimeType.startsWith("image/")) {
442             return Event.TYPE_SHARE_IMAGE;
443         } else if (mimeType.startsWith("video/")) {
444             return Event.TYPE_SHARE_VIDEO;
445         }
446         return Event.TYPE_SHARE_OTHER;
447     }
448 
pruneUninstalledPackageData(@onNull UserData userData)449     private void pruneUninstalledPackageData(@NonNull UserData userData) {
450         Set<String> installApps = new ArraySet<>();
451         mPackageManagerInternal.forEachInstalledPackage(
452                 pkg -> installApps.add(pkg.getPackageName()), userData.getUserId());
453         List<String> packagesToDelete = new ArrayList<>();
454         userData.forAllPackages(packageData -> {
455             if (!installApps.contains(packageData.getPackageName())) {
456                 packagesToDelete.add(packageData.getPackageName());
457             }
458         });
459         for (String packageName : packagesToDelete) {
460             userData.deletePackageData(packageName);
461         }
462     }
463 
464     /** Gets a list of {@link ShortcutInfo}s with the given shortcut IDs. */
getShortcuts( @onNull String packageName, @UserIdInt int userId, @Nullable List<String> shortcutIds)465     private List<ShortcutInfo> getShortcuts(
466             @NonNull String packageName, @UserIdInt int userId,
467             @Nullable List<String> shortcutIds) {
468         @ShortcutQuery.QueryFlags int queryFlags = ShortcutQuery.FLAG_MATCH_DYNAMIC
469                 | ShortcutQuery.FLAG_MATCH_PINNED | ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER;
470         return mShortcutServiceInternal.getShortcuts(
471                 UserHandle.USER_SYSTEM, mContext.getPackageName(),
472                 /*changedSince=*/ 0, packageName, shortcutIds, /*locusIds=*/ null,
473                 /*componentName=*/ null, queryFlags, userId, Process.myPid(), Process.myUid());
474     }
475 
forAllUnlockedUsers(Consumer<UserData> consumer)476     private void forAllUnlockedUsers(Consumer<UserData> consumer) {
477         for (int i = 0; i < mUserDataArray.size(); i++) {
478             int userId = mUserDataArray.keyAt(i);
479             UserData userData = mUserDataArray.get(userId);
480             if (userData.isUnlocked()) {
481                 consumer.accept(userData);
482             }
483         }
484     }
485 
486     @Nullable
getUnlockedUserData(int userId)487     private UserData getUnlockedUserData(int userId) {
488         UserData userData = mUserDataArray.get(userId);
489         return userData != null && userData.isUnlocked() ? userData : null;
490     }
491 
updateDefaultDialer(@onNull UserData userData)492     private void updateDefaultDialer(@NonNull UserData userData) {
493         TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
494         String defaultDialer = telecomManager != null
495                 ? telecomManager.getDefaultDialerPackage(
496                         new UserHandle(userData.getUserId())) : null;
497         userData.setDefaultDialer(defaultDialer);
498     }
499 
updateDefaultSmsApp(@onNull UserData userData)500     private void updateDefaultSmsApp(@NonNull UserData userData) {
501         ComponentName component = SmsApplication.getDefaultSmsApplicationAsUser(
502                 mContext, /* updateIfNeeded= */ false, userData.getUserId());
503         String defaultSmsApp = component != null ? component.getPackageName() : null;
504         userData.setDefaultSmsApp(defaultSmsApp);
505     }
506 
507     @Nullable
getPackageIfConversationExists(StatusBarNotification sbn, Consumer<ConversationInfo> conversationConsumer)508     private PackageData getPackageIfConversationExists(StatusBarNotification sbn,
509             Consumer<ConversationInfo> conversationConsumer) {
510         Notification notification = sbn.getNotification();
511         String shortcutId = notification.getShortcutId();
512         if (shortcutId == null) {
513             return null;
514         }
515         PackageData packageData = getPackage(sbn.getPackageName(),
516                 sbn.getUser().getIdentifier());
517         if (packageData == null) {
518             return null;
519         }
520         ConversationInfo conversationInfo =
521                 packageData.getConversationStore().getConversation(shortcutId);
522         if (conversationInfo == null) {
523             return null;
524         }
525         conversationConsumer.accept(conversationInfo);
526         return packageData;
527     }
528 
529     @VisibleForTesting
530     @WorkerThread
addOrUpdateConversationInfo(@onNull ShortcutInfo shortcutInfo)531     void addOrUpdateConversationInfo(@NonNull ShortcutInfo shortcutInfo) {
532         UserData userData = getUnlockedUserData(shortcutInfo.getUserId());
533         if (userData == null) {
534             return;
535         }
536         PackageData packageData = userData.getOrCreatePackageData(shortcutInfo.getPackage());
537         ConversationStore conversationStore = packageData.getConversationStore();
538         ConversationInfo oldConversationInfo =
539                 conversationStore.getConversation(shortcutInfo.getId());
540         ConversationInfo.Builder builder = oldConversationInfo != null
541                 ? new ConversationInfo.Builder(oldConversationInfo)
542                 : new ConversationInfo.Builder();
543 
544         builder.setShortcutId(shortcutInfo.getId());
545         builder.setLocusId(shortcutInfo.getLocusId());
546         builder.setShortcutFlags(shortcutInfo.getFlags());
547         builder.setContactUri(null);
548         builder.setContactPhoneNumber(null);
549         builder.setContactStarred(false);
550 
551         if (shortcutInfo.getPersons() != null && shortcutInfo.getPersons().length != 0) {
552             Person person = shortcutInfo.getPersons()[0];
553             builder.setPersonImportant(person.isImportant());
554             builder.setPersonBot(person.isBot());
555             String contactUri = person.getUri();
556             if (contactUri != null) {
557                 ContactsQueryHelper helper = mInjector.createContactsQueryHelper(mContext);
558                 if (helper.query(contactUri)) {
559                     builder.setContactUri(helper.getContactUri());
560                     builder.setContactStarred(helper.isStarred());
561                     builder.setContactPhoneNumber(helper.getPhoneNumber());
562                 }
563             }
564         }
565         conversationStore.addOrUpdate(builder.build());
566     }
567 
568     @VisibleForTesting
getContactsContentObserverForTesting(@serIdInt int userId)569     ContentObserver getContactsContentObserverForTesting(@UserIdInt int userId) {
570         return mContactsContentObservers.get(userId);
571     }
572 
573     @VisibleForTesting
getCallLogContentObserverForTesting()574     ContentObserver getCallLogContentObserverForTesting() {
575         return mCallLogContentObserver;
576     }
577 
578     @VisibleForTesting
getMmsSmsContentObserverForTesting()579     ContentObserver getMmsSmsContentObserverForTesting() {
580         return mMmsSmsContentObserver;
581     }
582 
583     @VisibleForTesting
getNotificationListenerServiceForTesting(@serIdInt int userId)584     NotificationListenerService getNotificationListenerServiceForTesting(@UserIdInt int userId) {
585         return mNotificationListeners.get(userId);
586     }
587 
588     @VisibleForTesting
getPackageMonitorForTesting(@serIdInt int userId)589     PackageMonitor getPackageMonitorForTesting(@UserIdInt int userId) {
590         return mPackageMonitors.get(userId);
591     }
592 
593     @VisibleForTesting
getUserDataForTesting(@serIdInt int userId)594     UserData getUserDataForTesting(@UserIdInt int userId) {
595         return mUserDataArray.get(userId);
596     }
597 
598     /** Observer that observes the changes in the Contacts database. */
599     private class ContactsContentObserver extends ContentObserver {
600 
601         private long mLastUpdatedTimestamp;
602 
ContactsContentObserver(Handler handler)603         private ContactsContentObserver(Handler handler) {
604             super(handler);
605             mLastUpdatedTimestamp = System.currentTimeMillis();
606         }
607 
608         @Override
onChange(boolean selfChange, Uri uri, @UserIdInt int userId)609         public void onChange(boolean selfChange, Uri uri, @UserIdInt int userId) {
610             ContactsQueryHelper helper = mInjector.createContactsQueryHelper(mContext);
611             if (!helper.querySince(mLastUpdatedTimestamp)) {
612                 return;
613             }
614             Uri contactUri = helper.getContactUri();
615 
616             final ConversationSelector conversationSelector = new ConversationSelector();
617             UserData userData = getUnlockedUserData(userId);
618             if (userData == null) {
619                 return;
620             }
621             userData.forAllPackages(packageData -> {
622                 ConversationInfo ci =
623                         packageData.getConversationStore().getConversationByContactUri(contactUri);
624                 if (ci != null) {
625                     conversationSelector.mConversationStore =
626                             packageData.getConversationStore();
627                     conversationSelector.mConversationInfo = ci;
628                 }
629             });
630             if (conversationSelector.mConversationInfo == null) {
631                 return;
632             }
633 
634             ConversationInfo.Builder builder =
635                     new ConversationInfo.Builder(conversationSelector.mConversationInfo);
636             builder.setContactStarred(helper.isStarred());
637             builder.setContactPhoneNumber(helper.getPhoneNumber());
638             conversationSelector.mConversationStore.addOrUpdate(builder.build());
639             mLastUpdatedTimestamp = helper.getLastUpdatedTimestamp();
640         }
641 
642         private class ConversationSelector {
643             private ConversationStore mConversationStore = null;
644             private ConversationInfo mConversationInfo = null;
645         }
646     }
647 
648     /** Observer that observes the changes in the call log database. */
649     private class CallLogContentObserver extends ContentObserver implements
650             BiConsumer<String, Event> {
651 
652         private final CallLogQueryHelper mCallLogQueryHelper;
653         private long mLastCallTimestamp;
654 
CallLogContentObserver(Handler handler)655         private CallLogContentObserver(Handler handler) {
656             super(handler);
657             mCallLogQueryHelper = mInjector.createCallLogQueryHelper(mContext, this);
658             mLastCallTimestamp = System.currentTimeMillis() - QUERY_EVENTS_MAX_AGE_MS;
659         }
660 
661         @Override
onChange(boolean selfChange)662         public void onChange(boolean selfChange) {
663             if (mCallLogQueryHelper.querySince(mLastCallTimestamp)) {
664                 mLastCallTimestamp = mCallLogQueryHelper.getLastCallTimestamp();
665             }
666         }
667 
668         @Override
accept(String phoneNumber, Event event)669         public void accept(String phoneNumber, Event event) {
670             forAllUnlockedUsers(userData -> {
671                 PackageData defaultDialer = userData.getDefaultDialer();
672                 if (defaultDialer == null) {
673                     return;
674                 }
675                 ConversationStore conversationStore = defaultDialer.getConversationStore();
676                 if (conversationStore.getConversationByPhoneNumber(phoneNumber) == null) {
677                     return;
678                 }
679                 EventStore eventStore = defaultDialer.getEventStore();
680                 eventStore.getOrCreateEventHistory(
681                         EventStore.CATEGORY_CALL, phoneNumber).addEvent(event);
682             });
683         }
684     }
685 
686     /** Observer that observes the changes in the MMS & SMS database. */
687     private class MmsSmsContentObserver extends ContentObserver implements
688             BiConsumer<String, Event> {
689 
690         private final MmsQueryHelper mMmsQueryHelper;
691         private long mLastMmsTimestamp;
692 
693         private final SmsQueryHelper mSmsQueryHelper;
694         private long mLastSmsTimestamp;
695 
MmsSmsContentObserver(Handler handler)696         private MmsSmsContentObserver(Handler handler) {
697             super(handler);
698             mMmsQueryHelper = mInjector.createMmsQueryHelper(mContext, this);
699             mSmsQueryHelper = mInjector.createSmsQueryHelper(mContext, this);
700             mLastSmsTimestamp = mLastMmsTimestamp =
701                     System.currentTimeMillis() - QUERY_EVENTS_MAX_AGE_MS;
702         }
703 
704         @Override
onChange(boolean selfChange)705         public void onChange(boolean selfChange) {
706             if (mMmsQueryHelper.querySince(mLastMmsTimestamp)) {
707                 mLastMmsTimestamp = mMmsQueryHelper.getLastMessageTimestamp();
708             }
709             if (mSmsQueryHelper.querySince(mLastSmsTimestamp)) {
710                 mLastSmsTimestamp = mSmsQueryHelper.getLastMessageTimestamp();
711             }
712         }
713 
714         @Override
accept(String phoneNumber, Event event)715         public void accept(String phoneNumber, Event event) {
716             forAllUnlockedUsers(userData -> {
717                 PackageData defaultSmsApp = userData.getDefaultSmsApp();
718                 if (defaultSmsApp == null) {
719                     return;
720                 }
721                 ConversationStore conversationStore = defaultSmsApp.getConversationStore();
722                 if (conversationStore.getConversationByPhoneNumber(phoneNumber) == null) {
723                     return;
724                 }
725                 EventStore eventStore = defaultSmsApp.getEventStore();
726                 eventStore.getOrCreateEventHistory(
727                         EventStore.CATEGORY_SMS, phoneNumber).addEvent(event);
728             });
729         }
730     }
731 
732     /** Listener for the shortcut data changes. */
733     private class ShortcutServiceCallback implements LauncherApps.ShortcutChangeCallback {
734 
735         @Override
onShortcutsAddedOrUpdated(@onNull String packageName, @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user)736         public void onShortcutsAddedOrUpdated(@NonNull String packageName,
737                 @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
738             mInjector.getBackgroundExecutor().execute(() -> {
739                 for (ShortcutInfo shortcut : shortcuts) {
740                     if (ShortcutHelper.isConversationShortcut(
741                             shortcut, mShortcutServiceInternal, user.getIdentifier())) {
742                         addOrUpdateConversationInfo(shortcut);
743                     }
744                 }
745             });
746         }
747 
748         @Override
onShortcutsRemoved(@onNull String packageName, @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user)749         public void onShortcutsRemoved(@NonNull String packageName,
750                 @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
751             mInjector.getBackgroundExecutor().execute(() -> {
752                 int uid = Process.INVALID_UID;
753                 try {
754                     uid = mContext.getPackageManager().getPackageUidAsUser(
755                             packageName, user.getIdentifier());
756                 } catch (PackageManager.NameNotFoundException e) {
757                     Slog.e(TAG, "Package not found: " + packageName, e);
758                 }
759                 PackageData packageData = getPackage(packageName, user.getIdentifier());
760                 for (ShortcutInfo shortcutInfo : shortcuts) {
761                     if (packageData != null) {
762                         packageData.deleteDataForConversation(shortcutInfo.getId());
763                     }
764                     if (uid != Process.INVALID_UID) {
765                         mNotificationManagerInternal.onConversationRemoved(
766                                 shortcutInfo.getPackage(), uid, shortcutInfo.getId());
767                     }
768                 }
769             });
770         }
771     }
772 
773     /** Listener for the notifications and their settings changes. */
774     private class NotificationListener extends NotificationListenerService {
775 
776         private final int mUserId;
777 
778         // Conversation package name + shortcut ID -> Number of active notifications
779         @GuardedBy("this")
780         private final Map<Pair<String, String>, Integer> mActiveNotifCounts = new ArrayMap<>();
781 
NotificationListener(int userId)782         private NotificationListener(int userId) {
783             mUserId = userId;
784         }
785 
786         @Override
onNotificationPosted(StatusBarNotification sbn)787         public void onNotificationPosted(StatusBarNotification sbn) {
788             if (sbn.getUser().getIdentifier() != mUserId) {
789                 return;
790             }
791             String shortcutId = sbn.getNotification().getShortcutId();
792             PackageData packageData = getPackageIfConversationExists(sbn, conversationInfo -> {
793                 synchronized (this) {
794                     mActiveNotifCounts.merge(
795                             Pair.create(sbn.getPackageName(), shortcutId), 1, Integer::sum);
796                 }
797             });
798 
799             if (packageData != null) {
800                 EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory(
801                         EventStore.CATEGORY_SHORTCUT_BASED, shortcutId);
802                 eventHistory.addEvent(new Event(sbn.getPostTime(), Event.TYPE_NOTIFICATION_POSTED));
803             }
804         }
805 
806         @Override
onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap, int reason)807         public synchronized void onNotificationRemoved(StatusBarNotification sbn,
808                 RankingMap rankingMap, int reason) {
809             if (sbn.getUser().getIdentifier() != mUserId) {
810                 return;
811             }
812             String shortcutId = sbn.getNotification().getShortcutId();
813             PackageData packageData = getPackageIfConversationExists(sbn, conversationInfo -> {
814                 Pair<String, String> conversationKey =
815                         Pair.create(sbn.getPackageName(), shortcutId);
816                 synchronized (this) {
817                     int count = mActiveNotifCounts.getOrDefault(conversationKey, 0) - 1;
818                     if (count <= 0) {
819                         mActiveNotifCounts.remove(conversationKey);
820                         // The shortcut was cached by Notification Manager synchronously when the
821                         // associated notification was posted. Uncache it here when all the
822                         // associated notifications are removed.
823                         if (conversationInfo.isShortcutCachedForNotification()
824                                 && conversationInfo.getNotificationChannelId() == null) {
825                             mShortcutServiceInternal.uncacheShortcuts(mUserId,
826                                     mContext.getPackageName(), sbn.getPackageName(),
827                                     Collections.singletonList(conversationInfo.getShortcutId()),
828                                     mUserId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
829                         }
830                     } else {
831                         mActiveNotifCounts.put(conversationKey, count);
832                     }
833                 }
834             });
835 
836             if (reason != REASON_CLICK || packageData == null) {
837                 return;
838             }
839             EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory(
840                     EventStore.CATEGORY_SHORTCUT_BASED, shortcutId);
841             long currentTime = System.currentTimeMillis();
842             eventHistory.addEvent(new Event(currentTime, Event.TYPE_NOTIFICATION_OPENED));
843         }
844 
845         @Override
onNotificationChannelModified(String pkg, UserHandle user, NotificationChannel channel, int modificationType)846         public void onNotificationChannelModified(String pkg, UserHandle user,
847                 NotificationChannel channel, int modificationType) {
848             if (user.getIdentifier() != mUserId) {
849                 return;
850             }
851             PackageData packageData = getPackage(pkg, user.getIdentifier());
852             String shortcutId = channel.getConversationId();
853             if (packageData == null || shortcutId == null) {
854                 return;
855             }
856             ConversationStore conversationStore = packageData.getConversationStore();
857             ConversationInfo conversationInfo = conversationStore.getConversation(shortcutId);
858             if (conversationInfo == null) {
859                 return;
860             }
861             ConversationInfo.Builder builder = new ConversationInfo.Builder(conversationInfo);
862             switch (modificationType) {
863                 case NOTIFICATION_CHANNEL_OR_GROUP_ADDED:
864                 case NOTIFICATION_CHANNEL_OR_GROUP_UPDATED:
865                     builder.setNotificationChannelId(channel.getId());
866                     builder.setImportant(channel.isImportantConversation());
867                     builder.setDemoted(channel.isDemoted());
868                     builder.setNotificationSilenced(
869                             channel.getImportance() <= NotificationManager.IMPORTANCE_LOW);
870                     builder.setBubbled(channel.canBubble());
871                     break;
872                 case NOTIFICATION_CHANNEL_OR_GROUP_DELETED:
873                     // If the notification channel is deleted, revert all the notification settings
874                     // to the default value.
875                     builder.setNotificationChannelId(null);
876                     builder.setImportant(false);
877                     builder.setDemoted(false);
878                     builder.setNotificationSilenced(false);
879                     builder.setBubbled(false);
880                     break;
881             }
882             conversationStore.addOrUpdate(builder.build());
883         }
884 
cleanupCachedShortcuts()885         synchronized void cleanupCachedShortcuts() {
886             for (Pair<String, String> conversationKey : mActiveNotifCounts.keySet()) {
887                 String packageName = conversationKey.first;
888                 String shortcutId = conversationKey.second;
889                 PackageData packageData = getPackage(packageName, mUserId);
890                 ConversationInfo conversationInfo =
891                         packageData != null ? packageData.getConversationInfo(shortcutId) : null;
892                 if (conversationInfo != null
893                         && conversationInfo.isShortcutCachedForNotification()
894                         && conversationInfo.getNotificationChannelId() == null) {
895                     mShortcutServiceInternal.uncacheShortcuts(mUserId,
896                             mContext.getPackageName(), packageName,
897                             Collections.singletonList(shortcutId),
898                             mUserId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
899                 }
900             }
901         }
902 
hasActiveNotifications(String packageName, String shortcutId)903         synchronized boolean hasActiveNotifications(String packageName, String shortcutId) {
904             return mActiveNotifCounts.containsKey(Pair.create(packageName, shortcutId));
905         }
906     }
907 
908     /**
909      * A {@link Runnable} that queries the Usage Stats Service for recent events for a specified
910      * user.
911      */
912     private class UsageStatsQueryRunnable implements Runnable {
913 
914         private final UsageStatsQueryHelper mUsageStatsQueryHelper;
915         private long mLastEventTimestamp;
916 
UsageStatsQueryRunnable(int userId)917         private UsageStatsQueryRunnable(int userId) {
918             mUsageStatsQueryHelper = mInjector.createUsageStatsQueryHelper(userId,
919                     (packageName) -> getPackage(packageName, userId));
920             mLastEventTimestamp = System.currentTimeMillis() - QUERY_EVENTS_MAX_AGE_MS;
921         }
922 
923         @Override
run()924         public void run() {
925             if (mUsageStatsQueryHelper.querySince(mLastEventTimestamp)) {
926                 mLastEventTimestamp = mUsageStatsQueryHelper.getLastEventTimestamp();
927             }
928         }
929     }
930 
931     /** A {@link BroadcastReceiver} that receives the intents for a specified user. */
932     private class PerUserBroadcastReceiver extends BroadcastReceiver {
933 
934         private final int mUserId;
935 
PerUserBroadcastReceiver(int userId)936         private PerUserBroadcastReceiver(int userId) {
937             mUserId = userId;
938         }
939 
940         @Override
onReceive(Context context, Intent intent)941         public void onReceive(Context context, Intent intent) {
942             UserData userData = getUnlockedUserData(mUserId);
943             if (userData == null) {
944                 return;
945             }
946             if (TelecomManager.ACTION_DEFAULT_DIALER_CHANGED.equals(intent.getAction())) {
947                 String defaultDialer = intent.getStringExtra(
948                         TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME);
949                 userData.setDefaultDialer(defaultDialer);
950             } else if (SmsApplication.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL.equals(
951                     intent.getAction())) {
952                 updateDefaultSmsApp(userData);
953             }
954         }
955     }
956 
957     private class PerUserPackageMonitor extends PackageMonitor {
958 
959         @Override
onPackageRemoved(String packageName, int uid)960         public void onPackageRemoved(String packageName, int uid) {
961             super.onPackageRemoved(packageName, uid);
962 
963             int userId = getChangingUserId();
964             UserData userData = getUnlockedUserData(userId);
965             if (userData != null) {
966                 userData.deletePackageData(packageName);
967             }
968         }
969     }
970 
971     private class ShutdownBroadcastReceiver extends BroadcastReceiver {
972 
973         @Override
onReceive(Context context, Intent intent)974         public void onReceive(Context context, Intent intent) {
975             forAllUnlockedUsers(userData -> {
976                 NotificationListener listener = mNotificationListeners.get(userData.getUserId());
977                 // Clean up the cached shortcuts because all the notifications are cleared after
978                 // system shutdown. The associated shortcuts need to be uncached to keep in sync
979                 // unless the settings are changed by the user.
980                 if (listener != null) {
981                     listener.cleanupCachedShortcuts();
982                 }
983                 userData.forAllPackages(PackageData::saveToDisk);
984             });
985         }
986     }
987 
988     @VisibleForTesting
989     static class Injector {
990 
createScheduledExecutor()991         ScheduledExecutorService createScheduledExecutor() {
992             return Executors.newSingleThreadScheduledExecutor();
993         }
994 
getBackgroundExecutor()995         Executor getBackgroundExecutor() {
996             return BackgroundThread.getExecutor();
997         }
998 
createContactsQueryHelper(Context context)999         ContactsQueryHelper createContactsQueryHelper(Context context) {
1000             return new ContactsQueryHelper(context);
1001         }
1002 
createCallLogQueryHelper(Context context, BiConsumer<String, Event> eventConsumer)1003         CallLogQueryHelper createCallLogQueryHelper(Context context,
1004                 BiConsumer<String, Event> eventConsumer) {
1005             return new CallLogQueryHelper(context, eventConsumer);
1006         }
1007 
createMmsQueryHelper(Context context, BiConsumer<String, Event> eventConsumer)1008         MmsQueryHelper createMmsQueryHelper(Context context,
1009                 BiConsumer<String, Event> eventConsumer) {
1010             return new MmsQueryHelper(context, eventConsumer);
1011         }
1012 
createSmsQueryHelper(Context context, BiConsumer<String, Event> eventConsumer)1013         SmsQueryHelper createSmsQueryHelper(Context context,
1014                 BiConsumer<String, Event> eventConsumer) {
1015             return new SmsQueryHelper(context, eventConsumer);
1016         }
1017 
createUsageStatsQueryHelper(@serIdInt int userId, Function<String, PackageData> packageDataGetter)1018         UsageStatsQueryHelper createUsageStatsQueryHelper(@UserIdInt int userId,
1019                 Function<String, PackageData> packageDataGetter) {
1020             return new UsageStatsQueryHelper(userId, packageDataGetter);
1021         }
1022     }
1023 }
1024