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