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