1 /* 2 * Copyright (C) 2017 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.launcher3.model; 18 19 import static com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN; 20 import static com.android.launcher3.Flags.enableLauncherBrMetricsFixed; 21 import static com.android.launcher3.Flags.enableSmartspaceAsAWidget; 22 import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle; 23 import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE; 24 import static com.android.launcher3.LauncherPrefs.SHOULD_SHOW_SMARTSPACE; 25 import static com.android.launcher3.LauncherSettings.Favorites.DESKTOP_ICON_FLAG; 26 import static com.android.launcher3.icons.CacheableShortcutInfo.convertShortcutsToCacheableShortcuts; 27 import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG; 28 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION; 29 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED; 30 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION; 31 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED; 32 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED; 33 import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER; 34 import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter; 35 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE; 36 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; 37 import static com.android.launcher3.util.LooperExecutor.CALLER_LOADER_TASK; 38 import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission; 39 40 import android.appwidget.AppWidgetProviderInfo; 41 import android.content.Context; 42 import android.content.Intent; 43 import android.content.IntentFilter; 44 import android.content.pm.LauncherActivityInfo; 45 import android.content.pm.LauncherApps; 46 import android.content.pm.PackageInstaller; 47 import android.content.pm.PackageInstaller.SessionInfo; 48 import android.content.pm.PackageManager; 49 import android.content.pm.ShortcutInfo; 50 import android.os.Bundle; 51 import android.os.Trace; 52 import android.os.UserHandle; 53 import android.os.UserManager; 54 import android.provider.Settings; 55 import android.util.Log; 56 import android.util.LongSparseArray; 57 58 import androidx.annotation.NonNull; 59 import androidx.annotation.Nullable; 60 import androidx.annotation.VisibleForTesting; 61 import androidx.annotation.WorkerThread; 62 63 import com.android.launcher3.Flags; 64 import com.android.launcher3.InvariantDeviceProfile; 65 import com.android.launcher3.LauncherModel; 66 import com.android.launcher3.LauncherPrefs; 67 import com.android.launcher3.LauncherSettings.Favorites; 68 import com.android.launcher3.Utilities; 69 import com.android.launcher3.backuprestore.LauncherRestoreEventLogger; 70 import com.android.launcher3.config.FeatureFlags; 71 import com.android.launcher3.dagger.ApplicationContext; 72 import com.android.launcher3.folder.Folder; 73 import com.android.launcher3.folder.FolderGridOrganizer; 74 import com.android.launcher3.folder.FolderNameInfos; 75 import com.android.launcher3.folder.FolderNameProvider; 76 import com.android.launcher3.icons.CacheableShortcutCachingLogic; 77 import com.android.launcher3.icons.CacheableShortcutInfo; 78 import com.android.launcher3.icons.IconCache; 79 import com.android.launcher3.icons.cache.CachedObject; 80 import com.android.launcher3.icons.cache.CachedObjectCachingLogic; 81 import com.android.launcher3.icons.cache.IconCacheUpdateHandler; 82 import com.android.launcher3.icons.cache.LauncherActivityCachingLogic; 83 import com.android.launcher3.logging.FileLog; 84 import com.android.launcher3.model.LoaderCursor.LoaderCursorFactory; 85 import com.android.launcher3.model.data.AppInfo; 86 import com.android.launcher3.model.data.AppPairInfo; 87 import com.android.launcher3.model.data.FolderInfo; 88 import com.android.launcher3.model.data.IconRequestInfo; 89 import com.android.launcher3.model.data.ItemInfo; 90 import com.android.launcher3.model.data.LauncherAppWidgetInfo; 91 import com.android.launcher3.model.data.WorkspaceItemInfo; 92 import com.android.launcher3.pm.InstallSessionHelper; 93 import com.android.launcher3.pm.PackageInstallInfo; 94 import com.android.launcher3.pm.UserCache; 95 import com.android.launcher3.shortcuts.ShortcutKey; 96 import com.android.launcher3.shortcuts.ShortcutRequest; 97 import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult; 98 import com.android.launcher3.util.ApiWrapper; 99 import com.android.launcher3.util.ComponentKey; 100 import com.android.launcher3.util.IOUtils; 101 import com.android.launcher3.util.IntArray; 102 import com.android.launcher3.util.IntSet; 103 import com.android.launcher3.util.LooperIdleLock; 104 import com.android.launcher3.util.PackageManagerHelper; 105 import com.android.launcher3.util.PackageUserKey; 106 import com.android.launcher3.util.TraceHelper; 107 import com.android.launcher3.widget.WidgetInflater; 108 109 import dagger.assisted.Assisted; 110 import dagger.assisted.AssistedFactory; 111 import dagger.assisted.AssistedInject; 112 113 import java.util.ArrayList; 114 import java.util.Arrays; 115 import java.util.HashMap; 116 import java.util.HashSet; 117 import java.util.List; 118 import java.util.Map; 119 import java.util.Objects; 120 import java.util.Optional; 121 import java.util.Set; 122 import java.util.concurrent.CancellationException; 123 124 import javax.inject.Named; 125 126 /** 127 * Runnable for the thread that loads the contents of the launcher: 128 * - workspace icons 129 * - widgets 130 * - all apps icons 131 * - deep shortcuts within apps 132 */ 133 @SuppressWarnings("NewApi") 134 public class LoaderTask implements Runnable { 135 private static final String TAG = "LoaderTask"; 136 public static final String SMARTSPACE_ON_HOME_SCREEN = "pref_smartspace_home_screen"; 137 138 private static final boolean DEBUG = true; 139 140 private final Context mContext; 141 private final LauncherModel mModel; 142 private final InvariantDeviceProfile mIDP; 143 private final boolean mIsSafeModeEnabled; 144 private final AllAppsList mBgAllAppsList; 145 protected final BgDataModel mBgDataModel; 146 private final LoaderCursorFactory mLoaderCursorFactory; 147 148 private final ModelDelegate mModelDelegate; 149 private boolean mIsRestoreFromBackup; 150 151 private FirstScreenBroadcast mFirstScreenBroadcast; 152 153 @NonNull 154 private final BaseLauncherBinder mLauncherBinder; 155 156 private final LauncherApps mLauncherApps; 157 private final UserManager mUserManager; 158 private final UserCache mUserCache; 159 private final PackageManagerHelper mPmHelper; 160 161 private final InstallSessionHelper mSessionHelper; 162 private final IconCache mIconCache; 163 164 private final UserManagerState mUserManagerState; 165 private Map<ShortcutKey, ShortcutInfo> mShortcutKeyToPinnedShortcuts; 166 private HashMap<PackageUserKey, SessionInfo> mInstallingPkgsCached; 167 168 private List<IconRequestInfo<WorkspaceItemInfo>> mWorkspaceIconRequestInfos = new ArrayList<>(); 169 170 private boolean mStopped; 171 172 private final Set<PackageUserKey> mPendingPackages = new HashSet<>(); 173 private boolean mItemsDeleted = false; 174 private String mDbName; 175 176 @AssistedInject LoaderTask( @pplicationContext Context context, InvariantDeviceProfile idp, LauncherModel model, UserCache userCache, PackageManagerHelper pmHelper, InstallSessionHelper sessionHelper, IconCache iconCache, AllAppsList bgAllAppsList, BgDataModel bgModel, LoaderCursorFactory loaderCursorFactory, @Named("SAFE_MODE") boolean isSafeModeEnabled, @Assisted @NonNull BaseLauncherBinder launcherBinder, @Assisted UserManagerState userManagerState)177 LoaderTask( 178 @ApplicationContext Context context, 179 InvariantDeviceProfile idp, 180 LauncherModel model, 181 UserCache userCache, 182 PackageManagerHelper pmHelper, 183 InstallSessionHelper sessionHelper, 184 IconCache iconCache, 185 AllAppsList bgAllAppsList, 186 BgDataModel bgModel, 187 LoaderCursorFactory loaderCursorFactory, 188 @Named("SAFE_MODE") boolean isSafeModeEnabled, 189 @Assisted @NonNull BaseLauncherBinder launcherBinder, 190 @Assisted UserManagerState userManagerState) { 191 mContext = context; 192 mIDP = idp; 193 mModel = model; 194 mIsSafeModeEnabled = isSafeModeEnabled; 195 mBgAllAppsList = bgAllAppsList; 196 mBgDataModel = bgModel; 197 mModelDelegate = model.getModelDelegate(); 198 mLauncherBinder = launcherBinder; 199 mLoaderCursorFactory = loaderCursorFactory; 200 mLauncherApps = mContext.getSystemService(LauncherApps.class); 201 mUserManager = mContext.getSystemService(UserManager.class); 202 mUserCache = userCache; 203 mPmHelper = pmHelper; 204 mSessionHelper = sessionHelper; 205 mIconCache = iconCache; 206 mUserManagerState = userManagerState; 207 mInstallingPkgsCached = null; 208 } 209 waitForIdle()210 protected synchronized void waitForIdle() { 211 // Wait until the either we're stopped or the other threads are done. 212 // This way we don't start loading all apps until the workspace has settled 213 // down. 214 LooperIdleLock idleLock = mLauncherBinder.newIdleLock(this); 215 // Just in case mFlushingWorkerThread changes but we aren't woken up, 216 // wait no longer than 1sec at a time 217 while (!mStopped && idleLock.awaitLocked(1000)); 218 } 219 verifyNotStopped()220 private synchronized void verifyNotStopped() throws CancellationException { 221 if (mStopped) { 222 throw new CancellationException("Loader stopped"); 223 } 224 } 225 sendFirstScreenActiveInstallsBroadcast()226 private void sendFirstScreenActiveInstallsBroadcast() { 227 // Screen set is never empty 228 IntArray allScreens = mBgDataModel.collectWorkspaceScreens(); 229 final int firstScreen = allScreens.get(0); 230 IntSet firstScreens = IntSet.wrap(firstScreen); 231 232 List<ItemInfo> firstScreenItems = 233 mBgDataModel.itemsIdMap.stream() 234 .filter(currentScreenContentFilter(firstScreens)) 235 .toList(); 236 final int disableArchivingLauncherBroadcast = Settings.Secure.getInt( 237 mContext.getContentResolver(), 238 "disable_launcher_broadcast_installed_apps", 239 /* default */ 0); 240 boolean shouldAttachArchivingExtras = mIsRestoreFromBackup 241 && disableArchivingLauncherBroadcast == 0 242 && Flags.enableFirstScreenBroadcastArchivingExtras(); 243 if (shouldAttachArchivingExtras) { 244 List<FirstScreenBroadcastModel> broadcastModels = 245 FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast( 246 mPmHelper, 247 firstScreenItems, 248 mInstallingPkgsCached, 249 mBgDataModel.itemsIdMap.stream().filter(WIDGET_FILTER).toList() 250 ); 251 logASplit("Sending first screen broadcast with additional archiving Extras"); 252 FirstScreenBroadcastHelper.sendBroadcastsForModels(mContext, broadcastModels); 253 } else { 254 logASplit("Sending first screen broadcast"); 255 mFirstScreenBroadcast.sendBroadcasts(mContext, firstScreenItems); 256 } 257 } 258 run()259 public void run() { 260 synchronized (this) { 261 // Skip fast if we are already stopped. 262 if (mStopped) { 263 return; 264 } 265 } 266 267 TraceHelper.INSTANCE.beginSection(TAG); 268 MODEL_EXECUTOR.elevatePriority(CALLER_LOADER_TASK); 269 LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger(); 270 mIsRestoreFromBackup = 271 LauncherPrefs.get(mContext).get(IS_FIRST_LOAD_AFTER_RESTORE); 272 LauncherRestoreEventLogger restoreEventLogger = null; 273 if (enableLauncherBrMetricsFixed()) { 274 restoreEventLogger = LauncherRestoreEventLogger.Companion.newInstance(mContext); 275 } 276 try (LauncherModel.LoaderTransaction transaction = mModel.beginLoader(this)) { 277 List<CacheableShortcutInfo> allShortcuts = new ArrayList<>(); 278 loadWorkspace(allShortcuts, "", new HashMap<>(), memoryLogger, restoreEventLogger); 279 280 // Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from db. 281 // sanitizeData should not be invoked if the workspace is loaded from a db different 282 // from the main db as defined in the invariant device profile. 283 // (e.g. both grid preview and minimal device mode uses a different db) 284 // TODO(b/384731096): Write Unit Test to make sure sanitizeWidgetsShortcutsAndPackages 285 // actually re-pins shortcuts that are in model but not in ShortcutManager, if possible 286 // after a simulated restore. 287 if (Objects.equals(mIDP.dbFile, mDbName)) { 288 verifyNotStopped(); 289 sanitizeFolders(mItemsDeleted); 290 sanitizeAppPairs(); 291 sanitizeWidgetsShortcutsAndPackages(); 292 logASplit("sanitizeData finished"); 293 } 294 295 verifyNotStopped(); 296 mLauncherBinder.bindWorkspace(true /* incrementBindId */, /* isBindSync= */ false); 297 logASplit("bindWorkspace finished"); 298 299 mModelDelegate.workspaceLoadComplete(); 300 // Notify the installer packages of packages with active installs on the first screen. 301 sendFirstScreenActiveInstallsBroadcast(); 302 303 // Take a break 304 waitForIdle(); 305 logASplit("step 1 loading workspace complete"); 306 verifyNotStopped(); 307 308 // second step 309 Trace.beginSection("LoadAllApps"); 310 List<LauncherActivityInfo> allActivityList; 311 try { 312 allActivityList = loadAllApps(); 313 } finally { 314 Trace.endSection(); 315 } 316 logASplit("loadAllApps finished"); 317 318 verifyNotStopped(); 319 mLauncherBinder.bindAllApps(); 320 logASplit("bindAllApps finished"); 321 322 verifyNotStopped(); 323 IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler(); 324 setIgnorePackages(updateHandler); 325 updateHandler.updateIcons(allActivityList, 326 LauncherActivityCachingLogic.INSTANCE, 327 mModel::onPackageIconsUpdated); 328 logASplit("update AllApps icon cache finished"); 329 330 verifyNotStopped(); 331 logASplit("saving all shortcuts in icon cache"); 332 updateHandler.updateIcons(allShortcuts, CacheableShortcutCachingLogic.INSTANCE, 333 mModel::onPackageIconsUpdated); 334 335 // Take a break 336 waitForIdle(); 337 logASplit("step 2 loading AllApps complete"); 338 verifyNotStopped(); 339 340 // third step 341 List<ShortcutInfo> allDeepShortcuts = loadDeepShortcuts(); 342 logASplit("loadDeepShortcuts finished"); 343 344 verifyNotStopped(); 345 mLauncherBinder.bindDeepShortcuts(); 346 logASplit("bindDeepShortcuts finished"); 347 348 verifyNotStopped(); 349 logASplit("saving deep shortcuts in icon cache"); 350 updateHandler.updateIcons( 351 convertShortcutsToCacheableShortcuts(allDeepShortcuts, allActivityList), 352 CacheableShortcutCachingLogic.INSTANCE, 353 (pkgs, user) -> { }); 354 355 // Take a break 356 waitForIdle(); 357 logASplit("step 3 loading all shortcuts complete"); 358 verifyNotStopped(); 359 360 // fourth step 361 WidgetsModel widgetsModel = mBgDataModel.widgetsModel; 362 List<CachedObject> allWidgetsList = widgetsModel.update(/*packageUser=*/null); 363 logASplit("load widgets finished"); 364 365 verifyNotStopped(); 366 mLauncherBinder.bindWidgets(); 367 logASplit("bindWidgets finished"); 368 verifyNotStopped(); 369 LauncherPrefs prefs = LauncherPrefs.get(mContext); 370 371 if (enableSmartspaceAsAWidget() && prefs.get(SHOULD_SHOW_SMARTSPACE)) { 372 mLauncherBinder.bindSmartspaceWidget(); 373 // Turn off pref. 374 prefs.putSync(SHOULD_SHOW_SMARTSPACE.to(false)); 375 logASplit("bindSmartspaceWidget finished"); 376 verifyNotStopped(); 377 } else if (!enableSmartspaceAsAWidget() && WIDGET_ON_FIRST_SCREEN 378 && !prefs.get(LauncherPrefs.SHOULD_SHOW_SMARTSPACE)) { 379 // Turn on pref. 380 prefs.putSync(SHOULD_SHOW_SMARTSPACE.to(true)); 381 } 382 383 logASplit("saving all widgets in icon cache"); 384 updateHandler.updateIcons(allWidgetsList, 385 CachedObjectCachingLogic.INSTANCE, 386 mModel::onWidgetLabelsUpdated); 387 388 // fifth step 389 loadFolderNames(); 390 391 verifyNotStopped(); 392 updateHandler.finish(); 393 logASplit("finish icon update"); 394 395 mModelDelegate.modelLoadComplete(); 396 transaction.commit(); 397 memoryLogger.clearLogs(); 398 if (mIsRestoreFromBackup) { 399 mIsRestoreFromBackup = false; 400 LauncherPrefs.get(mContext).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(false)); 401 if (restoreEventLogger != null) { 402 restoreEventLogger.reportLauncherRestoreResults(); 403 } 404 } 405 } catch (CancellationException e) { 406 // Loader stopped, ignore 407 FileLog.w(TAG, "LoaderTask cancelled"); 408 } catch (Exception e) { 409 memoryLogger.printLogs(); 410 throw e; 411 } 412 MODEL_EXECUTOR.restorePriority(CALLER_LOADER_TASK); 413 TraceHelper.INSTANCE.endSection(); 414 } 415 stopLocked()416 public synchronized void stopLocked() { 417 FileLog.w(TAG, "stopLocked: Loader stopping"); 418 mStopped = true; 419 this.notify(); 420 } 421 loadWorkspaceForPreview(String selection, Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap)422 public void loadWorkspaceForPreview(String selection, 423 Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) { 424 loadWorkspace(new ArrayList<>(), selection, widgetProviderInfoMap, null, null); 425 } 426 loadWorkspace( List<CacheableShortcutInfo> allDeepShortcuts, String selection, Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap, @Nullable LoaderMemoryLogger memoryLogger, @Nullable LauncherRestoreEventLogger restoreEventLogger )427 private void loadWorkspace( 428 List<CacheableShortcutInfo> allDeepShortcuts, 429 String selection, 430 Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap, 431 @Nullable LoaderMemoryLogger memoryLogger, 432 @Nullable LauncherRestoreEventLogger restoreEventLogger 433 ) { 434 Trace.beginSection("LoadWorkspace"); 435 try { 436 loadWorkspaceImpl(allDeepShortcuts, selection, widgetProviderInfoMap, 437 memoryLogger, restoreEventLogger); 438 } finally { 439 Trace.endSection(); 440 } 441 logASplit("loadWorkspace finished"); 442 443 mBgDataModel.isFirstPagePinnedItemEnabled = FeatureFlags.QSB_ON_FIRST_SCREEN 444 && (!enableSmartspaceRemovalToggle() 445 || LauncherPrefs.getPrefs(mContext).getBoolean(SMARTSPACE_ON_HOME_SCREEN, true)); 446 } 447 loadWorkspaceImpl( List<CacheableShortcutInfo> allDeepShortcuts, String selection, Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap, @Nullable LoaderMemoryLogger memoryLogger, @Nullable LauncherRestoreEventLogger restoreEventLogger)448 private void loadWorkspaceImpl( 449 List<CacheableShortcutInfo> allDeepShortcuts, 450 String selection, 451 Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap, 452 @Nullable LoaderMemoryLogger memoryLogger, 453 @Nullable LauncherRestoreEventLogger restoreEventLogger) { 454 final boolean isSdCardReady = Utilities.isBootCompleted(); 455 final WidgetInflater widgetInflater = new WidgetInflater(mContext, mIsSafeModeEnabled); 456 457 ModelDbController dbController = mModel.getModelDbController(); 458 if (Flags.gridMigrationRefactor()) { 459 try { 460 dbController.attemptMigrateDb(restoreEventLogger, mModelDelegate); 461 } catch (Exception e) { 462 FileLog.e(TAG, "Failed to migrate grid", e); 463 } 464 } else { 465 dbController.tryMigrateDB(restoreEventLogger, mModelDelegate); 466 } 467 Log.d(TAG, "loadWorkspace: loading default favorites if necessary"); 468 dbController.loadDefaultFavoritesIfNecessary(); 469 470 synchronized (mBgDataModel) { 471 mBgDataModel.clear(); 472 mPendingPackages.clear(); 473 474 final HashMap<PackageUserKey, SessionInfo> installingPkgs = 475 mSessionHelper.getActiveSessions(); 476 if (Flags.enableSupportForArchiving()) { 477 mInstallingPkgsCached = installingPkgs; 478 } 479 installingPkgs.forEach(mIconCache::updateSessionCache); 480 FileLog.d(TAG, "loadWorkspace: Packages with active install/update sessions: " 481 + installingPkgs.keySet().stream().map(info -> info.mPackageName).toList()); 482 483 mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs); 484 485 mShortcutKeyToPinnedShortcuts = new HashMap<>(); 486 final LoaderCursor c = mLoaderCursorFactory.createLoaderCursor( 487 dbController.query(null, selection, null, null), 488 mUserManagerState, 489 mIsRestoreFromBackup ? restoreEventLogger : null); 490 final Bundle extras = c.getExtras(); 491 mDbName = extras == null ? null : extras.getString(ModelDbController.EXTRA_DB_NAME); 492 try { 493 final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>(); 494 queryPinnedShortcutsForUnlockedUsers(mContext, unlockedUsers); 495 496 mWorkspaceIconRequestInfos = new ArrayList<>(); 497 WorkspaceItemProcessor itemProcessor = new WorkspaceItemProcessor(c, memoryLogger, 498 mUserCache, mUserManagerState, mLauncherApps, mPendingPackages, 499 mShortcutKeyToPinnedShortcuts, mContext, mIDP, mIconCache, 500 mIsSafeModeEnabled, mBgDataModel, 501 widgetProviderInfoMap, installingPkgs, isSdCardReady, 502 widgetInflater, mPmHelper, mWorkspaceIconRequestInfos, unlockedUsers, 503 allDeepShortcuts); 504 505 if (mStopped) { 506 Log.w(TAG, "loadWorkspaceImpl: Loader stopped, skipping item processing"); 507 } else { 508 while (!mStopped && c.moveToNext()) { 509 itemProcessor.processItem(); 510 } 511 } 512 tryLoadWorkspaceIconsInBulk(mWorkspaceIconRequestInfos); 513 } finally { 514 IOUtils.closeSilently(c); 515 } 516 517 mModelDelegate.loadAndBindWorkspaceItems(mUserManagerState, 518 mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts); 519 mModelDelegate.loadAndBindAllAppsItems(mUserManagerState, 520 mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts); 521 mModelDelegate.loadAndBindOtherItems(mLauncherBinder.mCallbacksList); 522 mModelDelegate.markActive(); 523 524 // Break early if we've stopped loading 525 if (mStopped) { 526 mBgDataModel.clear(); 527 return; 528 } 529 530 // Remove dead items 531 mItemsDeleted = c.commitDeleted(); 532 533 processFolderItems(); 534 processAppPairItems(); 535 536 c.commitRestoredItems(); 537 } 538 } 539 540 /** 541 * After all items have been processed and added to the BgDataModel, this method sorts and 542 * requests high-res icons for the items that are part of an app pair. 543 */ processAppPairItems()544 private void processAppPairItems() { 545 mBgDataModel.itemsIdMap.stream() 546 .filter(item -> item instanceof AppPairInfo) 547 .forEach(item -> { 548 AppPairInfo appPair = (AppPairInfo) item; 549 appPair.getContents().sort(Folder.ITEM_POS_COMPARATOR); 550 appPair.fetchHiResIconsIfNeeded(mIconCache); 551 }); 552 } 553 554 /** 555 * Initialized the UserManagerState, and determines which users are unlocked. Additionally, if 556 * the user is unlocked, it queries LauncherAppsService for pinned shortcuts and stores the 557 * result in a class variable to be used in other methods while processing workspace items. 558 * 559 * @param context used to query LauncherAppsService 560 * @param unlockedUsers this param is changed, and the updated value is used outside this method 561 */ 562 @WorkerThread queryPinnedShortcutsForUnlockedUsers(Context context, LongSparseArray<Boolean> unlockedUsers)563 private void queryPinnedShortcutsForUnlockedUsers(Context context, 564 LongSparseArray<Boolean> unlockedUsers) { 565 mUserManagerState.init(mUserCache, mUserManager); 566 567 for (UserHandle user : mUserCache.getUserProfiles()) { 568 long serialNo = mUserCache.getSerialNumberForUser(user); 569 boolean userUnlocked = mUserManager.isUserUnlocked(user); 570 571 // We can only query for shortcuts when the user is unlocked. 572 if (userUnlocked) { 573 QueryResult pinnedShortcuts = new ShortcutRequest(context, user) 574 .query(ShortcutRequest.PINNED); 575 if (pinnedShortcuts.wasSuccess()) { 576 for (ShortcutInfo shortcut : pinnedShortcuts) { 577 mShortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), 578 shortcut); 579 } 580 if (pinnedShortcuts.isEmpty()) { 581 FileLog.d(TAG, "No pinned shortcuts found for user " + user); 582 } 583 } else { 584 // Shortcut manager can fail due to some race condition when the 585 // lock state changes too frequently. For the purpose of the loading 586 // shortcuts, consider the user is still locked. 587 FileLog.d(TAG, "Shortcut request failed for user " 588 + user + ", user may still be locked."); 589 userUnlocked = false; 590 } 591 } 592 unlockedUsers.put(serialNo, userUnlocked); 593 } 594 595 } 596 597 /** 598 * After all items have been processed and added to the BgDataModel, this method can correctly 599 * rank items inside folders and load the correct miniature preview icons to be shown when the 600 * folder is collapsed. 601 */ 602 @WorkerThread processFolderItems()603 private void processFolderItems() { 604 // Sort the folder items, update ranks, and make sure all preview items are high res. 605 List<FolderGridOrganizer> verifiers = mIDP.supportedProfiles 606 .stream().map(FolderGridOrganizer::createFolderGridOrganizer).toList(); 607 for (ItemInfo itemInfo : mBgDataModel.itemsIdMap) { 608 if (!(itemInfo instanceof FolderInfo folder)) { 609 continue; 610 } 611 612 folder.getContents().sort(Folder.ITEM_POS_COMPARATOR); 613 verifiers.forEach(verifier -> verifier.setFolderInfo(folder)); 614 int size = folder.getContents().size(); 615 616 // Update ranks here to ensure there are no gaps caused by removed folder items. 617 // Ranks are the source of truth for folder items, so cellX and cellY can be 618 // ignored for now. Database will be updated once user manually modifies folder. 619 for (int rank = 0; rank < size; ++rank) { 620 ItemInfo info = folder.getContents().get(rank); 621 info.rank = rank; 622 623 if (info instanceof WorkspaceItemInfo wii 624 && wii.getMatchingLookupFlag().isVisuallyLessThan(DESKTOP_ICON_FLAG) 625 && wii.itemType == Favorites.ITEM_TYPE_APPLICATION 626 && verifiers.stream().anyMatch(it -> it.isItemInPreview(info.rank))) { 627 mIconCache.getTitleAndIcon(wii, DESKTOP_ICON_FLAG); 628 } else if (info instanceof AppPairInfo api) { 629 api.fetchHiResIconsIfNeeded(mIconCache); 630 } 631 } 632 } 633 } 634 tryLoadWorkspaceIconsInBulk( List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos)635 private void tryLoadWorkspaceIconsInBulk( 636 List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos) { 637 Trace.beginSection("LoadWorkspaceIconsInBulk"); 638 try { 639 mIconCache.getTitlesAndIconsInBulk(iconRequestInfos); 640 for (IconRequestInfo<WorkspaceItemInfo> iconRequestInfo : iconRequestInfos) { 641 WorkspaceItemInfo wai = iconRequestInfo.itemInfo; 642 if (mIconCache.isDefaultIcon(wai.bitmap, wai.user)) { 643 logASplit("tryLoadWorkspaceIconsInBulk: default icon found for " 644 + wai.getTargetComponent() + ", will attempt to load from iconBlob"); 645 iconRequestInfo.loadIconFromDbBlob(mContext); 646 } 647 } 648 } finally { 649 Trace.endSection(); 650 } 651 } 652 setIgnorePackages(IconCacheUpdateHandler updateHandler)653 private void setIgnorePackages(IconCacheUpdateHandler updateHandler) { 654 // Ignore packages which have a promise icon. 655 synchronized (mBgDataModel) { 656 for (ItemInfo info : mBgDataModel.itemsIdMap) { 657 if (info instanceof WorkspaceItemInfo) { 658 WorkspaceItemInfo si = (WorkspaceItemInfo) info; 659 if (si.isPromise() && si.getTargetComponent() != null) { 660 updateHandler.addPackagesToIgnore( 661 si.user, si.getTargetComponent().getPackageName()); 662 } 663 } else if (info instanceof LauncherAppWidgetInfo) { 664 LauncherAppWidgetInfo lawi = (LauncherAppWidgetInfo) info; 665 if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) { 666 updateHandler.addPackagesToIgnore( 667 lawi.user, lawi.providerName.getPackageName()); 668 } 669 } 670 } 671 } 672 } 673 sanitizeFolders(boolean itemsDeleted)674 private void sanitizeFolders(boolean itemsDeleted) { 675 if (itemsDeleted) { 676 // Remove any empty folder 677 IntArray deletedFolderIds = mModel.getModelDbController().deleteEmptyFolders(); 678 synchronized (mBgDataModel) { 679 for (int folderId : deletedFolderIds) { 680 mBgDataModel.itemsIdMap.remove(folderId); 681 } 682 } 683 } 684 } 685 686 /** Cleans up app pairs if they don't have the right number of member apps (2). */ sanitizeAppPairs()687 private void sanitizeAppPairs() { 688 IntArray deletedAppPairIds = mModel.getModelDbController().deleteBadAppPairs(); 689 IntArray deletedAppIds = mModel.getModelDbController().deleteUnparentedApps(); 690 691 IntArray deleted = new IntArray(); 692 deleted.addAll(deletedAppPairIds); 693 deleted.addAll(deletedAppIds); 694 695 synchronized (mBgDataModel) { 696 for (int id : deleted) { 697 mBgDataModel.itemsIdMap.remove(id); 698 } 699 } 700 } 701 sanitizeWidgetsShortcutsAndPackages()702 private void sanitizeWidgetsShortcutsAndPackages() { 703 // Remove any ghost widgets 704 mModel.getModelDbController().removeGhostWidgets(); 705 706 // Update pinned state of model shortcuts 707 mBgDataModel.updateShortcutPinnedState(mContext); 708 709 if (!Utilities.isBootCompleted() && !mPendingPackages.isEmpty()) { 710 mContext.registerReceiver( 711 new SdCardAvailableReceiver(mContext, mModel, mPendingPackages), 712 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), 713 null, 714 MODEL_EXECUTOR.getHandler()); 715 } 716 } 717 loadAllApps()718 private List<LauncherActivityInfo> loadAllApps() { 719 final List<UserHandle> profiles = mUserCache.getUserProfiles(); 720 List<LauncherActivityInfo> allActivityList = new ArrayList<>(); 721 // Clear the list of apps 722 mBgAllAppsList.clear(); 723 724 List<IconRequestInfo<AppInfo>> allAppsItemRequestInfos = new ArrayList<>(); 725 boolean isWorkProfileQuiet = false; 726 boolean isPrivateProfileQuiet = false; 727 for (UserHandle user : profiles) { 728 // Query for the set of apps 729 final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user); 730 // Fail if we don't have any apps 731 // TODO: Fix this. Only fail for the current user. 732 if (apps == null || apps.isEmpty()) { 733 return allActivityList; 734 } 735 boolean quietMode = mUserManagerState.isUserQuiet(user); 736 737 if (Flags.enablePrivateSpace()) { 738 if (mUserCache.getUserInfo(user).isWork()) { 739 isWorkProfileQuiet = quietMode; 740 } else if (mUserCache.getUserInfo(user).isPrivate()) { 741 isPrivateProfileQuiet = quietMode; 742 } 743 } 744 // Create the ApplicationInfos 745 for (int i = 0; i < apps.size(); i++) { 746 LauncherActivityInfo app = apps.get(i); 747 AppInfo appInfo = new AppInfo(app, mUserCache.getUserInfo(user), 748 ApiWrapper.INSTANCE.get(mContext), mPmHelper, quietMode); 749 if (Flags.enableSupportForArchiving() && app.getApplicationInfo().isArchived) { 750 // For archived apps, include progress info in case there is a pending 751 // install session post restart of device. 752 String appPackageName = app.getApplicationInfo().packageName; 753 SessionInfo si = mInstallingPkgsCached != null ? mInstallingPkgsCached.get( 754 new PackageUserKey(appPackageName, user)) 755 : mSessionHelper.getActiveSessionInfo(user, 756 appPackageName); 757 if (si != null) { 758 appInfo.runtimeStatusFlags |= FLAG_INSTALL_SESSION_ACTIVE; 759 appInfo.setProgressLevel((int) (si.getProgress() * 100), 760 PackageInstallInfo.STATUS_INSTALLING); 761 } 762 } 763 764 IconRequestInfo<AppInfo> iconRequestInfo = getAppInfoIconRequestInfo( 765 appInfo, app, mWorkspaceIconRequestInfos, mIsRestoreFromBackup); 766 allAppsItemRequestInfos.add(iconRequestInfo); 767 mBgAllAppsList.add(appInfo, app, false); 768 } 769 allActivityList.addAll(apps); 770 } 771 772 if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) { 773 // get all active sessions and add them to the all apps list 774 for (PackageInstaller.SessionInfo info : 775 mSessionHelper.getAllVerifiedSessions()) { 776 AppInfo promiseAppInfo = mBgAllAppsList.addPromiseApp( 777 mContext, 778 PackageInstallInfo.fromInstallingState(info), 779 false); 780 781 if (promiseAppInfo != null) { 782 allAppsItemRequestInfos.add(new IconRequestInfo<>( 783 promiseAppInfo, 784 /* launcherActivityInfo= */ null, 785 promiseAppInfo.getMatchingLookupFlag().useLowRes())); 786 } 787 } 788 } 789 790 Trace.beginSection("LoadAllAppsIconsInBulk"); 791 792 try { 793 mIconCache.getTitlesAndIconsInBulk(allAppsItemRequestInfos); 794 if (Flags.restoreArchivedAppIconsFromDb()) { 795 for (IconRequestInfo<AppInfo> iconRequestInfo : allAppsItemRequestInfos) { 796 AppInfo appInfo = iconRequestInfo.itemInfo; 797 if (mIconCache.isDefaultIcon(appInfo.bitmap, appInfo.user)) { 798 logASplit("LoadAllAppsIconsInBulk: default icon found for " 799 + appInfo.getTargetComponent() 800 + ", will attempt to load from iconBlob: " 801 + Arrays.toString(iconRequestInfo.iconBlob)); 802 iconRequestInfo.loadIconFromDbBlob(mContext); 803 } 804 } 805 } 806 allAppsItemRequestInfos.forEach(iconRequestInfo -> 807 mBgAllAppsList.updateSectionName(iconRequestInfo.itemInfo)); 808 } finally { 809 Trace.endSection(); 810 } 811 812 if (Flags.enablePrivateSpace()) { 813 mBgAllAppsList.setFlags(FLAG_WORK_PROFILE_QUIET_MODE_ENABLED, isWorkProfileQuiet); 814 mBgAllAppsList.setFlags(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED, isPrivateProfileQuiet); 815 } else { 816 mBgAllAppsList.setFlags(FLAG_QUIET_MODE_ENABLED, 817 mUserManagerState.isAnyProfileQuietModeEnabled()); 818 } 819 mBgAllAppsList.setFlags(FLAG_HAS_SHORTCUT_PERMISSION, 820 hasShortcutsPermission(mContext)); 821 mBgAllAppsList.setFlags(FLAG_QUIET_MODE_CHANGE_PERMISSION, 822 mContext.checkSelfPermission("android.permission.MODIFY_QUIET_MODE") 823 == PackageManager.PERMISSION_GRANTED); 824 825 mBgAllAppsList.getAndResetChangeFlag(); 826 return allActivityList; 827 } 828 829 @NonNull 830 @VisibleForTesting getAppInfoIconRequestInfo( AppInfo appInfo, LauncherActivityInfo activityInfo, List<IconRequestInfo<WorkspaceItemInfo>> workspaceRequestInfos, boolean isRestoreFromBackup )831 IconRequestInfo<AppInfo> getAppInfoIconRequestInfo( 832 AppInfo appInfo, 833 LauncherActivityInfo activityInfo, 834 List<IconRequestInfo<WorkspaceItemInfo>> workspaceRequestInfos, 835 boolean isRestoreFromBackup 836 ) { 837 if (Flags.restoreArchivedAppIconsFromDb() && isRestoreFromBackup) { 838 Optional<IconRequestInfo<WorkspaceItemInfo>> workspaceIconRequest = 839 workspaceRequestInfos.stream() 840 .filter(request -> appInfo.getTargetComponent().equals( 841 request.itemInfo.getTargetComponent())) 842 .findFirst(); 843 844 if (workspaceIconRequest.isPresent() && activityInfo.getApplicationInfo().isArchived) { 845 logASplit("getAppInfoIconRequestInfo:" 846 + " matching archived info found, loading icon blob into icon request." 847 + " Component=" + appInfo.getTargetComponent()); 848 IconRequestInfo<AppInfo> iconRequestInfo = new IconRequestInfo<>( 849 appInfo, 850 activityInfo, 851 workspaceIconRequest.get().iconBlob, 852 false /* useLowResIcon= */ 853 ); 854 if (!iconRequestInfo.loadIconFromDbBlob(mContext)) { 855 Log.d(TAG, "AppInfo Icon failed to load from blob, using cache."); 856 mIconCache.getTitleAndIcon( 857 appInfo, 858 iconRequestInfo.launcherActivityInfo, 859 DEFAULT_LOOKUP_FLAG 860 ); 861 } 862 return iconRequestInfo; 863 } else { 864 Log.d(TAG, "App not archived or workspace info not found" 865 + ", creating IconRequestInfo without icon blob." 866 + " Component:" + appInfo.getTargetComponent() 867 + ", isArchived: " + activityInfo.getApplicationInfo().isArchived); 868 } 869 } 870 return new IconRequestInfo<>(appInfo, activityInfo, false /* useLowResIcon= */); 871 } 872 loadDeepShortcuts()873 private List<ShortcutInfo> loadDeepShortcuts() { 874 List<ShortcutInfo> allShortcuts = new ArrayList<>(); 875 mBgDataModel.deepShortcutMap.clear(); 876 877 if (mBgAllAppsList.hasShortcutHostPermission()) { 878 for (UserHandle user : mUserCache.getUserProfiles()) { 879 if (mUserManager.isUserUnlocked(user)) { 880 List<ShortcutInfo> shortcuts = new ShortcutRequest(mContext, user) 881 .query(ShortcutRequest.ALL); 882 allShortcuts.addAll(shortcuts); 883 mBgDataModel.updateDeepShortcutCounts(null, user, shortcuts); 884 } 885 } 886 } 887 return allShortcuts; 888 } 889 loadFolderNames()890 private void loadFolderNames() { 891 FolderNameProvider provider = FolderNameProvider.newInstance(mContext, 892 mBgAllAppsList.data, FolderNameProvider.getCollectionForSuggestions(mBgDataModel)); 893 894 synchronized (mBgDataModel) { 895 mBgDataModel.itemsIdMap.stream() 896 .filter(item -> 897 item instanceof FolderInfo fi && fi.suggestedFolderNames == null) 898 .forEach(info -> { 899 FolderInfo fi = (FolderInfo) info; 900 FolderNameInfos suggestionInfos = new FolderNameInfos(); 901 provider.getSuggestedFolderName(mContext, fi.getAppContents(), 902 suggestionInfos); 903 fi.suggestedFolderNames = suggestionInfos; 904 }); 905 } 906 } 907 isValidProvider(AppWidgetProviderInfo provider)908 public static boolean isValidProvider(AppWidgetProviderInfo provider) { 909 return (provider != null) && (provider.provider != null) 910 && (provider.provider.getPackageName() != null); 911 } 912 logASplit(String label)913 private static void logASplit(String label) { 914 if (DEBUG) { 915 Log.d(TAG, label); 916 } 917 } 918 919 @AssistedFactory 920 public interface LoaderTaskFactory { 921 newLoaderTask(BaseLauncherBinder binder, UserManagerState userState)922 LoaderTask newLoaderTask(BaseLauncherBinder binder, UserManagerState userState); 923 } 924 } 925