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 private ContentObserver mCallLogContentObserver; 133 private ContentObserver mMmsSmsContentObserver; 134 135 private ShortcutServiceInternal mShortcutServiceInternal; 136 private PackageManagerInternal mPackageManagerInternal; 137 private NotificationManagerInternal mNotificationManagerInternal; 138 private UserManager mUserManager; 139 private ConversationStatusExpirationBroadcastReceiver mStatusExpReceiver; 140 DataManager(Context context)141 public DataManager(Context context) { 142 this(context, new Injector(), BackgroundThread.get().getLooper()); 143 } 144 DataManager(Context context, Injector injector, Looper looper)145 DataManager(Context context, Injector injector, Looper looper) { 146 mContext = context; 147 mInjector = injector; 148 mScheduledExecutor = mInjector.createScheduledExecutor(); 149 mHandler = new Handler(looper); 150 } 151 152 /** Initialization. Called when the system services are up running. */ initialize()153 public void initialize() { 154 mShortcutServiceInternal = LocalServices.getService(ShortcutServiceInternal.class); 155 mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); 156 mNotificationManagerInternal = LocalServices.getService(NotificationManagerInternal.class); 157 mUserManager = mContext.getSystemService(UserManager.class); 158 159 mShortcutServiceInternal.addShortcutChangeCallback(new ShortcutServiceCallback()); 160 161 mStatusExpReceiver = new ConversationStatusExpirationBroadcastReceiver(); 162 mContext.registerReceiver(mStatusExpReceiver, 163 ConversationStatusExpirationBroadcastReceiver.getFilter(), 164 Context.RECEIVER_NOT_EXPORTED); 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( 274 shortcutInfo, conversationInfo, packageName, userId, shortcutId); 275 } 276 277 @Nullable getConversationChannel(ShortcutInfo shortcutInfo, ConversationInfo conversationInfo, String packageName, int userId, String shortcutId)278 private ConversationChannel getConversationChannel(ShortcutInfo shortcutInfo, 279 ConversationInfo conversationInfo, String packageName, int userId, String shortcutId) { 280 if (conversationInfo == null || conversationInfo.isDemoted()) { 281 return null; 282 } 283 if (shortcutInfo == null) { 284 Slog.e(TAG, "Shortcut no longer found"); 285 mInjector.getBackgroundExecutor().execute( 286 () -> removeConversations(packageName, userId, Set.of(shortcutId))); 287 return null; 288 } 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 621 if (mBroadcastReceivers.get(userId) == null) { 622 BroadcastReceiver broadcastReceiver = new PerUserBroadcastReceiver(userId); 623 mBroadcastReceivers.put(userId, broadcastReceiver); 624 mContext.registerReceiverAsUser( 625 broadcastReceiver, UserHandle.of(userId), intentFilter, null, null); 626 } else { 627 // Stopped was not called on this user before setup is called again. This 628 // could happen during consecutive rapid user switching. 629 if (DEBUG) Log.d(TAG, "PerUserBroadcastReceiver was registered for: " + userId); 630 } 631 632 ContentObserver contactsContentObserver = new ContactsContentObserver( 633 BackgroundThread.getHandler()); 634 mContactsContentObservers.put(userId, contactsContentObserver); 635 mContext.getContentResolver().registerContentObserver( 636 Contacts.CONTENT_URI, /* notifyForDescendants= */ true, 637 contactsContentObserver, userId); 638 639 NotificationListener notificationListener = new NotificationListener(userId); 640 mNotificationListeners.put(userId, notificationListener); 641 try { 642 notificationListener.registerAsSystemService(mContext, 643 new ComponentName(mContext, getClass()), userId); 644 } catch (RemoteException e) { 645 // Should never occur for local calls. 646 } 647 648 if (mPackageMonitors.get(userId) == null) { 649 PackageMonitor packageMonitor = new PerUserPackageMonitor(); 650 packageMonitor.register(mContext, null, UserHandle.of(userId), true); 651 mPackageMonitors.put(userId, packageMonitor); 652 } else { 653 // Stopped was not called on this user before setup is called again. This 654 // could happen during consecutive rapid user switching. 655 if (DEBUG) Log.d(TAG, "PerUserPackageMonitor was registered for: " + userId); 656 } 657 658 if (userId == UserHandle.USER_SYSTEM) { 659 // The call log and MMS/SMS messages are shared across user profiles. So only need 660 // to register the content observers once for the primary user. 661 mCallLogContentObserver = new CallLogContentObserver(BackgroundThread.getHandler()); 662 mContext.getContentResolver().registerContentObserver( 663 CallLog.CONTENT_URI, /* notifyForDescendants= */ true, 664 mCallLogContentObserver, UserHandle.USER_SYSTEM); 665 666 mMmsSmsContentObserver = new MmsSmsContentObserver(BackgroundThread.getHandler()); 667 mContext.getContentResolver().registerContentObserver( 668 MmsSms.CONTENT_URI, /* notifyForDescendants= */ false, 669 mMmsSmsContentObserver, UserHandle.USER_SYSTEM); 670 } 671 672 DataMaintenanceService.scheduleJob(mContext, userId); 673 } 674 } 675 cleanupUser(@serIdInt int userId)676 private void cleanupUser(@UserIdInt int userId) { 677 synchronized (mLock) { 678 UserData userData = mUserDataArray.get(userId); 679 if (userData == null || userData.isUnlocked()) { 680 return; 681 } 682 ContentResolver contentResolver = mContext.getContentResolver(); 683 if (mUsageStatsQueryFutures.indexOfKey(userId) >= 0) { 684 mUsageStatsQueryFutures.get(userId).cancel(true); 685 } 686 if (mBroadcastReceivers.indexOfKey(userId) >= 0) { 687 mContext.unregisterReceiver(mBroadcastReceivers.get(userId)); 688 } 689 if (mContactsContentObservers.indexOfKey(userId) >= 0) { 690 contentResolver.unregisterContentObserver(mContactsContentObservers.get(userId)); 691 } 692 if (mNotificationListeners.indexOfKey(userId) >= 0) { 693 try { 694 mNotificationListeners.get(userId).unregisterAsSystemService(); 695 } catch (RemoteException e) { 696 // Should never occur for local calls. 697 } 698 } 699 if (mPackageMonitors.indexOfKey(userId) >= 0) { 700 mPackageMonitors.get(userId).unregister(); 701 } 702 if (userId == UserHandle.USER_SYSTEM) { 703 if (mCallLogContentObserver != null) { 704 contentResolver.unregisterContentObserver(mCallLogContentObserver); 705 mCallLogContentObserver = null; 706 } 707 if (mMmsSmsContentObserver != null) { 708 contentResolver.unregisterContentObserver(mMmsSmsContentObserver); 709 mCallLogContentObserver = null; 710 } 711 } 712 713 DataMaintenanceService.cancelJob(mContext, userId); 714 } 715 } 716 717 /** 718 * Converts {@code mimeType} to {@link Event.EventType}. 719 */ mimeTypeToShareEventType(String mimeType)720 public int mimeTypeToShareEventType(String mimeType) { 721 if (mimeType == null) { 722 return Event.TYPE_SHARE_OTHER; 723 } 724 if (mimeType.startsWith("text/")) { 725 return Event.TYPE_SHARE_TEXT; 726 } else if (mimeType.startsWith("image/")) { 727 return Event.TYPE_SHARE_IMAGE; 728 } else if (mimeType.startsWith("video/")) { 729 return Event.TYPE_SHARE_VIDEO; 730 } 731 return Event.TYPE_SHARE_OTHER; 732 } 733 pruneUninstalledPackageData(@onNull UserData userData)734 private void pruneUninstalledPackageData(@NonNull UserData userData) { 735 Set<String> installApps = new ArraySet<>(); 736 mPackageManagerInternal.forEachInstalledPackage( 737 pkg -> installApps.add(pkg.getPackageName()), userData.getUserId()); 738 List<String> packagesToDelete = new ArrayList<>(); 739 userData.forAllPackages(packageData -> { 740 if (!installApps.contains(packageData.getPackageName())) { 741 packagesToDelete.add(packageData.getPackageName()); 742 } 743 }); 744 for (String packageName : packagesToDelete) { 745 if (DEBUG) Log.d(TAG, "Deleting packages data for: " + packageName); 746 userData.deletePackageData(packageName); 747 } 748 } 749 750 /** Gets a list of {@link ShortcutInfo}s with the given shortcut IDs. */ getShortcuts( @onNull String packageName, @UserIdInt int userId, @Nullable List<String> shortcutIds)751 private List<ShortcutInfo> getShortcuts( 752 @NonNull String packageName, @UserIdInt int userId, 753 @Nullable List<String> shortcutIds) { 754 @ShortcutQuery.QueryFlags int queryFlags = ShortcutQuery.FLAG_MATCH_DYNAMIC 755 | ShortcutQuery.FLAG_MATCH_PINNED | ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER 756 | ShortcutQuery.FLAG_MATCH_CACHED | ShortcutQuery.FLAG_GET_PERSONS_DATA; 757 if (DEBUG) Log.d(TAG, " Get shortcuts with IDs: " + shortcutIds); 758 return mShortcutServiceInternal.getShortcuts( 759 UserHandle.USER_SYSTEM, mContext.getPackageName(), 760 /*changedSince=*/ 0, packageName, shortcutIds, /*locusIds=*/ null, 761 /*componentName=*/ null, queryFlags, userId, Process.myPid(), Process.myUid()); 762 } 763 forAllUnlockedUsers(Consumer<UserData> consumer)764 private void forAllUnlockedUsers(Consumer<UserData> consumer) { 765 for (int i = 0; i < mUserDataArray.size(); i++) { 766 int userId = mUserDataArray.keyAt(i); 767 UserData userData = mUserDataArray.get(userId); 768 if (userData.isUnlocked()) { 769 consumer.accept(userData); 770 } 771 } 772 } 773 774 @Nullable getUnlockedUserData(int userId)775 private UserData getUnlockedUserData(int userId) { 776 UserData userData = mUserDataArray.get(userId); 777 return userData != null && userData.isUnlocked() ? userData : null; 778 } 779 updateDefaultDialer(@onNull UserData userData)780 private void updateDefaultDialer(@NonNull UserData userData) { 781 TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class); 782 String defaultDialer = telecomManager != null 783 ? telecomManager.getDefaultDialerPackage( 784 new UserHandle(userData.getUserId())) : null; 785 userData.setDefaultDialer(defaultDialer); 786 } 787 updateDefaultSmsApp(@onNull UserData userData)788 private void updateDefaultSmsApp(@NonNull UserData userData) { 789 ComponentName component = SmsApplication.getDefaultSmsApplicationAsUser( 790 mContext, /* updateIfNeeded= */ false, UserHandle.of(userData.getUserId())); 791 String defaultSmsApp = component != null ? component.getPackageName() : null; 792 userData.setDefaultSmsApp(defaultSmsApp); 793 } 794 795 @Nullable getPackageIfConversationExists(StatusBarNotification sbn, Consumer<ConversationInfo> conversationConsumer)796 private PackageData getPackageIfConversationExists(StatusBarNotification sbn, 797 Consumer<ConversationInfo> conversationConsumer) { 798 Notification notification = sbn.getNotification(); 799 String shortcutId = notification.getShortcutId(); 800 if (shortcutId == null) { 801 return null; 802 } 803 PackageData packageData = getPackage(sbn.getPackageName(), 804 sbn.getUser().getIdentifier()); 805 if (packageData == null) { 806 return null; 807 } 808 ConversationInfo conversationInfo = 809 packageData.getConversationStore().getConversation(shortcutId); 810 if (conversationInfo == null) { 811 return null; 812 } 813 conversationConsumer.accept(conversationInfo); 814 return packageData; 815 } 816 isCachedRecentConversation(ConversationInfo conversationInfo)817 private boolean isCachedRecentConversation(ConversationInfo conversationInfo) { 818 return isEligibleForCleanUp(conversationInfo) 819 && conversationInfo.getLastEventTimestamp() > 0L; 820 } 821 822 /** 823 * Conversations that are cached and not customized are eligible for clean-up, even if they 824 * don't have an associated notification event with them. 825 */ isEligibleForCleanUp(ConversationInfo conversationInfo)826 private boolean isEligibleForCleanUp(ConversationInfo conversationInfo) { 827 return conversationInfo.isShortcutCachedForNotification() 828 && Objects.equals(conversationInfo.getNotificationChannelId(), 829 conversationInfo.getParentNotificationChannelId()); 830 } 831 hasActiveNotifications(String packageName, @UserIdInt int userId, String shortcutId)832 private boolean hasActiveNotifications(String packageName, @UserIdInt int userId, 833 String shortcutId) { 834 NotificationListener notificationListener = mNotificationListeners.get(userId); 835 return notificationListener != null 836 && notificationListener.hasActiveNotifications(packageName, shortcutId); 837 } 838 839 /** 840 * Cleans up the oldest cached shortcuts that don't have active notifications for the recent 841 * conversations. After the cleanup, normally, the total number of cached shortcuts will be 842 * less than or equal to the target count. However, there are exception cases: e.g. when all 843 * the existing cached shortcuts have active notifications. 844 */ cleanupCachedShortcuts(@serIdInt int userId, int targetCachedCount)845 private void cleanupCachedShortcuts(@UserIdInt int userId, int targetCachedCount) { 846 UserData userData = getUnlockedUserData(userId); 847 if (userData == null) { 848 return; 849 } 850 // pair of <package name, conversation info> 851 List<Pair<String, ConversationInfo>> cachedConvos = new ArrayList<>(); 852 userData.forAllPackages(packageData -> { 853 packageData.forAllConversations(conversationInfo -> { 854 if (isEligibleForCleanUp(conversationInfo)) { 855 cachedConvos.add( 856 Pair.create(packageData.getPackageName(), conversationInfo)); 857 } 858 }); 859 }); 860 if (cachedConvos.size() <= targetCachedCount) { 861 return; 862 } 863 int numToUncache = cachedConvos.size() - targetCachedCount; 864 // Max heap keeps the oldest cached conversations. 865 PriorityQueue<Pair<String, ConversationInfo>> maxHeap = new PriorityQueue<>( 866 numToUncache + 1, 867 Comparator.comparingLong((Pair<String, ConversationInfo> pair) -> 868 Math.max( 869 pair.second.getLastEventTimestamp(), 870 pair.second.getCreationTimestamp())).reversed()); 871 for (Pair<String, ConversationInfo> cached : cachedConvos) { 872 if (hasActiveNotifications(cached.first, userId, cached.second.getShortcutId())) { 873 continue; 874 } 875 maxHeap.offer(cached); 876 if (maxHeap.size() > numToUncache) { 877 maxHeap.poll(); 878 } 879 } 880 while (!maxHeap.isEmpty()) { 881 Pair<String, ConversationInfo> toUncache = maxHeap.poll(); 882 mShortcutServiceInternal.uncacheShortcuts(userId, 883 mContext.getPackageName(), toUncache.first, 884 Collections.singletonList(toUncache.second.getShortcutId()), 885 userId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS); 886 } 887 } 888 889 @VisibleForTesting 890 @WorkerThread addOrUpdateConversationInfo(@onNull ShortcutInfo shortcutInfo)891 void addOrUpdateConversationInfo(@NonNull ShortcutInfo shortcutInfo) { 892 UserData userData = getUnlockedUserData(shortcutInfo.getUserId()); 893 if (userData == null) { 894 return; 895 } 896 PackageData packageData = userData.getOrCreatePackageData(shortcutInfo.getPackage()); 897 ConversationStore conversationStore = packageData.getConversationStore(); 898 ConversationInfo oldConversationInfo = 899 conversationStore.getConversation(shortcutInfo.getId()); 900 if (oldConversationInfo == null) { 901 if (DEBUG) Log.d(TAG, "Nothing previously stored about conversation."); 902 } 903 ConversationInfo.Builder builder = oldConversationInfo != null 904 ? new ConversationInfo.Builder(oldConversationInfo) 905 : new ConversationInfo.Builder().setCreationTimestamp(System.currentTimeMillis()); 906 907 builder.setShortcutId(shortcutInfo.getId()); 908 builder.setLocusId(shortcutInfo.getLocusId()); 909 builder.setShortcutFlags(shortcutInfo.getFlags()); 910 builder.setContactUri(null); 911 builder.setContactPhoneNumber(null); 912 builder.setContactStarred(false); 913 914 if (shortcutInfo.getPersons() != null && shortcutInfo.getPersons().length != 0) { 915 Person person = shortcutInfo.getPersons()[0]; 916 builder.setPersonImportant(person.isImportant()); 917 builder.setPersonBot(person.isBot()); 918 String contactUri = person.getUri(); 919 if (contactUri != null) { 920 ContactsQueryHelper helper = mInjector.createContactsQueryHelper(mContext); 921 if (helper.query(contactUri)) { 922 builder.setContactUri(helper.getContactUri()); 923 builder.setContactStarred(helper.isStarred()); 924 builder.setContactPhoneNumber(helper.getPhoneNumber()); 925 } 926 } 927 } 928 updateConversationStoreThenNotifyListeners(conversationStore, builder.build(), 929 shortcutInfo); 930 } 931 932 @VisibleForTesting getContactsContentObserverForTesting(@serIdInt int userId)933 ContentObserver getContactsContentObserverForTesting(@UserIdInt int userId) { 934 return mContactsContentObservers.get(userId); 935 } 936 937 @VisibleForTesting getCallLogContentObserverForTesting()938 ContentObserver getCallLogContentObserverForTesting() { 939 return mCallLogContentObserver; 940 } 941 942 @VisibleForTesting getMmsSmsContentObserverForTesting()943 ContentObserver getMmsSmsContentObserverForTesting() { 944 return mMmsSmsContentObserver; 945 } 946 947 @VisibleForTesting getNotificationListenerServiceForTesting(@serIdInt int userId)948 NotificationListener getNotificationListenerServiceForTesting(@UserIdInt int userId) { 949 return mNotificationListeners.get(userId); 950 } 951 952 @VisibleForTesting getPackageMonitorForTesting(@serIdInt int userId)953 PackageMonitor getPackageMonitorForTesting(@UserIdInt int userId) { 954 return mPackageMonitors.get(userId); 955 } 956 957 @VisibleForTesting getUserDataForTesting(@serIdInt int userId)958 UserData getUserDataForTesting(@UserIdInt int userId) { 959 return mUserDataArray.get(userId); 960 } 961 962 /** Observer that observes the changes in the Contacts database. */ 963 private class ContactsContentObserver extends ContentObserver { 964 965 private long mLastUpdatedTimestamp; 966 ContactsContentObserver(Handler handler)967 private ContactsContentObserver(Handler handler) { 968 super(handler); 969 mLastUpdatedTimestamp = System.currentTimeMillis(); 970 } 971 972 @Override onChange(boolean selfChange, Uri uri, @UserIdInt int userId)973 public void onChange(boolean selfChange, Uri uri, @UserIdInt int userId) { 974 ContactsQueryHelper helper = mInjector.createContactsQueryHelper(mContext); 975 if (!helper.querySince(mLastUpdatedTimestamp)) { 976 return; 977 } 978 Uri contactUri = helper.getContactUri(); 979 980 final ConversationSelector conversationSelector = new ConversationSelector(); 981 UserData userData = getUnlockedUserData(userId); 982 if (userData == null) { 983 return; 984 } 985 userData.forAllPackages(packageData -> { 986 ConversationInfo ci = 987 packageData.getConversationStore().getConversationByContactUri(contactUri); 988 if (ci != null) { 989 conversationSelector.mConversationStore = 990 packageData.getConversationStore(); 991 conversationSelector.mConversationInfo = ci; 992 conversationSelector.mPackageName = packageData.getPackageName(); 993 } 994 }); 995 if (conversationSelector.mConversationInfo == null) { 996 return; 997 } 998 999 ConversationInfo.Builder builder = 1000 new ConversationInfo.Builder(conversationSelector.mConversationInfo); 1001 builder.setContactStarred(helper.isStarred()); 1002 builder.setContactPhoneNumber(helper.getPhoneNumber()); 1003 updateConversationStoreThenNotifyListeners(conversationSelector.mConversationStore, 1004 builder.build(), 1005 conversationSelector.mPackageName, userId); 1006 mLastUpdatedTimestamp = helper.getLastUpdatedTimestamp(); 1007 } 1008 1009 private class ConversationSelector { 1010 private ConversationStore mConversationStore = null; 1011 private ConversationInfo mConversationInfo = null; 1012 private String mPackageName = null; 1013 } 1014 } 1015 1016 /** Observer that observes the changes in the call log database. */ 1017 private class CallLogContentObserver extends ContentObserver implements 1018 BiConsumer<String, Event> { 1019 1020 private final CallLogQueryHelper mCallLogQueryHelper; 1021 private long mLastCallTimestamp; 1022 CallLogContentObserver(Handler handler)1023 private CallLogContentObserver(Handler handler) { 1024 super(handler); 1025 mCallLogQueryHelper = mInjector.createCallLogQueryHelper(mContext, this); 1026 mLastCallTimestamp = System.currentTimeMillis() - QUERY_EVENTS_MAX_AGE_MS; 1027 } 1028 1029 @Override onChange(boolean selfChange)1030 public void onChange(boolean selfChange) { 1031 if (mCallLogQueryHelper.querySince(mLastCallTimestamp)) { 1032 mLastCallTimestamp = mCallLogQueryHelper.getLastCallTimestamp(); 1033 } 1034 } 1035 1036 @Override accept(String phoneNumber, Event event)1037 public void accept(String phoneNumber, Event event) { 1038 forAllUnlockedUsers(userData -> { 1039 PackageData defaultDialer = userData.getDefaultDialer(); 1040 if (defaultDialer == null) { 1041 return; 1042 } 1043 ConversationStore conversationStore = defaultDialer.getConversationStore(); 1044 if (conversationStore.getConversationByPhoneNumber(phoneNumber) == null) { 1045 return; 1046 } 1047 EventStore eventStore = defaultDialer.getEventStore(); 1048 eventStore.getOrCreateEventHistory( 1049 EventStore.CATEGORY_CALL, phoneNumber).addEvent(event); 1050 }); 1051 } 1052 } 1053 1054 /** Observer that observes the changes in the MMS & SMS database. */ 1055 private class MmsSmsContentObserver extends ContentObserver implements 1056 BiConsumer<String, Event> { 1057 1058 private final MmsQueryHelper mMmsQueryHelper; 1059 private long mLastMmsTimestamp; 1060 1061 private final SmsQueryHelper mSmsQueryHelper; 1062 private long mLastSmsTimestamp; 1063 MmsSmsContentObserver(Handler handler)1064 private MmsSmsContentObserver(Handler handler) { 1065 super(handler); 1066 mMmsQueryHelper = mInjector.createMmsQueryHelper(mContext, this); 1067 mSmsQueryHelper = mInjector.createSmsQueryHelper(mContext, this); 1068 mLastSmsTimestamp = mLastMmsTimestamp = 1069 System.currentTimeMillis() - QUERY_EVENTS_MAX_AGE_MS; 1070 } 1071 1072 @Override onChange(boolean selfChange)1073 public void onChange(boolean selfChange) { 1074 if (mMmsQueryHelper.querySince(mLastMmsTimestamp)) { 1075 mLastMmsTimestamp = mMmsQueryHelper.getLastMessageTimestamp(); 1076 } 1077 if (mSmsQueryHelper.querySince(mLastSmsTimestamp)) { 1078 mLastSmsTimestamp = mSmsQueryHelper.getLastMessageTimestamp(); 1079 } 1080 } 1081 1082 @Override accept(String phoneNumber, Event event)1083 public void accept(String phoneNumber, Event event) { 1084 forAllUnlockedUsers(userData -> { 1085 PackageData defaultSmsApp = userData.getDefaultSmsApp(); 1086 if (defaultSmsApp == null) { 1087 return; 1088 } 1089 ConversationStore conversationStore = defaultSmsApp.getConversationStore(); 1090 if (conversationStore.getConversationByPhoneNumber(phoneNumber) == null) { 1091 return; 1092 } 1093 EventStore eventStore = defaultSmsApp.getEventStore(); 1094 eventStore.getOrCreateEventHistory( 1095 EventStore.CATEGORY_SMS, phoneNumber).addEvent(event); 1096 }); 1097 } 1098 } 1099 1100 /** Listener for the shortcut data changes. */ 1101 private class ShortcutServiceCallback implements LauncherApps.ShortcutChangeCallback { 1102 1103 @Override onShortcutsAddedOrUpdated(@onNull String packageName, @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user)1104 public void onShortcutsAddedOrUpdated(@NonNull String packageName, 1105 @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) { 1106 mInjector.getBackgroundExecutor().execute(() -> { 1107 PackageData packageData = getPackage(packageName, user.getIdentifier()); 1108 boolean hasCachedShortcut = false; 1109 for (ShortcutInfo shortcut : shortcuts) { 1110 if (ShortcutHelper.isConversationShortcut( 1111 shortcut, mShortcutServiceInternal, user.getIdentifier())) { 1112 if (shortcut.isCached()) { 1113 ConversationInfo conversationInfo = packageData != null 1114 ? packageData.getConversationInfo(shortcut.getId()) : null; 1115 if (conversationInfo == null 1116 || !conversationInfo.isShortcutCachedForNotification()) { 1117 hasCachedShortcut = true; 1118 } 1119 } 1120 addOrUpdateConversationInfo(shortcut); 1121 } 1122 } 1123 // Added at least one new conversation. Uncache older existing cached 1124 // shortcuts to ensure the cache size is under the limit. 1125 if (hasCachedShortcut) { 1126 cleanupCachedShortcuts(user.getIdentifier(), 1127 MAX_CACHED_RECENT_SHORTCUTS); 1128 } 1129 }); 1130 } 1131 1132 @Override onShortcutsRemoved(@onNull String packageName, @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user)1133 public void onShortcutsRemoved(@NonNull String packageName, 1134 @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) { 1135 mInjector.getBackgroundExecutor().execute(() -> { 1136 HashSet<String> shortcutIds = new HashSet<>(); 1137 for (ShortcutInfo shortcutInfo : shortcuts) { 1138 shortcutIds.add(shortcutInfo.getId()); 1139 } 1140 removeConversations(packageName, user.getIdentifier(), shortcutIds); 1141 }); 1142 } 1143 } 1144 removeConversations( @onNull String packageName, @NonNull int userId, @NonNull Set<String> shortcutIds)1145 private void removeConversations( 1146 @NonNull String packageName, @NonNull int userId, @NonNull Set<String> shortcutIds) { 1147 PackageData packageData = getPackage(packageName, userId); 1148 if (packageData != null) { 1149 for (String shortcutId : shortcutIds) { 1150 if (DEBUG) Log.d(TAG, "Deleting shortcut: " + shortcutId); 1151 packageData.deleteDataForConversation(shortcutId); 1152 } 1153 } 1154 try { 1155 int uid = mContext.getPackageManager().getPackageUidAsUser( 1156 packageName, userId); 1157 mNotificationManagerInternal.onConversationRemoved(packageName, uid, shortcutIds); 1158 } catch (PackageManager.NameNotFoundException e) { 1159 Slog.e(TAG, "Package not found when removing conversation: " + packageName, e); 1160 } 1161 } 1162 1163 /** Listener for the notifications and their settings changes. */ 1164 private class NotificationListener extends NotificationListenerService { 1165 1166 private final int mUserId; 1167 1168 // Conversation package name + shortcut ID -> Keys of active notifications 1169 @GuardedBy("this") 1170 private final Map<Pair<String, String>, Set<String>> mActiveNotifKeys = new ArrayMap<>(); 1171 NotificationListener(int userId)1172 private NotificationListener(int userId) { 1173 mUserId = userId; 1174 } 1175 1176 @Override onNotificationPosted(StatusBarNotification sbn, RankingMap map)1177 public void onNotificationPosted(StatusBarNotification sbn, RankingMap map) { 1178 if (sbn.getUser().getIdentifier() != mUserId) { 1179 return; 1180 } 1181 String shortcutId = sbn.getNotification().getShortcutId(); 1182 PackageData packageData = getPackageIfConversationExists(sbn, conversationInfo -> { 1183 synchronized (this) { 1184 Set<String> notificationKeys = mActiveNotifKeys.computeIfAbsent( 1185 Pair.create(sbn.getPackageName(), shortcutId), 1186 (unusedKey) -> new HashSet<>()); 1187 notificationKeys.add(sbn.getKey()); 1188 } 1189 }); 1190 1191 if (packageData != null) { 1192 Ranking rank = new Ranking(); 1193 map.getRanking(sbn.getKey(), rank); 1194 ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId); 1195 if (conversationInfo == null) { 1196 return; 1197 } 1198 if (DEBUG) Log.d(TAG, "Last event from notification: " + sbn.getPostTime()); 1199 ConversationInfo.Builder updated = new ConversationInfo.Builder(conversationInfo) 1200 .setLastEventTimestamp(sbn.getPostTime()) 1201 .setNotificationChannelId(rank.getChannel().getId()); 1202 if (!TextUtils.isEmpty(rank.getChannel().getParentChannelId())) { 1203 updated.setParentNotificationChannelId(rank.getChannel().getParentChannelId()); 1204 } else { 1205 updated.setParentNotificationChannelId(sbn.getNotification().getChannelId()); 1206 } 1207 packageData.getConversationStore().addOrUpdate(updated.build()); 1208 1209 EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory( 1210 EventStore.CATEGORY_SHORTCUT_BASED, shortcutId); 1211 eventHistory.addEvent(new Event(sbn.getPostTime(), Event.TYPE_NOTIFICATION_POSTED)); 1212 } 1213 } 1214 1215 @Override onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap, int reason)1216 public synchronized void onNotificationRemoved(StatusBarNotification sbn, 1217 RankingMap rankingMap, int reason) { 1218 if (sbn.getUser().getIdentifier() != mUserId) { 1219 return; 1220 } 1221 String shortcutId = sbn.getNotification().getShortcutId(); 1222 PackageData packageData = getPackageIfConversationExists(sbn, conversationInfo -> { 1223 Pair<String, String> conversationKey = 1224 Pair.create(sbn.getPackageName(), shortcutId); 1225 synchronized (this) { 1226 Set<String> notificationKeys = mActiveNotifKeys.computeIfAbsent( 1227 conversationKey, (unusedKey) -> new HashSet<>()); 1228 notificationKeys.remove(sbn.getKey()); 1229 if (notificationKeys.isEmpty()) { 1230 mActiveNotifKeys.remove(conversationKey); 1231 cleanupCachedShortcuts(mUserId, MAX_CACHED_RECENT_SHORTCUTS); 1232 } 1233 } 1234 }); 1235 1236 if (reason != REASON_CLICK || packageData == null) { 1237 return; 1238 } 1239 long currentTime = System.currentTimeMillis(); 1240 ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId); 1241 if (conversationInfo == null) { 1242 return; 1243 } 1244 if (DEBUG) Log.d(TAG, "Last event from notification removed: " + currentTime); 1245 ConversationInfo updated = new ConversationInfo.Builder(conversationInfo) 1246 .setLastEventTimestamp(currentTime) 1247 .build(); 1248 packageData.getConversationStore().addOrUpdate(updated); 1249 1250 EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory( 1251 EventStore.CATEGORY_SHORTCUT_BASED, shortcutId); 1252 eventHistory.addEvent(new Event(currentTime, Event.TYPE_NOTIFICATION_OPENED)); 1253 } 1254 1255 @Override onNotificationChannelModified(String pkg, UserHandle user, NotificationChannel channel, int modificationType)1256 public void onNotificationChannelModified(String pkg, UserHandle user, 1257 NotificationChannel channel, int modificationType) { 1258 if (user.getIdentifier() != mUserId) { 1259 return; 1260 } 1261 PackageData packageData = getPackage(pkg, user.getIdentifier()); 1262 String shortcutId = channel.getConversationId(); 1263 if (packageData == null || shortcutId == null) { 1264 return; 1265 } 1266 ConversationStore conversationStore = packageData.getConversationStore(); 1267 ConversationInfo conversationInfo = conversationStore.getConversation(shortcutId); 1268 if (conversationInfo == null) { 1269 return; 1270 } 1271 ConversationInfo.Builder builder = new ConversationInfo.Builder(conversationInfo); 1272 switch (modificationType) { 1273 case NOTIFICATION_CHANNEL_OR_GROUP_ADDED: 1274 case NOTIFICATION_CHANNEL_OR_GROUP_UPDATED: 1275 builder.setNotificationChannelId(channel.getId()); 1276 builder.setImportant(channel.isImportantConversation()); 1277 builder.setDemoted(channel.isDemoted()); 1278 builder.setNotificationSilenced( 1279 channel.getImportance() <= NotificationManager.IMPORTANCE_LOW); 1280 builder.setBubbled(channel.canBubble()); 1281 break; 1282 case NOTIFICATION_CHANNEL_OR_GROUP_DELETED: 1283 // If the notification channel is deleted, revert all the notification settings 1284 // to the default value. 1285 builder.setNotificationChannelId(null); 1286 builder.setImportant(false); 1287 builder.setDemoted(false); 1288 builder.setNotificationSilenced(false); 1289 builder.setBubbled(false); 1290 break; 1291 } 1292 updateConversationStoreThenNotifyListeners(conversationStore, builder.build(), pkg, 1293 packageData.getUserId()); 1294 } 1295 hasActiveNotifications(String packageName, String shortcutId)1296 synchronized boolean hasActiveNotifications(String packageName, String shortcutId) { 1297 return mActiveNotifKeys.containsKey(Pair.create(packageName, shortcutId)); 1298 } 1299 } 1300 1301 /** 1302 * A {@link Runnable} that queries the Usage Stats Service for recent events for a specified 1303 * user. 1304 */ 1305 private class UsageStatsQueryRunnable implements Runnable, 1306 UsageStatsQueryHelper.EventListener { 1307 1308 private final UsageStatsQueryHelper mUsageStatsQueryHelper; 1309 private long mLastEventTimestamp; 1310 UsageStatsQueryRunnable(int userId)1311 private UsageStatsQueryRunnable(int userId) { 1312 mUsageStatsQueryHelper = mInjector.createUsageStatsQueryHelper(userId, 1313 (packageName) -> getPackage(packageName, userId), this); 1314 mLastEventTimestamp = System.currentTimeMillis() - QUERY_EVENTS_MAX_AGE_MS; 1315 } 1316 1317 @Override run()1318 public void run() { 1319 if (mUsageStatsQueryHelper.querySince(mLastEventTimestamp)) { 1320 mLastEventTimestamp = mUsageStatsQueryHelper.getLastEventTimestamp(); 1321 } 1322 } 1323 1324 @Override onEvent(PackageData packageData, ConversationInfo conversationInfo, Event event)1325 public void onEvent(PackageData packageData, ConversationInfo conversationInfo, 1326 Event event) { 1327 if (event.getType() == Event.TYPE_IN_APP_CONVERSATION) { 1328 if (DEBUG) Log.d(TAG, "Last event from in-app: " + event.getTimestamp()); 1329 ConversationInfo updated = new ConversationInfo.Builder(conversationInfo) 1330 .setLastEventTimestamp(event.getTimestamp()) 1331 .build(); 1332 updateConversationStoreThenNotifyListeners(packageData.getConversationStore(), 1333 updated, 1334 packageData.getPackageName(), packageData.getUserId()); 1335 } 1336 } 1337 } 1338 1339 /** Adds {@code listener} to be notified on conversation changes. */ addConversationsListener( @onNull PeopleService.ConversationsListener listener)1340 public void addConversationsListener( 1341 @NonNull PeopleService.ConversationsListener listener) { 1342 synchronized (mLock) { 1343 mConversationsListeners.add(Objects.requireNonNull(listener)); 1344 } 1345 } 1346 1347 @VisibleForTesting updateConversationStoreThenNotifyListeners(ConversationStore cs, ConversationInfo modifiedConv, String packageName, int userId)1348 void updateConversationStoreThenNotifyListeners(ConversationStore cs, 1349 ConversationInfo modifiedConv, 1350 String packageName, int userId) { 1351 cs.addOrUpdate(modifiedConv); 1352 ConversationChannel channel = getConversationChannel(packageName, userId, 1353 modifiedConv.getShortcutId(), modifiedConv); 1354 if (channel != null) { 1355 notifyConversationsListeners(Arrays.asList(channel)); 1356 } 1357 } 1358 updateConversationStoreThenNotifyListeners(ConversationStore cs, ConversationInfo modifiedConv, @NonNull ShortcutInfo shortcutInfo)1359 private void updateConversationStoreThenNotifyListeners(ConversationStore cs, 1360 ConversationInfo modifiedConv, @NonNull ShortcutInfo shortcutInfo) { 1361 cs.addOrUpdate(modifiedConv); 1362 ConversationChannel channel = getConversationChannel( 1363 shortcutInfo, modifiedConv, shortcutInfo.getPackage(), shortcutInfo.getUserId(), 1364 shortcutInfo.getId()); 1365 if (channel != null) { 1366 notifyConversationsListeners(Arrays.asList(channel)); 1367 } 1368 } 1369 1370 1371 @VisibleForTesting notifyConversationsListeners( @ullable final List<ConversationChannel> changedConversations)1372 void notifyConversationsListeners( 1373 @Nullable final List<ConversationChannel> changedConversations) { 1374 mHandler.post(() -> { 1375 try { 1376 final List<PeopleService.ConversationsListener> copy; 1377 synchronized (mLock) { 1378 copy = new ArrayList<>(mConversationsListeners); 1379 } 1380 for (PeopleService.ConversationsListener listener : copy) { 1381 listener.onConversationsUpdate(changedConversations); 1382 } 1383 } catch (Exception e) { 1384 } 1385 }); 1386 } 1387 1388 /** A {@link BroadcastReceiver} that receives the intents for a specified user. */ 1389 private class PerUserBroadcastReceiver extends BroadcastReceiver { 1390 1391 private final int mUserId; 1392 PerUserBroadcastReceiver(int userId)1393 private PerUserBroadcastReceiver(int userId) { 1394 mUserId = userId; 1395 } 1396 1397 @Override onReceive(Context context, Intent intent)1398 public void onReceive(Context context, Intent intent) { 1399 UserData userData = getUnlockedUserData(mUserId); 1400 if (userData == null) { 1401 return; 1402 } 1403 if (TelecomManager.ACTION_DEFAULT_DIALER_CHANGED.equals(intent.getAction())) { 1404 String defaultDialer = intent.getStringExtra( 1405 TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME); 1406 userData.setDefaultDialer(defaultDialer); 1407 } else if (SmsApplication.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL.equals( 1408 intent.getAction())) { 1409 updateDefaultSmsApp(userData); 1410 } 1411 } 1412 } 1413 1414 private class PerUserPackageMonitor extends PackageMonitor { 1415 1416 @Override onPackageRemoved(String packageName, int uid)1417 public void onPackageRemoved(String packageName, int uid) { 1418 super.onPackageRemoved(packageName, uid); 1419 1420 int userId = getChangingUserId(); 1421 UserData userData = getUnlockedUserData(userId); 1422 if (userData != null) { 1423 if (DEBUG) Log.d(TAG, "Delete package data for: " + packageName); 1424 userData.deletePackageData(packageName); 1425 } 1426 } 1427 } 1428 1429 private class ShutdownBroadcastReceiver extends BroadcastReceiver { 1430 1431 @Override onReceive(Context context, Intent intent)1432 public void onReceive(Context context, Intent intent) { 1433 forAllUnlockedUsers(userData -> userData.forAllPackages(PackageData::saveToDisk)); 1434 } 1435 } 1436 1437 @VisibleForTesting 1438 static class Injector { 1439 createScheduledExecutor()1440 ScheduledExecutorService createScheduledExecutor() { 1441 return Executors.newSingleThreadScheduledExecutor(); 1442 } 1443 getBackgroundExecutor()1444 Executor getBackgroundExecutor() { 1445 return BackgroundThread.getExecutor(); 1446 } 1447 createContactsQueryHelper(Context context)1448 ContactsQueryHelper createContactsQueryHelper(Context context) { 1449 return new ContactsQueryHelper(context); 1450 } 1451 createCallLogQueryHelper(Context context, BiConsumer<String, Event> eventConsumer)1452 CallLogQueryHelper createCallLogQueryHelper(Context context, 1453 BiConsumer<String, Event> eventConsumer) { 1454 return new CallLogQueryHelper(context, eventConsumer); 1455 } 1456 createMmsQueryHelper(Context context, BiConsumer<String, Event> eventConsumer)1457 MmsQueryHelper createMmsQueryHelper(Context context, 1458 BiConsumer<String, Event> eventConsumer) { 1459 return new MmsQueryHelper(context, eventConsumer); 1460 } 1461 createSmsQueryHelper(Context context, BiConsumer<String, Event> eventConsumer)1462 SmsQueryHelper createSmsQueryHelper(Context context, 1463 BiConsumer<String, Event> eventConsumer) { 1464 return new SmsQueryHelper(context, eventConsumer); 1465 } 1466 createUsageStatsQueryHelper(@serIdInt int userId, Function<String, PackageData> packageDataGetter, UsageStatsQueryHelper.EventListener eventListener)1467 UsageStatsQueryHelper createUsageStatsQueryHelper(@UserIdInt int userId, 1468 Function<String, PackageData> packageDataGetter, 1469 UsageStatsQueryHelper.EventListener eventListener) { 1470 return new UsageStatsQueryHelper(userId, packageDataGetter, eventListener); 1471 } 1472 } 1473 } 1474