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