• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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