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