1 /*
2  * Copyright (C) 2008 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;
18 
19 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
20 
21 import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
22 import static com.android.launcher3.LauncherPrefs.WORK_EDU_STEP;
23 import static com.android.launcher3.config.FeatureFlags.IS_STUDIO_BUILD;
24 import static com.android.launcher3.icons.cache.BaseIconCache.EMPTY_CLASS_NAME;
25 import static com.android.launcher3.model.PackageUpdatedTask.OP_UPDATE;
26 import static com.android.launcher3.pm.UserCache.ACTION_PROFILE_AVAILABLE;
27 import static com.android.launcher3.pm.UserCache.ACTION_PROFILE_UNAVAILABLE;
28 import static com.android.launcher3.testing.shared.TestProtocol.sDebugTracing;
29 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
30 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
31 
32 import android.content.ComponentName;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.pm.PackageInstaller;
36 import android.content.pm.ShortcutInfo;
37 import android.os.UserHandle;
38 import android.text.TextUtils;
39 import android.util.Log;
40 import android.util.Pair;
41 
42 import androidx.annotation.NonNull;
43 import androidx.annotation.Nullable;
44 import androidx.annotation.WorkerThread;
45 
46 import com.android.launcher3.celllayout.CellPosMapper;
47 import com.android.launcher3.config.FeatureFlags;
48 import com.android.launcher3.icons.IconCache;
49 import com.android.launcher3.model.AddWorkspaceItemsTask;
50 import com.android.launcher3.model.AllAppsList;
51 import com.android.launcher3.model.BaseLauncherBinder;
52 import com.android.launcher3.model.BgDataModel;
53 import com.android.launcher3.model.BgDataModel.Callbacks;
54 import com.android.launcher3.model.CacheDataUpdatedTask;
55 import com.android.launcher3.model.ItemInstallQueue;
56 import com.android.launcher3.model.LoaderTask;
57 import com.android.launcher3.model.ModelDbController;
58 import com.android.launcher3.model.ModelDelegate;
59 import com.android.launcher3.model.ModelLauncherCallbacks;
60 import com.android.launcher3.model.ModelTaskController;
61 import com.android.launcher3.model.ModelWriter;
62 import com.android.launcher3.model.PackageInstallStateChangedTask;
63 import com.android.launcher3.model.PackageUpdatedTask;
64 import com.android.launcher3.model.ReloadStringCacheTask;
65 import com.android.launcher3.model.ShortcutsChangedTask;
66 import com.android.launcher3.model.UserLockStateChangedTask;
67 import com.android.launcher3.model.data.AppInfo;
68 import com.android.launcher3.model.data.ItemInfo;
69 import com.android.launcher3.model.data.WorkspaceItemInfo;
70 import com.android.launcher3.pm.InstallSessionTracker;
71 import com.android.launcher3.pm.PackageInstallInfo;
72 import com.android.launcher3.pm.UserCache;
73 import com.android.launcher3.shortcuts.ShortcutRequest;
74 import com.android.launcher3.util.IntSet;
75 import com.android.launcher3.util.ItemInfoMatcher;
76 import com.android.launcher3.util.PackageManagerHelper;
77 import com.android.launcher3.util.PackageUserKey;
78 import com.android.launcher3.util.Preconditions;
79 
80 import java.io.FileDescriptor;
81 import java.io.PrintWriter;
82 import java.util.ArrayList;
83 import java.util.HashSet;
84 import java.util.List;
85 import java.util.concurrent.CancellationException;
86 import java.util.function.Consumer;
87 import java.util.function.Supplier;
88 
89 /**
90  * Maintains in-memory state of the Launcher. It is expected that there should be only one
91  * LauncherModel object held in a static. Also provide APIs for updating the database state
92  * for the Launcher.
93  */
94 public class LauncherModel implements InstallSessionTracker.Callback {
95     private static final boolean DEBUG_RECEIVER = false;
96 
97     static final String TAG = "Launcher.Model";
98 
99     @NonNull
100     private final LauncherAppState mApp;
101     @NonNull
102     private final PackageManagerHelper mPmHelper;
103     @NonNull
104     private final ModelDbController mModelDbController;
105     @NonNull
106     private final Object mLock = new Object();
107     @Nullable
108     private LoaderTask mLoaderTask;
109     private boolean mIsLoaderTaskRunning;
110 
111     // only allow this once per reboot to reload work apps
112     private boolean mShouldReloadWorkProfile = true;
113 
114     // Indicates whether the current model data is valid or not.
115     // We start off with everything not loaded. After that, we assume that
116     // our monitoring of the package manager provides all updates and we never
117     // need to do a requery. This is only ever touched from the loader thread.
118     private boolean mModelLoaded;
119     private boolean mModelDestroyed = false;
isModelLoaded()120     public boolean isModelLoaded() {
121         synchronized (mLock) {
122             return mModelLoaded && mLoaderTask == null && !mModelDestroyed;
123         }
124     }
125 
126     @NonNull
127     private final ArrayList<Callbacks> mCallbacksList = new ArrayList<>(1);
128 
129     // < only access in worker thread >
130     @NonNull
131     private final AllAppsList mBgAllAppsList;
132 
133     /**
134      * All the static data should be accessed on the background thread, A lock should be acquired
135      * on this object when accessing any data from this model.
136      */
137     @NonNull
138     private final BgDataModel mBgDataModel = new BgDataModel();
139 
140     @NonNull
141     private final ModelDelegate mModelDelegate;
142 
143     private int mLastLoadId = -1;
144 
145     // Runnable to check if the shortcuts permission has changed.
146     @NonNull
147     private final Runnable mDataValidationCheck = new Runnable() {
148         @Override
149         public void run() {
150             if (mModelLoaded) {
151                 mModelDelegate.validateData();
152             }
153         }
154     };
155 
LauncherModel(@onNull final Context context, @NonNull final LauncherAppState app, @NonNull final IconCache iconCache, @NonNull final AppFilter appFilter, @NonNull final PackageManagerHelper pmHelper, final boolean isPrimaryInstance)156     LauncherModel(@NonNull final Context context, @NonNull final LauncherAppState app,
157             @NonNull final IconCache iconCache, @NonNull final AppFilter appFilter,
158             @NonNull final PackageManagerHelper pmHelper, final boolean isPrimaryInstance) {
159         mApp = app;
160         mPmHelper = pmHelper;
161         mModelDbController = new ModelDbController(context);
162         mBgAllAppsList = new AllAppsList(iconCache, appFilter);
163         mModelDelegate = ModelDelegate.newInstance(context, app, mPmHelper, mBgAllAppsList,
164                 mBgDataModel, isPrimaryInstance);
165     }
166 
167     @NonNull
getModelDelegate()168     public ModelDelegate getModelDelegate() {
169         return mModelDelegate;
170     }
171 
getModelDbController()172     public ModelDbController getModelDbController() {
173         return mModelDbController;
174     }
175 
newModelCallbacks()176     public ModelLauncherCallbacks newModelCallbacks() {
177         return new ModelLauncherCallbacks(this::enqueueModelUpdateTask);
178     }
179 
180     /**
181      * Adds the provided items to the workspace.
182      */
addAndBindAddedWorkspaceItems( @onNull final List<Pair<ItemInfo, Object>> itemList)183     public void addAndBindAddedWorkspaceItems(
184             @NonNull final List<Pair<ItemInfo, Object>> itemList) {
185         for (Callbacks cb : getCallbacks()) {
186             cb.preAddApps();
187         }
188         enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList));
189     }
190 
191     @NonNull
getWriter(final boolean verifyChanges, CellPosMapper cellPosMapper, @Nullable final Callbacks owner)192     public ModelWriter getWriter(final boolean verifyChanges, CellPosMapper cellPosMapper,
193             @Nullable final Callbacks owner) {
194         return new ModelWriter(mApp.getContext(), this, mBgDataModel, verifyChanges, cellPosMapper,
195                 owner);
196     }
197 
198     /**
199      * Called when the icon for an app changes, outside of package event
200      */
201     @WorkerThread
onAppIconChanged(@onNull final String packageName, @NonNull final UserHandle user)202     public void onAppIconChanged(@NonNull final String packageName,
203             @NonNull final UserHandle user) {
204         // Update the icon for the calendar package
205         Context context = mApp.getContext();
206         enqueueModelUpdateTask(new PackageUpdatedTask(OP_UPDATE, user, packageName));
207 
208         List<ShortcutInfo> pinnedShortcuts = new ShortcutRequest(context, user)
209                 .forPackage(packageName).query(ShortcutRequest.PINNED);
210         if (!pinnedShortcuts.isEmpty()) {
211             enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, pinnedShortcuts, user,
212                     false));
213         }
214     }
215 
216     /**
217      * Called when the workspace items have drastically changed
218      */
onWorkspaceUiChanged()219     public void onWorkspaceUiChanged() {
220         MODEL_EXECUTOR.execute(mModelDelegate::workspaceLoadComplete);
221     }
222 
223     /**
224      * Called when the model is destroyed
225      */
destroy()226     public void destroy() {
227         mModelDestroyed = true;
228         MODEL_EXECUTOR.execute(mModelDelegate::destroy);
229     }
230 
onBroadcastIntent(@onNull final Intent intent)231     public void onBroadcastIntent(@NonNull final Intent intent) {
232         if (DEBUG_RECEIVER || sDebugTracing) Log.d(TAG, "onReceive intent=" + intent);
233         final String action = intent.getAction();
234         if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
235             // If we have changed locale we need to clear out the labels in all apps/workspace.
236             forceReload();
237         } else if (ACTION_DEVICE_POLICY_RESOURCE_UPDATED.equals(action)) {
238             enqueueModelUpdateTask(new ReloadStringCacheTask(mModelDelegate));
239         } else if (IS_STUDIO_BUILD && ACTION_FORCE_ROLOAD.equals(action)) {
240             for (Callbacks cb : getCallbacks()) {
241                 if (cb instanceof Launcher) {
242                     ((Launcher) cb).recreate();
243                 }
244             }
245         }
246     }
247 
248     /**
249      * Called then there use a user event
250      * @see UserCache#addUserEventListener
251      */
onUserEvent(UserHandle user, String action)252     public void onUserEvent(UserHandle user, String action) {
253         if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)
254                 && mShouldReloadWorkProfile) {
255             mShouldReloadWorkProfile = false;
256             forceReload();
257         } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)
258                 || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
259             mShouldReloadWorkProfile = false;
260             enqueueModelUpdateTask(new PackageUpdatedTask(
261                     PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
262         } else if (UserCache.ACTION_PROFILE_LOCKED.equals(action)
263                 || UserCache.ACTION_PROFILE_UNLOCKED.equals(action)) {
264             enqueueModelUpdateTask(new UserLockStateChangedTask(
265                     user, UserCache.ACTION_PROFILE_UNLOCKED.equals(action)));
266         } else if (UserCache.ACTION_PROFILE_ADDED.equals(action)
267                 || UserCache.ACTION_PROFILE_REMOVED.equals(action)) {
268             forceReload();
269         } else if (ACTION_PROFILE_AVAILABLE.equals(action)
270                 || ACTION_PROFILE_UNAVAILABLE.equals(action)) {
271             /*
272              * This broadcast is only available when android.os.Flags.allowPrivateProfile() is set.
273              * For Work-profile this broadcast will be sent in addition to
274              * ACTION_MANAGED_PROFILE_AVAILABLE/UNAVAILABLE.
275              * So effectively, this if block only handles the non-work profile case.
276              */
277             enqueueModelUpdateTask(new PackageUpdatedTask(
278                     PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
279         }
280         if (Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
281             LauncherPrefs.get(mApp.getContext()).put(WORK_EDU_STEP, 0);
282         }
283     }
284 
285     /**
286      * Reloads the workspace items from the DB and re-binds the workspace. This should generally
287      * not be called as DB updates are automatically followed by UI update
288      */
forceReload()289     public void forceReload() {
290         synchronized (mLock) {
291             // Stop any existing loaders first, so they don't set mModelLoaded to true later
292             stopLoader();
293             mModelLoaded = false;
294         }
295 
296         // Start the loader if launcher is already running, otherwise the loader will run,
297         // the next time launcher starts
298         if (hasCallbacks()) {
299             startLoader();
300         }
301     }
302 
303     /**
304      * Rebinds all existing callbacks with already loaded model
305      */
rebindCallbacks()306     public void rebindCallbacks() {
307         if (hasCallbacks()) {
308             startLoader();
309         }
310     }
311 
312     /**
313      * Removes an existing callback
314      */
removeCallbacks(@onNull final Callbacks callbacks)315     public void removeCallbacks(@NonNull final Callbacks callbacks) {
316         synchronized (mCallbacksList) {
317             Preconditions.assertUIThread();
318             if (mCallbacksList.remove(callbacks)) {
319                 if (stopLoader()) {
320                     // Rebind existing callbacks
321                     startLoader();
322                 }
323             }
324         }
325     }
326 
327     /**
328      * Adds a callbacks to receive model updates
329      * @return true if workspace load was performed synchronously
330      */
addCallbacksAndLoad(@onNull final Callbacks callbacks)331     public boolean addCallbacksAndLoad(@NonNull final Callbacks callbacks) {
332         synchronized (mLock) {
333             addCallbacks(callbacks);
334             return startLoader(new Callbacks[] { callbacks });
335 
336         }
337     }
338 
339     /**
340      * Adds a callbacks to receive model updates
341      */
addCallbacks(@onNull final Callbacks callbacks)342     public void addCallbacks(@NonNull final Callbacks callbacks) {
343         Preconditions.assertUIThread();
344         synchronized (mCallbacksList) {
345             mCallbacksList.add(callbacks);
346         }
347     }
348 
349     /**
350      * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible.
351      * @return true if the page could be bound synchronously.
352      */
startLoader()353     public boolean startLoader() {
354         return startLoader(new Callbacks[0]);
355     }
356 
startLoader(@onNull final Callbacks[] newCallbacks)357     private boolean startLoader(@NonNull final Callbacks[] newCallbacks) {
358         // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
359         ItemInstallQueue.INSTANCE.get(mApp.getContext())
360                 .pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING);
361         synchronized (mLock) {
362             // If there is already one running, tell it to stop.
363             boolean wasRunning = stopLoader();
364             boolean bindDirectly = mModelLoaded && !mIsLoaderTaskRunning;
365             boolean bindAllCallbacks = wasRunning || !bindDirectly || newCallbacks.length == 0;
366             final Callbacks[] callbacksList = bindAllCallbacks ? getCallbacks() : newCallbacks;
367 
368             if (callbacksList.length > 0) {
369                 // Clear any pending bind-runnables from the synchronized load process.
370                 for (Callbacks cb : callbacksList) {
371                     MAIN_EXECUTOR.execute(cb::clearPendingBinds);
372                 }
373 
374                 BaseLauncherBinder launcherBinder = new BaseLauncherBinder(
375                         mApp, mBgDataModel, mBgAllAppsList, callbacksList);
376                 if (bindDirectly) {
377                     // Divide the set of loaded items into those that we are binding synchronously,
378                     // and everything else that is to be bound normally (asynchronously).
379                     launcherBinder.bindWorkspace(bindAllCallbacks, /* isBindSync= */ true);
380                     // For now, continue posting the binding of AllApps as there are other
381                     // issues that arise from that.
382                     launcherBinder.bindAllApps();
383                     launcherBinder.bindDeepShortcuts();
384                     launcherBinder.bindWidgets();
385                     if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
386                         mModelDelegate.bindAllModelExtras(callbacksList);
387                     }
388                     return true;
389                 } else {
390                     stopLoader();
391                     mLoaderTask = new LoaderTask(
392                             mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, launcherBinder);
393 
394                     // Always post the loader task, instead of running directly
395                     // (even on same thread) so that we exit any nested synchronized blocks
396                     MODEL_EXECUTOR.post(mLoaderTask);
397                 }
398             }
399         }
400         return false;
401     }
402 
403     /**
404      * If there is already a loader task running, tell it to stop.
405      * @return true if an existing loader was stopped.
406      */
stopLoader()407     private boolean stopLoader() {
408         synchronized (mLock) {
409             LoaderTask oldTask = mLoaderTask;
410             mLoaderTask = null;
411             if (oldTask != null) {
412                 oldTask.stopLocked();
413                 return true;
414             }
415             return false;
416         }
417     }
418 
419     /**
420      * Loads the model if not loaded
421      * @param callback called with the data model upon successful load or null on model thread.
422      */
loadAsync(@onNull final Consumer<BgDataModel> callback)423     public void loadAsync(@NonNull final Consumer<BgDataModel> callback) {
424         synchronized (mLock) {
425             if (!mModelLoaded && !mIsLoaderTaskRunning) {
426                 startLoader();
427             }
428         }
429         MODEL_EXECUTOR.post(() -> callback.accept(isModelLoaded() ? mBgDataModel : null));
430     }
431 
432     @Override
onInstallSessionCreated(@onNull final PackageInstallInfo sessionInfo)433     public void onInstallSessionCreated(@NonNull final PackageInstallInfo sessionInfo) {
434         if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
435             enqueueModelUpdateTask((taskController, dataModel, apps) -> {
436                 apps.addPromiseApp(mApp.getContext(), sessionInfo);
437                 taskController.bindApplicationsIfNeeded();
438             });
439         }
440     }
441 
442     @Override
onSessionFailure(@onNull final String packageName, @NonNull final UserHandle user)443     public void onSessionFailure(@NonNull final String packageName,
444             @NonNull final UserHandle user) {
445         enqueueModelUpdateTask((taskController, dataModel, apps) -> {
446             IconCache iconCache = mApp.getIconCache();
447             final IntSet removedIds = new IntSet();
448             HashSet<WorkspaceItemInfo> archivedWorkspaceItemsToCacheRefresh = new HashSet<>();
449             boolean isAppArchived = PackageManagerHelper.INSTANCE.get(mApp.getContext())
450                     .isAppArchivedForUser(packageName, user);
451             synchronized (dataModel) {
452                 if (isAppArchived) {
453                     // Remove package icon cache entry for archived app in case of a session
454                     // failure.
455                     mApp.getIconCache().remove(
456                             new ComponentName(packageName, packageName + EMPTY_CLASS_NAME),
457                             user);
458                 }
459 
460                 for (ItemInfo info : dataModel.itemsIdMap) {
461                     if (info instanceof WorkspaceItemInfo
462                             && ((WorkspaceItemInfo) info).hasPromiseIconUi()
463                             && user.equals(info.user)
464                             && info.getIntent() != null) {
465                         if (TextUtils.equals(packageName, info.getIntent().getPackage())) {
466                             removedIds.add(info.id);
467                         }
468                         if (((WorkspaceItemInfo) info).isArchived()) {
469                             WorkspaceItemInfo workspaceItem = (WorkspaceItemInfo) info;
470                             // Refresh icons on the workspace for archived apps.
471                             iconCache.getTitleAndIcon(workspaceItem,
472                                     workspaceItem.usingLowResIcon());
473                             archivedWorkspaceItemsToCacheRefresh.add(workspaceItem);
474                         }
475                     }
476                 }
477 
478                 if (isAppArchived) {
479                     apps.updateIconsAndLabels(new HashSet<>(List.of(packageName)), user);
480                 }
481             }
482 
483             if (!removedIds.isEmpty() && !isAppArchived) {
484                 taskController.deleteAndBindComponentsRemoved(
485                         ItemInfoMatcher.ofItemIds(removedIds),
486                         "removed because install session failed");
487             }
488             if (!archivedWorkspaceItemsToCacheRefresh.isEmpty()) {
489                 taskController.bindUpdatedWorkspaceItems(
490                         archivedWorkspaceItemsToCacheRefresh.stream().toList());
491             }
492             if (isAppArchived) {
493                 taskController.bindApplicationsIfNeeded();
494             }
495         });
496     }
497 
498     @Override
onPackageStateChanged(@onNull final PackageInstallInfo installInfo)499     public void onPackageStateChanged(@NonNull final PackageInstallInfo installInfo) {
500         enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo));
501     }
502 
503     /**
504      * Updates the icons and label of all pending icons for the provided package name.
505      */
506     @Override
onUpdateSessionDisplay(@onNull final PackageUserKey key, @NonNull final PackageInstaller.SessionInfo info)507     public void onUpdateSessionDisplay(@NonNull final PackageUserKey key,
508             @NonNull final PackageInstaller.SessionInfo info) {
509         mApp.getIconCache().updateSessionCache(key, info);
510 
511         HashSet<String> packages = new HashSet<>();
512         packages.add(key.mPackageName);
513         enqueueModelUpdateTask(new CacheDataUpdatedTask(
514                 CacheDataUpdatedTask.OP_SESSION_UPDATE, key.mUser, packages));
515     }
516 
517     public class LoaderTransaction implements AutoCloseable {
518 
519         @NonNull
520         private final LoaderTask mTask;
521 
LoaderTransaction(@onNull final LoaderTask task)522         private LoaderTransaction(@NonNull final LoaderTask task) throws CancellationException {
523             synchronized (mLock) {
524                 if (mLoaderTask != task) {
525                     throw new CancellationException("Loader already stopped");
526                 }
527                 mLastLoadId++;
528                 mTask = task;
529                 mIsLoaderTaskRunning = true;
530                 mModelLoaded = false;
531             }
532         }
533 
commit()534         public void commit() {
535             synchronized (mLock) {
536                 // Everything loaded bind the data.
537                 mModelLoaded = true;
538             }
539         }
540 
541         @Override
close()542         public void close() {
543             synchronized (mLock) {
544                 // If we are still the last one to be scheduled, remove ourselves.
545                 if (mLoaderTask == mTask) {
546                     mLoaderTask = null;
547                 }
548                 mIsLoaderTaskRunning = false;
549             }
550         }
551     }
552 
beginLoader(@onNull final LoaderTask task)553     public LoaderTransaction beginLoader(@NonNull final LoaderTask task)
554             throws CancellationException {
555         return new LoaderTransaction(task);
556     }
557 
558     /**
559      * Refreshes the cached shortcuts if the shortcut permission has changed.
560      * Current implementation simply reloads the workspace, but it can be optimized to
561      * use partial updates similar to {@link UserCache}
562      */
validateModelDataOnResume()563     public void validateModelDataOnResume() {
564         MODEL_EXECUTOR.getHandler().removeCallbacks(mDataValidationCheck);
565         MODEL_EXECUTOR.post(mDataValidationCheck);
566     }
567 
568     /**
569      * Called when the icons for packages have been updated in the icon cache.
570      */
onPackageIconsUpdated(@onNull final HashSet<String> updatedPackages, @NonNull final UserHandle user)571     public void onPackageIconsUpdated(@NonNull final HashSet<String> updatedPackages,
572             @NonNull final UserHandle user) {
573         // If any package icon has changed (app was updated while launcher was dead),
574         // update the corresponding shortcuts.
575         enqueueModelUpdateTask(new CacheDataUpdatedTask(
576                 CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages));
577     }
578 
579     /**
580      * Called when the labels for the widgets has updated in the icon cache.
581      */
onWidgetLabelsUpdated(@onNull final HashSet<String> updatedPackages, @NonNull final UserHandle user)582     public void onWidgetLabelsUpdated(@NonNull final HashSet<String> updatedPackages,
583             @NonNull final UserHandle user) {
584         enqueueModelUpdateTask((taskController, dataModel, apps) ->  {
585             dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, mApp);
586             taskController.bindUpdatedWidgets(dataModel);
587         });
588     }
589 
enqueueModelUpdateTask(@onNull final ModelUpdateTask task)590     public void enqueueModelUpdateTask(@NonNull final ModelUpdateTask task) {
591         if (mModelDestroyed) {
592             return;
593         }
594         MODEL_EXECUTOR.execute(() -> {
595             if (!isModelLoaded()) {
596                 // Loader has not yet run.
597                 return;
598             }
599             ModelTaskController controller = new ModelTaskController(
600                     mApp, mBgDataModel, mBgAllAppsList, this, MAIN_EXECUTOR);
601             task.execute(controller, mBgDataModel, mBgAllAppsList);
602         });
603     }
604 
605     /**
606      * A task to be executed on the current callbacks on the UI thread.
607      * If there is no current callbacks, the task is ignored.
608      */
609     public interface CallbackTask {
610 
execute(@onNull Callbacks callbacks)611         void execute(@NonNull Callbacks callbacks);
612     }
613 
614     public interface ModelUpdateTask {
615 
execute(@onNull ModelTaskController taskController, @NonNull BgDataModel dataModel, @NonNull AllAppsList apps)616         void execute(@NonNull ModelTaskController taskController,
617                 @NonNull BgDataModel dataModel, @NonNull AllAppsList apps);
618     }
619 
updateAndBindWorkspaceItem(@onNull final WorkspaceItemInfo si, @NonNull final ShortcutInfo info)620     public void updateAndBindWorkspaceItem(@NonNull final WorkspaceItemInfo si,
621             @NonNull final ShortcutInfo info) {
622         updateAndBindWorkspaceItem(() -> {
623             si.updateFromDeepShortcutInfo(info, mApp.getContext());
624             mApp.getIconCache().getShortcutIcon(si, info);
625             return si;
626         });
627     }
628 
629     /**
630      * Utility method to update a shortcut on the background thread.
631      */
updateAndBindWorkspaceItem( @onNull final Supplier<WorkspaceItemInfo> itemProvider)632     public void updateAndBindWorkspaceItem(
633             @NonNull final Supplier<WorkspaceItemInfo> itemProvider) {
634         enqueueModelUpdateTask((taskController, dataModel, apps) ->  {
635             WorkspaceItemInfo info = itemProvider.get();
636             taskController.getModelWriter().updateItemInDatabase(info);
637             ArrayList<WorkspaceItemInfo> update = new ArrayList<>();
638             update.add(info);
639             taskController.bindUpdatedWorkspaceItems(update);
640         });
641     }
642 
refreshAndBindWidgetsAndShortcuts(@ullable final PackageUserKey packageUser)643     public void refreshAndBindWidgetsAndShortcuts(@Nullable final PackageUserKey packageUser) {
644         enqueueModelUpdateTask((taskController, dataModel, apps) ->  {
645             dataModel.widgetsModel.update(taskController.getApp(), packageUser);
646             taskController.bindUpdatedWidgets(dataModel);
647         });
648     }
649 
dumpState(@ullable final String prefix, @Nullable final FileDescriptor fd, @NonNull final PrintWriter writer, @NonNull final String[] args)650     public void dumpState(@Nullable final String prefix, @Nullable final FileDescriptor fd,
651             @NonNull final PrintWriter writer, @NonNull final String[] args) {
652         if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
653             writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size());
654             for (AppInfo info : mBgAllAppsList.data) {
655                 writer.println(prefix + "   title=\"" + info.title
656                         + "\" bitmapIcon=" + info.bitmap.icon
657                         + " componentName=" + info.componentName.getPackageName());
658             }
659             writer.println();
660         }
661         mModelDelegate.dump(prefix, fd, writer, args);
662         mBgDataModel.dump(prefix, fd, writer, args);
663     }
664 
665     /**
666      * Returns true if there are any callbacks attached to the model
667      */
hasCallbacks()668     public boolean hasCallbacks() {
669         synchronized (mCallbacksList) {
670             return !mCallbacksList.isEmpty();
671         }
672     }
673 
674     /**
675      * Returns an array of currently attached callbacks
676      */
677     @NonNull
getCallbacks()678     public Callbacks[] getCallbacks() {
679         synchronized (mCallbacksList) {
680             return mCallbacksList.toArray(new Callbacks[mCallbacksList.size()]);
681         }
682     }
683 
684     /**
685      * Returns the ID for the last model load. If the load ID doesn't match for a transaction, the
686      * transaction should be ignored.
687      */
getLastLoadId()688     public int getLastLoadId() {
689         return mLastLoadId;
690     }
691 }
692