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