• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.NotificationChannelGroup;
26 import android.app.NotificationManager;
27 import android.app.Person;
28 import android.app.people.ConversationChannel;
29 import android.app.people.ConversationStatus;
30 import android.app.prediction.AppTarget;
31 import android.app.prediction.AppTargetEvent;
32 import android.app.usage.UsageEvents;
33 import android.content.BroadcastReceiver;
34 import android.content.ComponentName;
35 import android.content.ContentResolver;
36 import android.content.Context;
37 import android.content.Intent;
38 import android.content.IntentFilter;
39 import android.content.pm.LauncherApps;
40 import android.content.pm.LauncherApps.ShortcutQuery;
41 import android.content.pm.PackageManager;
42 import android.content.pm.PackageManagerInternal;
43 import android.content.pm.ShortcutInfo;
44 import android.content.pm.ShortcutManager.ShareShortcutInfo;
45 import android.content.pm.ShortcutServiceInternal;
46 import android.content.pm.UserInfo;
47 import android.database.ContentObserver;
48 import android.net.Uri;
49 import android.os.CancellationSignal;
50 import android.os.Handler;
51 import android.os.Looper;
52 import android.os.Process;
53 import android.os.RemoteException;
54 import android.os.UserHandle;
55 import android.os.UserManager;
56 import android.provider.CallLog;
57 import android.provider.ContactsContract.Contacts;
58 import android.provider.Telephony.MmsSms;
59 import android.service.notification.NotificationListenerService;
60 import android.service.notification.StatusBarNotification;
61 import android.telecom.TelecomManager;
62 import android.text.TextUtils;
63 import android.text.format.DateUtils;
64 import android.util.ArrayMap;
65 import android.util.ArraySet;
66 import android.util.Log;
67 import android.util.Pair;
68 import android.util.Slog;
69 import android.util.SparseArray;
70 
71 import com.android.internal.annotations.GuardedBy;
72 import com.android.internal.annotations.VisibleForTesting;
73 import com.android.internal.app.ChooserActivity;
74 import com.android.internal.content.PackageMonitor;
75 import com.android.internal.os.BackgroundThread;
76 import com.android.internal.telephony.SmsApplication;
77 import com.android.server.LocalServices;
78 import com.android.server.notification.NotificationManagerInternal;
79 import com.android.server.notification.ShortcutHelper;
80 import com.android.server.people.PeopleService;
81 
82 import java.util.ArrayList;
83 import java.util.Arrays;
84 import java.util.Collection;
85 import java.util.Collections;
86 import java.util.Comparator;
87 import java.util.HashSet;
88 import java.util.List;
89 import java.util.Map;
90 import java.util.Objects;
91 import java.util.PriorityQueue;
92 import java.util.Set;
93 import java.util.concurrent.Executor;
94 import java.util.concurrent.Executors;
95 import java.util.concurrent.ScheduledExecutorService;
96 import java.util.concurrent.ScheduledFuture;
97 import java.util.concurrent.TimeUnit;
98 import java.util.function.BiConsumer;
99 import java.util.function.Consumer;
100 import java.util.function.Function;
101 
102 /**
103  * A class manages the lifecycle of the conversations and associated data, and exposes the methods
104  * to access the data in People Service and other system services.
105  */
106 public class DataManager {
107 
108     private static final String TAG = "DataManager";
109     private static final boolean DEBUG = false;
110 
111     private static final long RECENT_NOTIFICATIONS_MAX_AGE_MS = 10 * DateUtils.DAY_IN_MILLIS;
112     private static final long QUERY_EVENTS_MAX_AGE_MS = 5L * DateUtils.MINUTE_IN_MILLIS;
113     private static final long USAGE_STATS_QUERY_INTERVAL_SEC = 120L;
114     @VisibleForTesting
115     static final int MAX_CACHED_RECENT_SHORTCUTS = 30;
116 
117     private final Context mContext;
118     private final Injector mInjector;
119     private final ScheduledExecutorService mScheduledExecutor;
120     private final Object mLock = new Object();
121 
122     private final SparseArray<UserData> mUserDataArray = new SparseArray<>();
123     private final SparseArray<BroadcastReceiver> mBroadcastReceivers = new SparseArray<>();
124     private final SparseArray<ContentObserver> mContactsContentObservers = new SparseArray<>();
125     private final SparseArray<ScheduledFuture<?>> mUsageStatsQueryFutures = new SparseArray<>();
126     private final SparseArray<NotificationListener> mNotificationListeners = new SparseArray<>();
127     private final SparseArray<PackageMonitor> mPackageMonitors = new SparseArray<>();
128     @GuardedBy("mLock")
129     private final List<PeopleService.ConversationsListener> mConversationsListeners =
130             new ArrayList<>(1);
131     private final Handler mHandler;
132 
133     private ContentObserver mCallLogContentObserver;
134     private ContentObserver mMmsSmsContentObserver;
135 
136     private ShortcutServiceInternal mShortcutServiceInternal;
137     private PackageManagerInternal mPackageManagerInternal;
138     private NotificationManagerInternal mNotificationManagerInternal;
139     private UserManager mUserManager;
140     private ConversationStatusExpirationBroadcastReceiver mStatusExpReceiver;
141 
DataManager(Context context)142     public DataManager(Context context) {
143         this(context, new Injector(), BackgroundThread.get().getLooper());
144     }
145 
DataManager(Context context, Injector injector, Looper looper)146     DataManager(Context context, Injector injector, Looper looper) {
147         mContext = context;
148         mInjector = injector;
149         mScheduledExecutor = mInjector.createScheduledExecutor();
150         mHandler = new Handler(looper);
151     }
152 
153     /** Initialization. Called when the system services are up running. */
initialize()154     public void initialize() {
155         mShortcutServiceInternal = LocalServices.getService(ShortcutServiceInternal.class);
156         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
157         mNotificationManagerInternal = LocalServices.getService(NotificationManagerInternal.class);
158         mUserManager = mContext.getSystemService(UserManager.class);
159 
160         mShortcutServiceInternal.addShortcutChangeCallback(new ShortcutServiceCallback());
161 
162         mStatusExpReceiver = new ConversationStatusExpirationBroadcastReceiver();
163         mContext.registerReceiver(mStatusExpReceiver,
164                 ConversationStatusExpirationBroadcastReceiver.getFilter());
165 
166         IntentFilter shutdownIntentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
167         BroadcastReceiver shutdownBroadcastReceiver = new ShutdownBroadcastReceiver();
168         mContext.registerReceiver(shutdownBroadcastReceiver, shutdownIntentFilter);
169     }
170 
171     /** This method is called when a user is unlocked. */
onUserUnlocked(int userId)172     public void onUserUnlocked(int userId) {
173         synchronized (mLock) {
174             UserData userData = mUserDataArray.get(userId);
175             if (userData == null) {
176                 userData = new UserData(userId, mScheduledExecutor);
177                 mUserDataArray.put(userId, userData);
178             }
179             userData.setUserUnlocked();
180         }
181         mScheduledExecutor.execute(() -> setupUser(userId));
182     }
183 
184     /** This method is called when a user is stopping. */
onUserStopping(int userId)185     public void onUserStopping(int userId) {
186         synchronized (mLock) {
187             UserData userData = mUserDataArray.get(userId);
188             if (userData != null) {
189                 userData.setUserStopped();
190             }
191         }
192         mScheduledExecutor.execute(() -> cleanupUser(userId));
193     }
194 
195     /**
196      * Iterates through all the {@link PackageData}s owned by the unlocked users who are in the
197      * same profile group as the calling user.
198      */
forPackagesInProfile(@serIdInt int callingUserId, Consumer<PackageData> consumer)199     void forPackagesInProfile(@UserIdInt int callingUserId, Consumer<PackageData> consumer) {
200         List<UserInfo> users = mUserManager.getEnabledProfiles(callingUserId);
201         for (UserInfo userInfo : users) {
202             UserData userData = getUnlockedUserData(userInfo.id);
203             if (userData != null) {
204                 userData.forAllPackages(consumer);
205             }
206         }
207     }
208 
209     /** Gets the {@link PackageData} for the given package and user. */
210     @Nullable
getPackage(@onNull String packageName, @UserIdInt int userId)211     public PackageData getPackage(@NonNull String packageName, @UserIdInt int userId) {
212         UserData userData = getUnlockedUserData(userId);
213         return userData != null ? userData.getPackageData(packageName) : null;
214     }
215 
216     /** Gets the {@link ShortcutInfo} for the given shortcut ID. */
217     @Nullable
getShortcut(@onNull String packageName, @UserIdInt int userId, @NonNull String shortcutId)218     public ShortcutInfo getShortcut(@NonNull String packageName, @UserIdInt int userId,
219             @NonNull String shortcutId) {
220         List<ShortcutInfo> shortcuts = getShortcuts(packageName, userId,
221                 Collections.singletonList(shortcutId));
222         if (shortcuts != null && !shortcuts.isEmpty()) {
223             if (DEBUG) Log.d(TAG, "Found shortcut for " + shortcuts.get(0).getLabel());
224             return shortcuts.get(0);
225         }
226         return null;
227     }
228 
229     /**
230      * Gets the {@link ShareShortcutInfo}s from all packages owned by the calling user that match
231      * the specified {@link IntentFilter}.
232      */
getShareShortcuts(@onNull IntentFilter intentFilter, @UserIdInt int callingUserId)233     public List<ShareShortcutInfo> getShareShortcuts(@NonNull IntentFilter intentFilter,
234             @UserIdInt int callingUserId) {
235         return mShortcutServiceInternal.getShareTargets(
236                 mContext.getPackageName(), intentFilter, callingUserId);
237     }
238 
239     /**
240      * Returns a {@link ConversationChannel} with the associated {@code shortcutId} if existent.
241      * Otherwise, returns null.
242      */
243     @Nullable
getConversation(String packageName, int userId, String shortcutId)244     public ConversationChannel getConversation(String packageName, int userId, String shortcutId) {
245         UserData userData = getUnlockedUserData(userId);
246         if (userData != null) {
247             PackageData packageData = userData.getPackageData(packageName);
248             // App may have been uninstalled.
249             if (packageData != null) {
250                 ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId);
251                 return getConversationChannel(packageName, userId, shortcutId, conversationInfo);
252             }
253         }
254         return null;
255     }
256 
getConversationInfo(String packageName, int userId, String shortcutId)257     ConversationInfo getConversationInfo(String packageName, int userId, String shortcutId) {
258         UserData userData = getUnlockedUserData(userId);
259         if (userData != null) {
260             PackageData packageData = userData.getPackageData(packageName);
261             // App may have been uninstalled.
262             if (packageData != null) {
263                 return packageData.getConversationInfo(shortcutId);
264             }
265         }
266         return null;
267     }
268 
269     @Nullable
getConversationChannel(String packageName, int userId, String shortcutId, ConversationInfo conversationInfo)270     private ConversationChannel getConversationChannel(String packageName, int userId,
271             String shortcutId, ConversationInfo conversationInfo) {
272         ShortcutInfo shortcutInfo = getShortcut(packageName, userId, shortcutId);
273         return getConversationChannel(shortcutInfo, conversationInfo);
274     }
275 
276     @Nullable
getConversationChannel(ShortcutInfo shortcutInfo, ConversationInfo conversationInfo)277     private ConversationChannel getConversationChannel(ShortcutInfo shortcutInfo,
278             ConversationInfo conversationInfo) {
279         if (conversationInfo == null || conversationInfo.isDemoted()) {
280             return null;
281         }
282         if (shortcutInfo == null) {
283             Slog.e(TAG, " Shortcut no longer found");
284             return null;
285         }
286         String packageName = shortcutInfo.getPackage();
287         String shortcutId = shortcutInfo.getId();
288         int userId = shortcutInfo.getUserId();
289         int uid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
290         NotificationChannel parentChannel =
291                 mNotificationManagerInternal.getNotificationChannel(packageName, uid,
292                         conversationInfo.getNotificationChannelId());
293         NotificationChannelGroup parentChannelGroup = null;
294         if (parentChannel != null) {
295             parentChannelGroup =
296                     mNotificationManagerInternal.getNotificationChannelGroup(packageName,
297                             uid, parentChannel.getId());
298         }
299         return new ConversationChannel(shortcutInfo, uid, parentChannel,
300                 parentChannelGroup,
301                 conversationInfo.getLastEventTimestamp(),
302                 hasActiveNotifications(packageName, userId, shortcutId), false,
303                 getStatuses(conversationInfo));
304     }
305 
306     /** Returns the cached non-customized recent conversations. */
getRecentConversations(@serIdInt int callingUserId)307     public List<ConversationChannel> getRecentConversations(@UserIdInt int callingUserId) {
308         List<ConversationChannel> conversationChannels = new ArrayList<>();
309         forPackagesInProfile(callingUserId, packageData -> {
310             packageData.forAllConversations(conversationInfo -> {
311                 if (!isCachedRecentConversation(conversationInfo)) {
312                     return;
313                 }
314                 String shortcutId = conversationInfo.getShortcutId();
315                 ConversationChannel channel = getConversationChannel(packageData.getPackageName(),
316                         packageData.getUserId(), shortcutId, conversationInfo);
317                 if (channel == null || channel.getNotificationChannel() == null) {
318                     return;
319                 }
320                 conversationChannels.add(channel);
321             });
322         });
323         return conversationChannels;
324     }
325 
326     /**
327      * Uncaches the shortcut that's associated with the specified conversation so this conversation
328      * will not show up in the recent conversations list.
329      */
removeRecentConversation(String packageName, int userId, String shortcutId, @UserIdInt int callingUserId)330     public void removeRecentConversation(String packageName, int userId, String shortcutId,
331             @UserIdInt int callingUserId) {
332         if (!hasActiveNotifications(packageName, userId, shortcutId)) {
333             mShortcutServiceInternal.uncacheShortcuts(callingUserId, mContext.getPackageName(),
334                     packageName, Collections.singletonList(shortcutId), userId,
335                     ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
336         }
337     }
338 
339     /**
340      * Uncaches the shortcuts for all the recent conversations that they don't have active
341      * notifications.
342      */
removeAllRecentConversations(@serIdInt int callingUserId)343     public void removeAllRecentConversations(@UserIdInt int callingUserId) {
344         pruneOldRecentConversations(callingUserId, Long.MAX_VALUE);
345     }
346 
347     /**
348      * Uncaches the shortcuts for all the recent conversations that haven't been interacted with
349      * recently.
350      */
pruneOldRecentConversations(@serIdInt int callingUserId, long currentTimeMs)351     public void pruneOldRecentConversations(@UserIdInt int callingUserId, long currentTimeMs) {
352         forPackagesInProfile(callingUserId, packageData -> {
353             String packageName = packageData.getPackageName();
354             int userId = packageData.getUserId();
355             List<String> idsToUncache = new ArrayList<>();
356             packageData.forAllConversations(conversationInfo -> {
357                 String shortcutId = conversationInfo.getShortcutId();
358                 if (isCachedRecentConversation(conversationInfo)
359                         && (currentTimeMs - conversationInfo.getLastEventTimestamp()
360                         > RECENT_NOTIFICATIONS_MAX_AGE_MS)
361                         && !hasActiveNotifications(packageName, userId, shortcutId)) {
362                     idsToUncache.add(shortcutId);
363                 }
364             });
365             if (!idsToUncache.isEmpty()) {
366                 mShortcutServiceInternal.uncacheShortcuts(callingUserId, mContext.getPackageName(),
367                         packageName, idsToUncache, userId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
368             }
369         });
370     }
371 
372     /**
373      * Removes any status with an expiration time in the past.
374      */
pruneExpiredConversationStatuses(@serIdInt int callingUserId, long currentTimeMs)375     public void pruneExpiredConversationStatuses(@UserIdInt int callingUserId, long currentTimeMs) {
376         forPackagesInProfile(callingUserId, packageData -> {
377             final ConversationStore cs = packageData.getConversationStore();
378             packageData.forAllConversations(conversationInfo -> {
379                 ConversationInfo.Builder builder = new ConversationInfo.Builder(conversationInfo);
380                 List<ConversationStatus> newStatuses = new ArrayList<>();
381                 for (ConversationStatus status : conversationInfo.getStatuses()) {
382                     if (status.getEndTimeMillis() < 0
383                             || currentTimeMs < status.getEndTimeMillis()) {
384                         newStatuses.add(status);
385                     }
386                 }
387                 builder.setStatuses(newStatuses);
388                 updateConversationStoreThenNotifyListeners(cs, builder.build(),
389                         packageData.getPackageName(),
390                         packageData.getUserId());
391             });
392         });
393     }
394 
395     /** Returns whether {@code shortcutId} is backed by Conversation. */
isConversation(String packageName, int userId, String shortcutId)396     public boolean isConversation(String packageName, int userId, String shortcutId) {
397         ConversationChannel channel = getConversation(packageName, userId, shortcutId);
398         return channel != null
399                 && channel.getShortcutInfo() != null
400                 && !TextUtils.isEmpty(channel.getShortcutInfo().getLabel());
401     }
402 
403     /**
404      * Returns the last notification interaction with the specified conversation. If the
405      * conversation can't be found or no interactions have been recorded, returns 0L.
406      */
getLastInteraction(String packageName, int userId, String shortcutId)407     public long getLastInteraction(String packageName, int userId, String shortcutId) {
408         final PackageData packageData = getPackage(packageName, userId);
409         if (packageData != null) {
410             final ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId);
411             if (conversationInfo != null) {
412                 return conversationInfo.getLastEventTimestamp();
413             }
414         }
415         return 0L;
416     }
417 
addOrUpdateStatus(String packageName, int userId, String conversationId, ConversationStatus status)418     public void addOrUpdateStatus(String packageName, int userId, String conversationId,
419             ConversationStatus status) {
420         ConversationStore cs = getConversationStoreOrThrow(packageName, userId);
421         ConversationInfo convToModify = getConversationInfoOrThrow(cs, conversationId);
422         ConversationInfo.Builder builder = new ConversationInfo.Builder(convToModify);
423         builder.addOrUpdateStatus(status);
424         updateConversationStoreThenNotifyListeners(cs, builder.build(), packageName, userId);
425 
426         if (status.getEndTimeMillis() >= 0) {
427             mStatusExpReceiver.scheduleExpiration(
428                     mContext, userId, packageName, conversationId, status);
429         }
430 
431     }
432 
clearStatus(String packageName, int userId, String conversationId, String statusId)433     public void clearStatus(String packageName, int userId, String conversationId,
434             String statusId) {
435         ConversationStore cs = getConversationStoreOrThrow(packageName, userId);
436         ConversationInfo convToModify = getConversationInfoOrThrow(cs, conversationId);
437         ConversationInfo.Builder builder = new ConversationInfo.Builder(convToModify);
438         builder.clearStatus(statusId);
439         updateConversationStoreThenNotifyListeners(cs, builder.build(), packageName, userId);
440     }
441 
clearStatuses(String packageName, int userId, String conversationId)442     public void clearStatuses(String packageName, int userId, String conversationId) {
443         ConversationStore cs = getConversationStoreOrThrow(packageName, userId);
444         ConversationInfo convToModify = getConversationInfoOrThrow(cs, conversationId);
445         ConversationInfo.Builder builder = new ConversationInfo.Builder(convToModify);
446         builder.setStatuses(null);
447         updateConversationStoreThenNotifyListeners(cs, builder.build(), packageName, userId);
448     }
449 
getStatuses(String packageName, int userId, String conversationId)450     public @NonNull List<ConversationStatus> getStatuses(String packageName, int userId,
451             String conversationId) {
452         ConversationStore cs = getConversationStoreOrThrow(packageName, userId);
453         ConversationInfo conversationInfo = getConversationInfoOrThrow(cs, conversationId);
454         return getStatuses(conversationInfo);
455     }
456 
getStatuses(ConversationInfo conversationInfo)457     private @NonNull List<ConversationStatus> getStatuses(ConversationInfo conversationInfo) {
458         Collection<ConversationStatus> statuses = conversationInfo.getStatuses();
459         if (statuses != null) {
460             final ArrayList<ConversationStatus> list = new ArrayList<>(statuses.size());
461             list.addAll(statuses);
462             return list;
463         }
464         return new ArrayList<>();
465     }
466 
467     /**
468      * Returns a conversation store for a package, if it exists.
469      */
getConversationStoreOrThrow(String packageName, int userId)470     private @NonNull ConversationStore getConversationStoreOrThrow(String packageName, int userId) {
471         final PackageData packageData = getPackage(packageName, userId);
472         if (packageData == null) {
473             throw new IllegalArgumentException("No settings exist for package " + packageName);
474         }
475         ConversationStore cs = packageData.getConversationStore();
476         if (cs == null) {
477             throw new IllegalArgumentException("No conversations exist for package " + packageName);
478         }
479         return cs;
480     }
481 
482     /**
483      * Returns a conversation store for a package, if it exists.
484      */
getConversationInfoOrThrow(ConversationStore cs, String conversationId)485     private @NonNull ConversationInfo getConversationInfoOrThrow(ConversationStore cs,
486             String conversationId) {
487         ConversationInfo ci = cs.getConversation(conversationId);
488 
489         if (ci == null) {
490             throw new IllegalArgumentException("Conversation does not exist");
491         }
492         return ci;
493     }
494 
495     /** Reports the sharing related {@link AppTargetEvent} from App Prediction Manager. */
reportShareTargetEvent(@onNull AppTargetEvent event, @NonNull IntentFilter intentFilter)496     public void reportShareTargetEvent(@NonNull AppTargetEvent event,
497             @NonNull IntentFilter intentFilter) {
498         AppTarget appTarget = event.getTarget();
499         if (appTarget == null || event.getAction() != AppTargetEvent.ACTION_LAUNCH) {
500             return;
501         }
502         UserData userData = getUnlockedUserData(appTarget.getUser().getIdentifier());
503         if (userData == null) {
504             return;
505         }
506         PackageData packageData = userData.getOrCreatePackageData(appTarget.getPackageName());
507         @Event.EventType int eventType = mimeTypeToShareEventType(intentFilter.getDataType(0));
508         EventHistoryImpl eventHistory;
509         if (ChooserActivity.LAUNCH_LOCATION_DIRECT_SHARE.equals(event.getLaunchLocation())) {
510             // Direct share event
511             if (appTarget.getShortcutInfo() == null) {
512                 return;
513             }
514             String shortcutId = appTarget.getShortcutInfo().getId();
515             // Skip storing chooserTargets sharing events
516             if (ChooserActivity.CHOOSER_TARGET.equals(shortcutId)) {
517                 return;
518             }
519             if (packageData.getConversationStore().getConversation(shortcutId) == null) {
520                 addOrUpdateConversationInfo(appTarget.getShortcutInfo());
521             }
522             eventHistory = packageData.getEventStore().getOrCreateEventHistory(
523                     EventStore.CATEGORY_SHORTCUT_BASED, shortcutId);
524         } else {
525             // App share event
526             eventHistory = packageData.getEventStore().getOrCreateEventHistory(
527                     EventStore.CATEGORY_CLASS_BASED, appTarget.getClassName());
528         }
529         eventHistory.addEvent(new Event(System.currentTimeMillis(), eventType));
530     }
531 
532     /**
533      * Queries events for moving app to foreground between {@code startTime} and {@code endTime}.
534      */
535     @NonNull
queryAppMovingToForegroundEvents(@serIdInt int callingUserId, long startTime, long endTime)536     public List<UsageEvents.Event> queryAppMovingToForegroundEvents(@UserIdInt int callingUserId,
537             long startTime, long endTime) {
538         return UsageStatsQueryHelper.queryAppMovingToForegroundEvents(callingUserId, startTime,
539                 endTime);
540     }
541 
542     /**
543      * Queries usage stats of apps within {@code packageNameFilter} between {@code startTime} and
544      * {@code endTime}.
545      *
546      * @return a map which keys are package names and values are {@link AppUsageStatsData}.
547      */
548     @NonNull
queryAppUsageStats( @serIdInt int callingUserId, long startTime, long endTime, Set<String> packageNameFilter)549     public Map<String, AppUsageStatsData> queryAppUsageStats(
550             @UserIdInt int callingUserId, long startTime,
551             long endTime, Set<String> packageNameFilter) {
552         return UsageStatsQueryHelper.queryAppUsageStats(callingUserId, startTime, endTime,
553                 packageNameFilter);
554     }
555 
556     /** Prunes the data for the specified user. */
pruneDataForUser(@serIdInt int userId, @NonNull CancellationSignal signal)557     public void pruneDataForUser(@UserIdInt int userId, @NonNull CancellationSignal signal) {
558         UserData userData = getUnlockedUserData(userId);
559         if (userData == null || signal.isCanceled()) {
560             return;
561         }
562         pruneUninstalledPackageData(userData);
563 
564         userData.forAllPackages(packageData -> {
565             if (signal.isCanceled()) {
566                 return;
567             }
568             packageData.getEventStore().pruneOldEvents();
569             if (!packageData.isDefaultDialer()) {
570                 packageData.getEventStore().deleteEventHistories(EventStore.CATEGORY_CALL);
571             }
572             if (!packageData.isDefaultSmsApp()) {
573                 packageData.getEventStore().deleteEventHistories(EventStore.CATEGORY_SMS);
574             }
575             packageData.pruneOrphanEvents();
576             pruneExpiredConversationStatuses(userId, System.currentTimeMillis());
577             pruneOldRecentConversations(userId, System.currentTimeMillis());
578             cleanupCachedShortcuts(userId, MAX_CACHED_RECENT_SHORTCUTS);
579         });
580     }
581 
582     /** Retrieves a backup payload blob for specified user id. */
583     @Nullable
getBackupPayload(@serIdInt int userId)584     public byte[] getBackupPayload(@UserIdInt int userId) {
585         UserData userData = getUnlockedUserData(userId);
586         if (userData == null) {
587             return null;
588         }
589         return userData.getBackupPayload();
590     }
591 
592     /** Attempts to restore data for the specified user id. */
restore(@serIdInt int userId, @NonNull byte[] payload)593     public void restore(@UserIdInt int userId, @NonNull byte[] payload) {
594         UserData userData = getUnlockedUserData(userId);
595         if (userData == null) {
596             return;
597         }
598         userData.restore(payload);
599     }
600 
setupUser(@serIdInt int userId)601     private void setupUser(@UserIdInt int userId) {
602         synchronized (mLock) {
603             UserData userData = getUnlockedUserData(userId);
604             if (userData == null) {
605                 return;
606             }
607             userData.loadUserData();
608 
609             updateDefaultDialer(userData);
610             updateDefaultSmsApp(userData);
611 
612             ScheduledFuture<?> scheduledFuture = mScheduledExecutor.scheduleAtFixedRate(
613                     new UsageStatsQueryRunnable(userId), 1L, USAGE_STATS_QUERY_INTERVAL_SEC,
614                     TimeUnit.SECONDS);
615             mUsageStatsQueryFutures.put(userId, scheduledFuture);
616 
617             IntentFilter intentFilter = new IntentFilter();
618             intentFilter.addAction(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED);
619             intentFilter.addAction(SmsApplication.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL);
620             BroadcastReceiver broadcastReceiver = new PerUserBroadcastReceiver(userId);
621             mBroadcastReceivers.put(userId, broadcastReceiver);
622             mContext.registerReceiverAsUser(
623                     broadcastReceiver, UserHandle.of(userId), intentFilter, null, null);
624 
625             ContentObserver contactsContentObserver = new ContactsContentObserver(
626                     BackgroundThread.getHandler());
627             mContactsContentObservers.put(userId, contactsContentObserver);
628             mContext.getContentResolver().registerContentObserver(
629                     Contacts.CONTENT_URI, /* notifyForDescendants= */ true,
630                     contactsContentObserver, userId);
631 
632             NotificationListener notificationListener = new NotificationListener(userId);
633             mNotificationListeners.put(userId, notificationListener);
634             try {
635                 notificationListener.registerAsSystemService(mContext,
636                         new ComponentName(mContext, getClass()), userId);
637             } catch (RemoteException e) {
638                 // Should never occur for local calls.
639             }
640 
641             PackageMonitor packageMonitor = new PerUserPackageMonitor();
642             packageMonitor.register(mContext, null, UserHandle.of(userId), true);
643             mPackageMonitors.put(userId, packageMonitor);
644 
645             if (userId == UserHandle.USER_SYSTEM) {
646                 // The call log and MMS/SMS messages are shared across user profiles. So only need
647                 // to register the content observers once for the primary user.
648                 mCallLogContentObserver = new CallLogContentObserver(BackgroundThread.getHandler());
649                 mContext.getContentResolver().registerContentObserver(
650                         CallLog.CONTENT_URI, /* notifyForDescendants= */ true,
651                         mCallLogContentObserver, UserHandle.USER_SYSTEM);
652 
653                 mMmsSmsContentObserver = new MmsSmsContentObserver(BackgroundThread.getHandler());
654                 mContext.getContentResolver().registerContentObserver(
655                         MmsSms.CONTENT_URI, /* notifyForDescendants= */ false,
656                         mMmsSmsContentObserver, UserHandle.USER_SYSTEM);
657             }
658 
659             DataMaintenanceService.scheduleJob(mContext, userId);
660         }
661     }
662 
cleanupUser(@serIdInt int userId)663     private void cleanupUser(@UserIdInt int userId) {
664         synchronized (mLock) {
665             UserData userData = mUserDataArray.get(userId);
666             if (userData == null || userData.isUnlocked()) {
667                 return;
668             }
669             ContentResolver contentResolver = mContext.getContentResolver();
670             if (mUsageStatsQueryFutures.indexOfKey(userId) >= 0) {
671                 mUsageStatsQueryFutures.get(userId).cancel(true);
672             }
673             if (mBroadcastReceivers.indexOfKey(userId) >= 0) {
674                 mContext.unregisterReceiver(mBroadcastReceivers.get(userId));
675             }
676             if (mContactsContentObservers.indexOfKey(userId) >= 0) {
677                 contentResolver.unregisterContentObserver(mContactsContentObservers.get(userId));
678             }
679             if (mNotificationListeners.indexOfKey(userId) >= 0) {
680                 try {
681                     mNotificationListeners.get(userId).unregisterAsSystemService();
682                 } catch (RemoteException e) {
683                     // Should never occur for local calls.
684                 }
685             }
686             if (mPackageMonitors.indexOfKey(userId) >= 0) {
687                 mPackageMonitors.get(userId).unregister();
688             }
689             if (userId == UserHandle.USER_SYSTEM) {
690                 if (mCallLogContentObserver != null) {
691                     contentResolver.unregisterContentObserver(mCallLogContentObserver);
692                     mCallLogContentObserver = null;
693                 }
694                 if (mMmsSmsContentObserver != null) {
695                     contentResolver.unregisterContentObserver(mMmsSmsContentObserver);
696                     mCallLogContentObserver = null;
697                 }
698             }
699 
700             DataMaintenanceService.cancelJob(mContext, userId);
701         }
702     }
703 
704     /**
705      * Converts {@code mimeType} to {@link Event.EventType}.
706      */
mimeTypeToShareEventType(String mimeType)707     public int mimeTypeToShareEventType(String mimeType) {
708         if (mimeType == null) {
709             return Event.TYPE_SHARE_OTHER;
710         }
711         if (mimeType.startsWith("text/")) {
712             return Event.TYPE_SHARE_TEXT;
713         } else if (mimeType.startsWith("image/")) {
714             return Event.TYPE_SHARE_IMAGE;
715         } else if (mimeType.startsWith("video/")) {
716             return Event.TYPE_SHARE_VIDEO;
717         }
718         return Event.TYPE_SHARE_OTHER;
719     }
720 
pruneUninstalledPackageData(@onNull UserData userData)721     private void pruneUninstalledPackageData(@NonNull UserData userData) {
722         Set<String> installApps = new ArraySet<>();
723         mPackageManagerInternal.forEachInstalledPackage(
724                 pkg -> installApps.add(pkg.getPackageName()), userData.getUserId());
725         List<String> packagesToDelete = new ArrayList<>();
726         userData.forAllPackages(packageData -> {
727             if (!installApps.contains(packageData.getPackageName())) {
728                 packagesToDelete.add(packageData.getPackageName());
729             }
730         });
731         for (String packageName : packagesToDelete) {
732             if (DEBUG) Log.d(TAG, "Deleting packages data for: " + packageName);
733             userData.deletePackageData(packageName);
734         }
735     }
736 
737     /** Gets a list of {@link ShortcutInfo}s with the given shortcut IDs. */
getShortcuts( @onNull String packageName, @UserIdInt int userId, @Nullable List<String> shortcutIds)738     private List<ShortcutInfo> getShortcuts(
739             @NonNull String packageName, @UserIdInt int userId,
740             @Nullable List<String> shortcutIds) {
741         @ShortcutQuery.QueryFlags int queryFlags = ShortcutQuery.FLAG_MATCH_DYNAMIC
742                 | ShortcutQuery.FLAG_MATCH_PINNED | ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER
743                 | ShortcutQuery.FLAG_MATCH_CACHED | ShortcutQuery.FLAG_GET_PERSONS_DATA;
744         if (DEBUG) Log.d(TAG, " Get shortcuts with IDs: " + shortcutIds);
745         return mShortcutServiceInternal.getShortcuts(
746                 UserHandle.USER_SYSTEM, mContext.getPackageName(),
747                 /*changedSince=*/ 0, packageName, shortcutIds, /*locusIds=*/ null,
748                 /*componentName=*/ null, queryFlags, userId, Process.myPid(), Process.myUid());
749     }
750 
forAllUnlockedUsers(Consumer<UserData> consumer)751     private void forAllUnlockedUsers(Consumer<UserData> consumer) {
752         for (int i = 0; i < mUserDataArray.size(); i++) {
753             int userId = mUserDataArray.keyAt(i);
754             UserData userData = mUserDataArray.get(userId);
755             if (userData.isUnlocked()) {
756                 consumer.accept(userData);
757             }
758         }
759     }
760 
761     @Nullable
getUnlockedUserData(int userId)762     private UserData getUnlockedUserData(int userId) {
763         UserData userData = mUserDataArray.get(userId);
764         return userData != null && userData.isUnlocked() ? userData : null;
765     }
766 
updateDefaultDialer(@onNull UserData userData)767     private void updateDefaultDialer(@NonNull UserData userData) {
768         TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
769         String defaultDialer = telecomManager != null
770                 ? telecomManager.getDefaultDialerPackage(
771                 new UserHandle(userData.getUserId())) : null;
772         userData.setDefaultDialer(defaultDialer);
773     }
774 
updateDefaultSmsApp(@onNull UserData userData)775     private void updateDefaultSmsApp(@NonNull UserData userData) {
776         ComponentName component = SmsApplication.getDefaultSmsApplicationAsUser(
777                 mContext, /* updateIfNeeded= */ false, userData.getUserId());
778         String defaultSmsApp = component != null ? component.getPackageName() : null;
779         userData.setDefaultSmsApp(defaultSmsApp);
780     }
781 
782     @Nullable
getPackageIfConversationExists(StatusBarNotification sbn, Consumer<ConversationInfo> conversationConsumer)783     private PackageData getPackageIfConversationExists(StatusBarNotification sbn,
784             Consumer<ConversationInfo> conversationConsumer) {
785         Notification notification = sbn.getNotification();
786         String shortcutId = notification.getShortcutId();
787         if (shortcutId == null) {
788             return null;
789         }
790         PackageData packageData = getPackage(sbn.getPackageName(),
791                 sbn.getUser().getIdentifier());
792         if (packageData == null) {
793             return null;
794         }
795         ConversationInfo conversationInfo =
796                 packageData.getConversationStore().getConversation(shortcutId);
797         if (conversationInfo == null) {
798             return null;
799         }
800         conversationConsumer.accept(conversationInfo);
801         return packageData;
802     }
803 
isCachedRecentConversation(ConversationInfo conversationInfo)804     private boolean isCachedRecentConversation(ConversationInfo conversationInfo) {
805         return conversationInfo.isShortcutCachedForNotification()
806                 && Objects.equals(conversationInfo.getNotificationChannelId(),
807                 conversationInfo.getParentNotificationChannelId())
808                 && conversationInfo.getLastEventTimestamp() > 0L;
809     }
810 
hasActiveNotifications(String packageName, @UserIdInt int userId, String shortcutId)811     private boolean hasActiveNotifications(String packageName, @UserIdInt int userId,
812             String shortcutId) {
813         NotificationListener notificationListener = mNotificationListeners.get(userId);
814         return notificationListener != null
815                 && notificationListener.hasActiveNotifications(packageName, shortcutId);
816     }
817 
818     /**
819      * Cleans up the oldest cached shortcuts that don't have active notifications for the recent
820      * conversations. After the cleanup, normally, the total number of cached shortcuts will be
821      * less than or equal to the target count. However, there are exception cases: e.g. when all
822      * the existing cached shortcuts have active notifications.
823      */
cleanupCachedShortcuts(@serIdInt int userId, int targetCachedCount)824     private void cleanupCachedShortcuts(@UserIdInt int userId, int targetCachedCount) {
825         UserData userData = getUnlockedUserData(userId);
826         if (userData == null) {
827             return;
828         }
829         // pair of <package name, conversation info>
830         List<Pair<String, ConversationInfo>> cachedConvos = new ArrayList<>();
831         userData.forAllPackages(packageData ->
832                 packageData.forAllConversations(conversationInfo -> {
833                     if (isCachedRecentConversation(conversationInfo)) {
834                         cachedConvos.add(
835                                 Pair.create(packageData.getPackageName(), conversationInfo));
836                     }
837                 })
838         );
839         if (cachedConvos.size() <= targetCachedCount) {
840             return;
841         }
842         int numToUncache = cachedConvos.size() - targetCachedCount;
843         // Max heap keeps the oldest cached conversations.
844         PriorityQueue<Pair<String, ConversationInfo>> maxHeap = new PriorityQueue<>(
845                 numToUncache + 1,
846                 Comparator.comparingLong((Pair<String, ConversationInfo> pair) ->
847                         pair.second.getLastEventTimestamp()).reversed());
848         for (Pair<String, ConversationInfo> cached : cachedConvos) {
849             if (hasActiveNotifications(cached.first, userId, cached.second.getShortcutId())) {
850                 continue;
851             }
852             maxHeap.offer(cached);
853             if (maxHeap.size() > numToUncache) {
854                 maxHeap.poll();
855             }
856         }
857         while (!maxHeap.isEmpty()) {
858             Pair<String, ConversationInfo> toUncache = maxHeap.poll();
859             mShortcutServiceInternal.uncacheShortcuts(userId,
860                     mContext.getPackageName(), toUncache.first,
861                     Collections.singletonList(toUncache.second.getShortcutId()),
862                     userId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
863         }
864     }
865 
866     @VisibleForTesting
867     @WorkerThread
addOrUpdateConversationInfo(@onNull ShortcutInfo shortcutInfo)868     void addOrUpdateConversationInfo(@NonNull ShortcutInfo shortcutInfo) {
869         UserData userData = getUnlockedUserData(shortcutInfo.getUserId());
870         if (userData == null) {
871             return;
872         }
873         PackageData packageData = userData.getOrCreatePackageData(shortcutInfo.getPackage());
874         ConversationStore conversationStore = packageData.getConversationStore();
875         ConversationInfo oldConversationInfo =
876                 conversationStore.getConversation(shortcutInfo.getId());
877         if (oldConversationInfo == null) {
878             if (DEBUG) Log.d(TAG, "Nothing previously stored about conversation.");
879         }
880         ConversationInfo.Builder builder = oldConversationInfo != null
881                 ? new ConversationInfo.Builder(oldConversationInfo)
882                 : new ConversationInfo.Builder();
883 
884         builder.setShortcutId(shortcutInfo.getId());
885         builder.setLocusId(shortcutInfo.getLocusId());
886         builder.setShortcutFlags(shortcutInfo.getFlags());
887         builder.setContactUri(null);
888         builder.setContactPhoneNumber(null);
889         builder.setContactStarred(false);
890 
891         if (shortcutInfo.getPersons() != null && shortcutInfo.getPersons().length != 0) {
892             Person person = shortcutInfo.getPersons()[0];
893             builder.setPersonImportant(person.isImportant());
894             builder.setPersonBot(person.isBot());
895             String contactUri = person.getUri();
896             if (contactUri != null) {
897                 ContactsQueryHelper helper = mInjector.createContactsQueryHelper(mContext);
898                 if (helper.query(contactUri)) {
899                     builder.setContactUri(helper.getContactUri());
900                     builder.setContactStarred(helper.isStarred());
901                     builder.setContactPhoneNumber(helper.getPhoneNumber());
902                 }
903             }
904         }
905         updateConversationStoreThenNotifyListeners(conversationStore, builder.build(),
906                 shortcutInfo);
907     }
908 
909     @VisibleForTesting
getContactsContentObserverForTesting(@serIdInt int userId)910     ContentObserver getContactsContentObserverForTesting(@UserIdInt int userId) {
911         return mContactsContentObservers.get(userId);
912     }
913 
914     @VisibleForTesting
getCallLogContentObserverForTesting()915     ContentObserver getCallLogContentObserverForTesting() {
916         return mCallLogContentObserver;
917     }
918 
919     @VisibleForTesting
getMmsSmsContentObserverForTesting()920     ContentObserver getMmsSmsContentObserverForTesting() {
921         return mMmsSmsContentObserver;
922     }
923 
924     @VisibleForTesting
getNotificationListenerServiceForTesting(@serIdInt int userId)925     NotificationListener getNotificationListenerServiceForTesting(@UserIdInt int userId) {
926         return mNotificationListeners.get(userId);
927     }
928 
929     @VisibleForTesting
getPackageMonitorForTesting(@serIdInt int userId)930     PackageMonitor getPackageMonitorForTesting(@UserIdInt int userId) {
931         return mPackageMonitors.get(userId);
932     }
933 
934     @VisibleForTesting
getUserDataForTesting(@serIdInt int userId)935     UserData getUserDataForTesting(@UserIdInt int userId) {
936         return mUserDataArray.get(userId);
937     }
938 
939     /** Observer that observes the changes in the Contacts database. */
940     private class ContactsContentObserver extends ContentObserver {
941 
942         private long mLastUpdatedTimestamp;
943 
ContactsContentObserver(Handler handler)944         private ContactsContentObserver(Handler handler) {
945             super(handler);
946             mLastUpdatedTimestamp = System.currentTimeMillis();
947         }
948 
949         @Override
onChange(boolean selfChange, Uri uri, @UserIdInt int userId)950         public void onChange(boolean selfChange, Uri uri, @UserIdInt int userId) {
951             ContactsQueryHelper helper = mInjector.createContactsQueryHelper(mContext);
952             if (!helper.querySince(mLastUpdatedTimestamp)) {
953                 return;
954             }
955             Uri contactUri = helper.getContactUri();
956 
957             final ConversationSelector conversationSelector = new ConversationSelector();
958             UserData userData = getUnlockedUserData(userId);
959             if (userData == null) {
960                 return;
961             }
962             userData.forAllPackages(packageData -> {
963                 ConversationInfo ci =
964                         packageData.getConversationStore().getConversationByContactUri(contactUri);
965                 if (ci != null) {
966                     conversationSelector.mConversationStore =
967                             packageData.getConversationStore();
968                     conversationSelector.mConversationInfo = ci;
969                     conversationSelector.mPackageName = packageData.getPackageName();
970                 }
971             });
972             if (conversationSelector.mConversationInfo == null) {
973                 return;
974             }
975 
976             ConversationInfo.Builder builder =
977                     new ConversationInfo.Builder(conversationSelector.mConversationInfo);
978             builder.setContactStarred(helper.isStarred());
979             builder.setContactPhoneNumber(helper.getPhoneNumber());
980             updateConversationStoreThenNotifyListeners(conversationSelector.mConversationStore,
981                     builder.build(),
982                     conversationSelector.mPackageName, userId);
983             mLastUpdatedTimestamp = helper.getLastUpdatedTimestamp();
984         }
985 
986         private class ConversationSelector {
987             private ConversationStore mConversationStore = null;
988             private ConversationInfo mConversationInfo = null;
989             private String mPackageName = null;
990         }
991     }
992 
993     /** Observer that observes the changes in the call log database. */
994     private class CallLogContentObserver extends ContentObserver implements
995             BiConsumer<String, Event> {
996 
997         private final CallLogQueryHelper mCallLogQueryHelper;
998         private long mLastCallTimestamp;
999 
CallLogContentObserver(Handler handler)1000         private CallLogContentObserver(Handler handler) {
1001             super(handler);
1002             mCallLogQueryHelper = mInjector.createCallLogQueryHelper(mContext, this);
1003             mLastCallTimestamp = System.currentTimeMillis() - QUERY_EVENTS_MAX_AGE_MS;
1004         }
1005 
1006         @Override
onChange(boolean selfChange)1007         public void onChange(boolean selfChange) {
1008             if (mCallLogQueryHelper.querySince(mLastCallTimestamp)) {
1009                 mLastCallTimestamp = mCallLogQueryHelper.getLastCallTimestamp();
1010             }
1011         }
1012 
1013         @Override
accept(String phoneNumber, Event event)1014         public void accept(String phoneNumber, Event event) {
1015             forAllUnlockedUsers(userData -> {
1016                 PackageData defaultDialer = userData.getDefaultDialer();
1017                 if (defaultDialer == null) {
1018                     return;
1019                 }
1020                 ConversationStore conversationStore = defaultDialer.getConversationStore();
1021                 if (conversationStore.getConversationByPhoneNumber(phoneNumber) == null) {
1022                     return;
1023                 }
1024                 EventStore eventStore = defaultDialer.getEventStore();
1025                 eventStore.getOrCreateEventHistory(
1026                         EventStore.CATEGORY_CALL, phoneNumber).addEvent(event);
1027             });
1028         }
1029     }
1030 
1031     /** Observer that observes the changes in the MMS & SMS database. */
1032     private class MmsSmsContentObserver extends ContentObserver implements
1033             BiConsumer<String, Event> {
1034 
1035         private final MmsQueryHelper mMmsQueryHelper;
1036         private long mLastMmsTimestamp;
1037 
1038         private final SmsQueryHelper mSmsQueryHelper;
1039         private long mLastSmsTimestamp;
1040 
MmsSmsContentObserver(Handler handler)1041         private MmsSmsContentObserver(Handler handler) {
1042             super(handler);
1043             mMmsQueryHelper = mInjector.createMmsQueryHelper(mContext, this);
1044             mSmsQueryHelper = mInjector.createSmsQueryHelper(mContext, this);
1045             mLastSmsTimestamp = mLastMmsTimestamp =
1046                     System.currentTimeMillis() - QUERY_EVENTS_MAX_AGE_MS;
1047         }
1048 
1049         @Override
onChange(boolean selfChange)1050         public void onChange(boolean selfChange) {
1051             if (mMmsQueryHelper.querySince(mLastMmsTimestamp)) {
1052                 mLastMmsTimestamp = mMmsQueryHelper.getLastMessageTimestamp();
1053             }
1054             if (mSmsQueryHelper.querySince(mLastSmsTimestamp)) {
1055                 mLastSmsTimestamp = mSmsQueryHelper.getLastMessageTimestamp();
1056             }
1057         }
1058 
1059         @Override
accept(String phoneNumber, Event event)1060         public void accept(String phoneNumber, Event event) {
1061             forAllUnlockedUsers(userData -> {
1062                 PackageData defaultSmsApp = userData.getDefaultSmsApp();
1063                 if (defaultSmsApp == null) {
1064                     return;
1065                 }
1066                 ConversationStore conversationStore = defaultSmsApp.getConversationStore();
1067                 if (conversationStore.getConversationByPhoneNumber(phoneNumber) == null) {
1068                     return;
1069                 }
1070                 EventStore eventStore = defaultSmsApp.getEventStore();
1071                 eventStore.getOrCreateEventHistory(
1072                         EventStore.CATEGORY_SMS, phoneNumber).addEvent(event);
1073             });
1074         }
1075     }
1076 
1077     /** Listener for the shortcut data changes. */
1078     private class ShortcutServiceCallback implements LauncherApps.ShortcutChangeCallback {
1079 
1080         @Override
onShortcutsAddedOrUpdated(@onNull String packageName, @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user)1081         public void onShortcutsAddedOrUpdated(@NonNull String packageName,
1082                 @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
1083             mInjector.getBackgroundExecutor().execute(() -> {
1084                 PackageData packageData = getPackage(packageName, user.getIdentifier());
1085                 for (ShortcutInfo shortcut : shortcuts) {
1086                     if (ShortcutHelper.isConversationShortcut(
1087                             shortcut, mShortcutServiceInternal, user.getIdentifier())) {
1088                         if (shortcut.isCached()) {
1089                             ConversationInfo conversationInfo = packageData != null
1090                                     ? packageData.getConversationInfo(shortcut.getId()) : null;
1091                             if (conversationInfo == null
1092                                     || !conversationInfo.isShortcutCachedForNotification()) {
1093                                 // This is a newly cached shortcut. Clean up the existing cached
1094                                 // shortcuts to ensure the cache size is under the limit.
1095                                 cleanupCachedShortcuts(user.getIdentifier(),
1096                                         MAX_CACHED_RECENT_SHORTCUTS - 1);
1097                             }
1098                         }
1099                         addOrUpdateConversationInfo(shortcut);
1100                     }
1101                 }
1102             });
1103         }
1104 
1105         @Override
onShortcutsRemoved(@onNull String packageName, @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user)1106         public void onShortcutsRemoved(@NonNull String packageName,
1107                 @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
1108             mInjector.getBackgroundExecutor().execute(() -> {
1109                 int uid = Process.INVALID_UID;
1110                 try {
1111                     uid = mContext.getPackageManager().getPackageUidAsUser(
1112                             packageName, user.getIdentifier());
1113                 } catch (PackageManager.NameNotFoundException e) {
1114                     Slog.e(TAG, "Package not found: " + packageName, e);
1115                 }
1116                 PackageData packageData = getPackage(packageName, user.getIdentifier());
1117                 Set<String> shortcutIds = new HashSet<>();
1118                 for (ShortcutInfo shortcutInfo : shortcuts) {
1119                     if (packageData != null) {
1120                         if (DEBUG) Log.d(TAG, "Deleting shortcut: " + shortcutInfo.getId());
1121                         packageData.deleteDataForConversation(shortcutInfo.getId());
1122                     }
1123                     shortcutIds.add(shortcutInfo.getId());
1124                 }
1125                 if (uid != Process.INVALID_UID) {
1126                     mNotificationManagerInternal.onConversationRemoved(
1127                             packageName, uid, shortcutIds);
1128                 }
1129             });
1130         }
1131     }
1132 
1133     /** Listener for the notifications and their settings changes. */
1134     private class NotificationListener extends NotificationListenerService {
1135 
1136         private final int mUserId;
1137 
1138         // Conversation package name + shortcut ID -> Number of active notifications
1139         @GuardedBy("this")
1140         private final Map<Pair<String, String>, Integer> mActiveNotifCounts = new ArrayMap<>();
1141 
NotificationListener(int userId)1142         private NotificationListener(int userId) {
1143             mUserId = userId;
1144         }
1145 
1146         @Override
onNotificationPosted(StatusBarNotification sbn, RankingMap map)1147         public void onNotificationPosted(StatusBarNotification sbn, RankingMap map) {
1148             if (sbn.getUser().getIdentifier() != mUserId) {
1149                 return;
1150             }
1151             String shortcutId = sbn.getNotification().getShortcutId();
1152             PackageData packageData = getPackageIfConversationExists(sbn, conversationInfo -> {
1153                 synchronized (this) {
1154                     mActiveNotifCounts.merge(
1155                             Pair.create(sbn.getPackageName(), shortcutId), 1, Integer::sum);
1156                 }
1157             });
1158 
1159             if (packageData != null) {
1160                 Ranking rank = new Ranking();
1161                 map.getRanking(sbn.getKey(), rank);
1162                 ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId);
1163                 if (conversationInfo == null) {
1164                     return;
1165                 }
1166                 if (DEBUG) Log.d(TAG, "Last event from notification: " + sbn.getPostTime());
1167                 ConversationInfo.Builder updated = new ConversationInfo.Builder(conversationInfo)
1168                         .setLastEventTimestamp(sbn.getPostTime())
1169                         .setNotificationChannelId(rank.getChannel().getId());
1170                 if (!TextUtils.isEmpty(rank.getChannel().getParentChannelId())) {
1171                     updated.setParentNotificationChannelId(rank.getChannel().getParentChannelId());
1172                 } else {
1173                     updated.setParentNotificationChannelId(sbn.getNotification().getChannelId());
1174                 }
1175                 packageData.getConversationStore().addOrUpdate(updated.build());
1176 
1177                 EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory(
1178                         EventStore.CATEGORY_SHORTCUT_BASED, shortcutId);
1179                 eventHistory.addEvent(new Event(sbn.getPostTime(), Event.TYPE_NOTIFICATION_POSTED));
1180             }
1181         }
1182 
1183         @Override
onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap, int reason)1184         public synchronized void onNotificationRemoved(StatusBarNotification sbn,
1185                 RankingMap rankingMap, int reason) {
1186             if (sbn.getUser().getIdentifier() != mUserId) {
1187                 return;
1188             }
1189             String shortcutId = sbn.getNotification().getShortcutId();
1190             PackageData packageData = getPackageIfConversationExists(sbn, conversationInfo -> {
1191                 Pair<String, String> conversationKey =
1192                         Pair.create(sbn.getPackageName(), shortcutId);
1193                 synchronized (this) {
1194                     int count = mActiveNotifCounts.getOrDefault(conversationKey, 0) - 1;
1195                     if (count <= 0) {
1196                         mActiveNotifCounts.remove(conversationKey);
1197                         cleanupCachedShortcuts(mUserId, MAX_CACHED_RECENT_SHORTCUTS);
1198                     } else {
1199                         mActiveNotifCounts.put(conversationKey, count);
1200                     }
1201                 }
1202             });
1203 
1204             if (reason != REASON_CLICK || packageData == null) {
1205                 return;
1206             }
1207             long currentTime = System.currentTimeMillis();
1208             ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId);
1209             if (conversationInfo == null) {
1210                 return;
1211             }
1212             if (DEBUG) Log.d(TAG, "Last event from notification removed: " + currentTime);
1213             ConversationInfo updated = new ConversationInfo.Builder(conversationInfo)
1214                     .setLastEventTimestamp(currentTime)
1215                     .build();
1216             packageData.getConversationStore().addOrUpdate(updated);
1217 
1218             EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory(
1219                     EventStore.CATEGORY_SHORTCUT_BASED, shortcutId);
1220             eventHistory.addEvent(new Event(currentTime, Event.TYPE_NOTIFICATION_OPENED));
1221         }
1222 
1223         @Override
onNotificationChannelModified(String pkg, UserHandle user, NotificationChannel channel, int modificationType)1224         public void onNotificationChannelModified(String pkg, UserHandle user,
1225                 NotificationChannel channel, int modificationType) {
1226             if (user.getIdentifier() != mUserId) {
1227                 return;
1228             }
1229             PackageData packageData = getPackage(pkg, user.getIdentifier());
1230             String shortcutId = channel.getConversationId();
1231             if (packageData == null || shortcutId == null) {
1232                 return;
1233             }
1234             ConversationStore conversationStore = packageData.getConversationStore();
1235             ConversationInfo conversationInfo = conversationStore.getConversation(shortcutId);
1236             if (conversationInfo == null) {
1237                 return;
1238             }
1239             ConversationInfo.Builder builder = new ConversationInfo.Builder(conversationInfo);
1240             switch (modificationType) {
1241                 case NOTIFICATION_CHANNEL_OR_GROUP_ADDED:
1242                 case NOTIFICATION_CHANNEL_OR_GROUP_UPDATED:
1243                     builder.setNotificationChannelId(channel.getId());
1244                     builder.setImportant(channel.isImportantConversation());
1245                     builder.setDemoted(channel.isDemoted());
1246                     builder.setNotificationSilenced(
1247                             channel.getImportance() <= NotificationManager.IMPORTANCE_LOW);
1248                     builder.setBubbled(channel.canBubble());
1249                     break;
1250                 case NOTIFICATION_CHANNEL_OR_GROUP_DELETED:
1251                     // If the notification channel is deleted, revert all the notification settings
1252                     // to the default value.
1253                     builder.setNotificationChannelId(null);
1254                     builder.setImportant(false);
1255                     builder.setDemoted(false);
1256                     builder.setNotificationSilenced(false);
1257                     builder.setBubbled(false);
1258                     break;
1259             }
1260             updateConversationStoreThenNotifyListeners(conversationStore, builder.build(), pkg,
1261                     packageData.getUserId());
1262         }
1263 
hasActiveNotifications(String packageName, String shortcutId)1264         synchronized boolean hasActiveNotifications(String packageName, String shortcutId) {
1265             return mActiveNotifCounts.containsKey(Pair.create(packageName, shortcutId));
1266         }
1267     }
1268 
1269     /**
1270      * A {@link Runnable} that queries the Usage Stats Service for recent events for a specified
1271      * user.
1272      */
1273     private class UsageStatsQueryRunnable implements Runnable,
1274             UsageStatsQueryHelper.EventListener {
1275 
1276         private final UsageStatsQueryHelper mUsageStatsQueryHelper;
1277         private long mLastEventTimestamp;
1278 
UsageStatsQueryRunnable(int userId)1279         private UsageStatsQueryRunnable(int userId) {
1280             mUsageStatsQueryHelper = mInjector.createUsageStatsQueryHelper(userId,
1281                     (packageName) -> getPackage(packageName, userId), this);
1282             mLastEventTimestamp = System.currentTimeMillis() - QUERY_EVENTS_MAX_AGE_MS;
1283         }
1284 
1285         @Override
run()1286         public void run() {
1287             if (mUsageStatsQueryHelper.querySince(mLastEventTimestamp)) {
1288                 mLastEventTimestamp = mUsageStatsQueryHelper.getLastEventTimestamp();
1289             }
1290         }
1291 
1292         @Override
onEvent(PackageData packageData, ConversationInfo conversationInfo, Event event)1293         public void onEvent(PackageData packageData, ConversationInfo conversationInfo,
1294                 Event event) {
1295             if (event.getType() == Event.TYPE_IN_APP_CONVERSATION) {
1296                 if (DEBUG) Log.d(TAG, "Last event from in-app: " + event.getTimestamp());
1297                 ConversationInfo updated = new ConversationInfo.Builder(conversationInfo)
1298                         .setLastEventTimestamp(event.getTimestamp())
1299                         .build();
1300                 updateConversationStoreThenNotifyListeners(packageData.getConversationStore(),
1301                         updated,
1302                         packageData.getPackageName(), packageData.getUserId());
1303             }
1304         }
1305     }
1306 
1307     /** Adds {@code listener} to be notified on conversation changes. */
addConversationsListener( @onNull PeopleService.ConversationsListener listener)1308     public void addConversationsListener(
1309             @NonNull PeopleService.ConversationsListener listener) {
1310         synchronized (mConversationsListeners) {
1311             mConversationsListeners.add(Objects.requireNonNull(listener));
1312         }
1313     }
1314 
updateConversationStoreThenNotifyListeners(ConversationStore cs, ConversationInfo modifiedConv, String packageName, int userId)1315     private void updateConversationStoreThenNotifyListeners(ConversationStore cs,
1316             ConversationInfo modifiedConv,
1317             String packageName, int userId) {
1318         cs.addOrUpdate(modifiedConv);
1319         ConversationChannel channel = getConversationChannel(packageName, userId,
1320                 modifiedConv.getShortcutId(), modifiedConv);
1321         if (channel != null) {
1322             notifyConversationsListeners(Arrays.asList(channel));
1323         }
1324     }
1325 
updateConversationStoreThenNotifyListeners(ConversationStore cs, ConversationInfo modifiedConv, ShortcutInfo shortcutInfo)1326     private void updateConversationStoreThenNotifyListeners(ConversationStore cs,
1327             ConversationInfo modifiedConv, ShortcutInfo shortcutInfo) {
1328         cs.addOrUpdate(modifiedConv);
1329         ConversationChannel channel = getConversationChannel(shortcutInfo, modifiedConv);
1330         if (channel != null) {
1331             notifyConversationsListeners(Arrays.asList(channel));
1332         }
1333     }
1334 
1335 
1336     @VisibleForTesting
notifyConversationsListeners( @ullable final List<ConversationChannel> changedConversations)1337     void notifyConversationsListeners(
1338             @Nullable final List<ConversationChannel> changedConversations) {
1339         mHandler.post(() -> {
1340             try {
1341                 final List<PeopleService.ConversationsListener> copy;
1342                 synchronized (mLock) {
1343                     copy = new ArrayList<>(mConversationsListeners);
1344                 }
1345                 for (PeopleService.ConversationsListener listener : copy) {
1346                     listener.onConversationsUpdate(changedConversations);
1347                 }
1348             } catch (Exception e) {
1349             }
1350         });
1351     }
1352 
1353     /** A {@link BroadcastReceiver} that receives the intents for a specified user. */
1354     private class PerUserBroadcastReceiver extends BroadcastReceiver {
1355 
1356         private final int mUserId;
1357 
PerUserBroadcastReceiver(int userId)1358         private PerUserBroadcastReceiver(int userId) {
1359             mUserId = userId;
1360         }
1361 
1362         @Override
onReceive(Context context, Intent intent)1363         public void onReceive(Context context, Intent intent) {
1364             UserData userData = getUnlockedUserData(mUserId);
1365             if (userData == null) {
1366                 return;
1367             }
1368             if (TelecomManager.ACTION_DEFAULT_DIALER_CHANGED.equals(intent.getAction())) {
1369                 String defaultDialer = intent.getStringExtra(
1370                         TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME);
1371                 userData.setDefaultDialer(defaultDialer);
1372             } else if (SmsApplication.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL.equals(
1373                     intent.getAction())) {
1374                 updateDefaultSmsApp(userData);
1375             }
1376         }
1377     }
1378 
1379     private class PerUserPackageMonitor extends PackageMonitor {
1380 
1381         @Override
onPackageRemoved(String packageName, int uid)1382         public void onPackageRemoved(String packageName, int uid) {
1383             super.onPackageRemoved(packageName, uid);
1384 
1385             int userId = getChangingUserId();
1386             UserData userData = getUnlockedUserData(userId);
1387             if (userData != null) {
1388                 if (DEBUG) Log.d(TAG, "Delete package data for: " + packageName);
1389                 userData.deletePackageData(packageName);
1390             }
1391         }
1392     }
1393 
1394     private class ShutdownBroadcastReceiver extends BroadcastReceiver {
1395 
1396         @Override
onReceive(Context context, Intent intent)1397         public void onReceive(Context context, Intent intent) {
1398             forAllUnlockedUsers(userData -> userData.forAllPackages(PackageData::saveToDisk));
1399         }
1400     }
1401 
1402     @VisibleForTesting
1403     static class Injector {
1404 
createScheduledExecutor()1405         ScheduledExecutorService createScheduledExecutor() {
1406             return Executors.newSingleThreadScheduledExecutor();
1407         }
1408 
getBackgroundExecutor()1409         Executor getBackgroundExecutor() {
1410             return BackgroundThread.getExecutor();
1411         }
1412 
createContactsQueryHelper(Context context)1413         ContactsQueryHelper createContactsQueryHelper(Context context) {
1414             return new ContactsQueryHelper(context);
1415         }
1416 
createCallLogQueryHelper(Context context, BiConsumer<String, Event> eventConsumer)1417         CallLogQueryHelper createCallLogQueryHelper(Context context,
1418                 BiConsumer<String, Event> eventConsumer) {
1419             return new CallLogQueryHelper(context, eventConsumer);
1420         }
1421 
createMmsQueryHelper(Context context, BiConsumer<String, Event> eventConsumer)1422         MmsQueryHelper createMmsQueryHelper(Context context,
1423                 BiConsumer<String, Event> eventConsumer) {
1424             return new MmsQueryHelper(context, eventConsumer);
1425         }
1426 
createSmsQueryHelper(Context context, BiConsumer<String, Event> eventConsumer)1427         SmsQueryHelper createSmsQueryHelper(Context context,
1428                 BiConsumer<String, Event> eventConsumer) {
1429             return new SmsQueryHelper(context, eventConsumer);
1430         }
1431 
createUsageStatsQueryHelper(@serIdInt int userId, Function<String, PackageData> packageDataGetter, UsageStatsQueryHelper.EventListener eventListener)1432         UsageStatsQueryHelper createUsageStatsQueryHelper(@UserIdInt int userId,
1433                 Function<String, PackageData> packageDataGetter,
1434                 UsageStatsQueryHelper.EventListener eventListener) {
1435             return new UsageStatsQueryHelper(userId, packageDataGetter, eventListener);
1436         }
1437     }
1438 }
1439