• 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 com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
20 import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD;
21 
22 import android.content.BroadcastReceiver;
23 import android.content.ContentProviderOperation;
24 import android.content.ContentResolver;
25 import android.content.ContentValues;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.net.Uri;
29 import android.os.Handler;
30 import android.os.HandlerThread;
31 import android.os.Looper;
32 import android.os.Process;
33 import android.os.UserHandle;
34 import android.support.annotation.Nullable;
35 import android.text.TextUtils;
36 import android.util.Log;
37 import android.util.Pair;
38 
39 import com.android.launcher3.compat.LauncherAppsCompat;
40 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
41 import com.android.launcher3.compat.UserManagerCompat;
42 import com.android.launcher3.graphics.LauncherIcons;
43 import com.android.launcher3.model.AddWorkspaceItemsTask;
44 import com.android.launcher3.model.BaseModelUpdateTask;
45 import com.android.launcher3.model.BgDataModel;
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.provider.LauncherDbUtils;
55 import com.android.launcher3.shortcuts.DeepShortcutManager;
56 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
57 import com.android.launcher3.util.ComponentKey;
58 import com.android.launcher3.util.ItemInfoMatcher;
59 import com.android.launcher3.util.MultiHashMap;
60 import com.android.launcher3.util.PackageUserKey;
61 import com.android.launcher3.util.Preconditions;
62 import com.android.launcher3.util.Provider;
63 import com.android.launcher3.util.Thunk;
64 import com.android.launcher3.util.ViewOnDrawExecutor;
65 import com.android.launcher3.widget.WidgetListRowEntry;
66 
67 import java.io.FileDescriptor;
68 import java.io.PrintWriter;
69 import java.lang.ref.WeakReference;
70 import java.util.ArrayList;
71 import java.util.HashSet;
72 import java.util.Iterator;
73 import java.util.List;
74 import java.util.concurrent.CancellationException;
75 import java.util.concurrent.Executor;
76 
77 /**
78  * Maintains in-memory state of the Launcher. It is expected that there should be only one
79  * LauncherModel object held in a static. Also provide APIs for updating the database state
80  * for the Launcher.
81  */
82 public class LauncherModel extends BroadcastReceiver
83         implements LauncherAppsCompat.OnAppsChangedCallbackCompat {
84     private static final boolean DEBUG_RECEIVER = false;
85 
86     static final String TAG = "Launcher.Model";
87 
88     private final MainThreadExecutor mUiExecutor = new MainThreadExecutor();
89     @Thunk final LauncherAppState mApp;
90     @Thunk final Object mLock = new Object();
91     @Thunk
92     LoaderTask mLoaderTask;
93     @Thunk boolean mIsLoaderTaskRunning;
94 
95     @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
96     static {
sWorkerThread.start()97         sWorkerThread.start();
98     }
99     @Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());
100 
101     // Indicates whether the current model data is valid or not.
102     // We start off with everything not loaded. After that, we assume that
103     // our monitoring of the package manager provides all updates and we never
104     // need to do a requery. This is only ever touched from the loader thread.
105     private boolean mModelLoaded;
isModelLoaded()106     public boolean isModelLoaded() {
107         synchronized (mLock) {
108             return mModelLoaded && mLoaderTask == null;
109         }
110     }
111 
112     @Thunk WeakReference<Callbacks> mCallbacks;
113 
114     // < only access in worker thread >
115     private final AllAppsList mBgAllAppsList;
116 
117     /**
118      * All the static data should be accessed on the background thread, A lock should be acquired
119      * on this object when accessing any data from this model.
120      */
121     static final BgDataModel sBgDataModel = new BgDataModel();
122 
123     // Runnable to check if the shortcuts permission has changed.
124     private final Runnable mShortcutPermissionCheckRunnable = new Runnable() {
125         @Override
126         public void run() {
127             if (mModelLoaded) {
128                 boolean hasShortcutHostPermission =
129                         DeepShortcutManager.getInstance(mApp.getContext()).hasHostPermission();
130                 if (hasShortcutHostPermission != sBgDataModel.hasShortcutHostPermission) {
131                     forceReload();
132                 }
133             }
134         }
135     };
136 
137     public interface Callbacks {
rebindModel()138         public void rebindModel();
139 
getCurrentWorkspaceScreen()140         public int getCurrentWorkspaceScreen();
clearPendingBinds()141         public void clearPendingBinds();
startBinding()142         public void startBinding();
bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons)143         public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons);
bindScreens(ArrayList<Long> orderedScreenIds)144         public void bindScreens(ArrayList<Long> orderedScreenIds);
finishFirstPageBind(ViewOnDrawExecutor executor)145         public void finishFirstPageBind(ViewOnDrawExecutor executor);
finishBindingItems()146         public void finishBindingItems();
bindAllApplications(ArrayList<AppInfo> apps)147         public void bindAllApplications(ArrayList<AppInfo> apps);
bindAppsAddedOrUpdated(ArrayList<AppInfo> apps)148         public void bindAppsAddedOrUpdated(ArrayList<AppInfo> apps);
bindAppsAdded(ArrayList<Long> newScreens, ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated)149         public void bindAppsAdded(ArrayList<Long> newScreens,
150                                   ArrayList<ItemInfo> addNotAnimated,
151                                   ArrayList<ItemInfo> addAnimated);
bindPromiseAppProgressUpdated(PromiseAppInfo app)152         public void bindPromiseAppProgressUpdated(PromiseAppInfo app);
bindShortcutsChanged(ArrayList<ShortcutInfo> updated, UserHandle user)153         public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated, UserHandle user);
bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets)154         public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
bindRestoreItemsChange(HashSet<ItemInfo> updates)155         public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher)156         public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher);
bindAppInfosRemoved(ArrayList<AppInfo> appInfos)157         public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos);
bindAllWidgets(ArrayList<WidgetListRowEntry> widgets)158         public void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets);
onPageBoundSynchronously(int page)159         public void onPageBoundSynchronously(int page);
executeOnNextDraw(ViewOnDrawExecutor executor)160         public void executeOnNextDraw(ViewOnDrawExecutor executor);
bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap)161         public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap);
162     }
163 
LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter)164     LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
165         mApp = app;
166         mBgAllAppsList = new AllAppsList(iconCache, appFilter);
167     }
168 
169     /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
170      * posted on the worker thread handler. */
runOnWorkerThread(Runnable r)171     private static void runOnWorkerThread(Runnable r) {
172         if (sWorkerThread.getThreadId() == Process.myTid()) {
173             r.run();
174         } else {
175             // If we are not on the worker thread, then post to the worker handler
176             sWorker.post(r);
177         }
178     }
179 
setPackageState(PackageInstallInfo installInfo)180     public void setPackageState(PackageInstallInfo installInfo) {
181         enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo));
182     }
183 
184     /**
185      * Updates the icons and label of all pending icons for the provided package name.
186      */
updateSessionDisplayInfo(final String packageName)187     public void updateSessionDisplayInfo(final String packageName) {
188         HashSet<String> packages = new HashSet<>();
189         packages.add(packageName);
190         enqueueModelUpdateTask(new CacheDataUpdatedTask(
191                 CacheDataUpdatedTask.OP_SESSION_UPDATE, Process.myUserHandle(), packages));
192     }
193 
194     /**
195      * Adds the provided items to the workspace.
196      */
addAndBindAddedWorkspaceItems(List<Pair<ItemInfo, Object>> itemList)197     public void addAndBindAddedWorkspaceItems(List<Pair<ItemInfo, Object>> itemList) {
198         enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList));
199     }
200 
getWriter(boolean hasVerticalHotseat, boolean verifyChanges)201     public ModelWriter getWriter(boolean hasVerticalHotseat, boolean verifyChanges) {
202         return new ModelWriter(mApp.getContext(), this, sBgDataModel,
203                 hasVerticalHotseat, verifyChanges);
204     }
205 
checkItemInfoLocked( final long itemId, final ItemInfo item, StackTraceElement[] stackTrace)206     static void checkItemInfoLocked(
207             final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
208         ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId);
209         if (modelItem != null && item != modelItem) {
210             // check all the data is consistent
211             if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
212                 ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
213                 ShortcutInfo shortcut = (ShortcutInfo) item;
214                 if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
215                         modelShortcut.intent.filterEquals(shortcut.intent) &&
216                         modelShortcut.id == shortcut.id &&
217                         modelShortcut.itemType == shortcut.itemType &&
218                         modelShortcut.container == shortcut.container &&
219                         modelShortcut.screenId == shortcut.screenId &&
220                         modelShortcut.cellX == shortcut.cellX &&
221                         modelShortcut.cellY == shortcut.cellY &&
222                         modelShortcut.spanX == shortcut.spanX &&
223                         modelShortcut.spanY == shortcut.spanY) {
224                     // For all intents and purposes, this is the same object
225                     return;
226                 }
227             }
228 
229             // the modelItem needs to match up perfectly with item if our model is
230             // to be consistent with the database-- for now, just require
231             // modelItem == item or the equality check above
232             String msg = "item: " + ((item != null) ? item.toString() : "null") +
233                     "modelItem: " +
234                     ((modelItem != null) ? modelItem.toString() : "null") +
235                     "Error: ItemInfo passed to checkItemInfo doesn't match original";
236             RuntimeException e = new RuntimeException(msg);
237             if (stackTrace != null) {
238                 e.setStackTrace(stackTrace);
239             }
240             throw e;
241         }
242     }
243 
checkItemInfo(final ItemInfo item)244     static void checkItemInfo(final ItemInfo item) {
245         final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
246         final long itemId = item.id;
247         Runnable r = new Runnable() {
248             public void run() {
249                 synchronized (sBgDataModel) {
250                     checkItemInfoLocked(itemId, item, stackTrace);
251                 }
252             }
253         };
254         runOnWorkerThread(r);
255     }
256 
257     /**
258      * Update the order of the workspace screens in the database. The array list contains
259      * a list of screen ids in the order that they should appear.
260      */
updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens)261     public static void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
262         final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
263         final ContentResolver cr = context.getContentResolver();
264         final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
265 
266         // Remove any negative screen ids -- these aren't persisted
267         Iterator<Long> iter = screensCopy.iterator();
268         while (iter.hasNext()) {
269             long id = iter.next();
270             if (id < 0) {
271                 iter.remove();
272             }
273         }
274 
275         Runnable r = new Runnable() {
276             @Override
277             public void run() {
278                 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
279                 // Clear the table
280                 ops.add(ContentProviderOperation.newDelete(uri).build());
281                 int count = screensCopy.size();
282                 for (int i = 0; i < count; i++) {
283                     ContentValues v = new ContentValues();
284                     long screenId = screensCopy.get(i);
285                     v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
286                     v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
287                     ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
288                 }
289 
290                 try {
291                     cr.applyBatch(LauncherProvider.AUTHORITY, ops);
292                 } catch (Exception ex) {
293                     throw new RuntimeException(ex);
294                 }
295 
296                 synchronized (sBgDataModel) {
297                     sBgDataModel.workspaceScreens.clear();
298                     sBgDataModel.workspaceScreens.addAll(screensCopy);
299                 }
300             }
301         };
302         runOnWorkerThread(r);
303     }
304 
305     /**
306      * Set this as the current Launcher activity object for the loader.
307      */
initialize(Callbacks callbacks)308     public void initialize(Callbacks callbacks) {
309         synchronized (mLock) {
310             Preconditions.assertUIThread();
311             mCallbacks = new WeakReference<>(callbacks);
312         }
313     }
314 
315     @Override
onPackageChanged(String packageName, UserHandle user)316     public void onPackageChanged(String packageName, UserHandle user) {
317         int op = PackageUpdatedTask.OP_UPDATE;
318         enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
319     }
320 
321     @Override
onPackageRemoved(String packageName, UserHandle user)322     public void onPackageRemoved(String packageName, UserHandle user) {
323         onPackagesRemoved(user, packageName);
324     }
325 
onPackagesRemoved(UserHandle user, String... packages)326     public void onPackagesRemoved(UserHandle user, String... packages) {
327         int op = PackageUpdatedTask.OP_REMOVE;
328         enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages));
329     }
330 
331     @Override
onPackageAdded(String packageName, UserHandle user)332     public void onPackageAdded(String packageName, UserHandle user) {
333         int op = PackageUpdatedTask.OP_ADD;
334         enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
335     }
336 
337     @Override
onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing)338     public void onPackagesAvailable(String[] packageNames, UserHandle user,
339             boolean replacing) {
340         enqueueModelUpdateTask(
341                 new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, user, packageNames));
342     }
343 
344     @Override
onPackagesUnavailable(String[] packageNames, UserHandle user, boolean replacing)345     public void onPackagesUnavailable(String[] packageNames, UserHandle user,
346             boolean replacing) {
347         if (!replacing) {
348             enqueueModelUpdateTask(new PackageUpdatedTask(
349                     PackageUpdatedTask.OP_UNAVAILABLE, user, packageNames));
350         }
351     }
352 
353     @Override
onPackagesSuspended(String[] packageNames, UserHandle user)354     public void onPackagesSuspended(String[] packageNames, UserHandle user) {
355         enqueueModelUpdateTask(new PackageUpdatedTask(
356                 PackageUpdatedTask.OP_SUSPEND, user, packageNames));
357     }
358 
359     @Override
onPackagesUnsuspended(String[] packageNames, UserHandle user)360     public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
361         enqueueModelUpdateTask(new PackageUpdatedTask(
362                 PackageUpdatedTask.OP_UNSUSPEND, user, packageNames));
363     }
364 
365     @Override
onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts, UserHandle user)366     public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
367             UserHandle user) {
368         enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true));
369     }
370 
updatePinnedShortcuts(String packageName, List<ShortcutInfoCompat> shortcuts, UserHandle user)371     public void updatePinnedShortcuts(String packageName, List<ShortcutInfoCompat> shortcuts,
372             UserHandle user) {
373         enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, false));
374     }
375 
376     /**
377      * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
378      * ACTION_PACKAGE_CHANGED.
379      */
380     @Override
onReceive(Context context, Intent intent)381     public void onReceive(Context context, Intent intent) {
382         if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
383 
384         final String action = intent.getAction();
385         if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
386             // If we have changed locale we need to clear out the labels in all apps/workspace.
387             forceReload();
388         } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)
389                 || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
390             UserManagerCompat.getInstance(context).enableAndResetCache();
391             forceReload();
392         } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
393                 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
394                 Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
395             UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
396             if (user != null) {
397                 if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
398                         Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
399                     enqueueModelUpdateTask(new PackageUpdatedTask(
400                             PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
401                 }
402 
403                 // ACTION_MANAGED_PROFILE_UNAVAILABLE sends the profile back to locked mode, so
404                 // we need to run the state change task again.
405                 if (Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
406                         Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
407                     enqueueModelUpdateTask(new UserLockStateChangedTask(user));
408                 }
409             }
410         } else if (IS_DOGFOOD_BUILD && ACTION_FORCE_ROLOAD.equals(action)) {
411             forceReload();
412         }
413     }
414 
415     /**
416      * Reloads the workspace items from the DB and re-binds the workspace. This should generally
417      * not be called as DB updates are automatically followed by UI update
418      */
forceReload()419     public void forceReload() {
420         synchronized (mLock) {
421             // Stop any existing loaders first, so they don't set mModelLoaded to true later
422             stopLoader();
423             mModelLoaded = false;
424         }
425 
426         // Start the loader if launcher is already running, otherwise the loader will run,
427         // the next time launcher starts
428         Callbacks callbacks = getCallback();
429         if (callbacks != null) {
430             startLoader(callbacks.getCurrentWorkspaceScreen());
431         }
432     }
433 
isCurrentCallbacks(Callbacks callbacks)434     public boolean isCurrentCallbacks(Callbacks callbacks) {
435         return (mCallbacks != null && mCallbacks.get() == callbacks);
436     }
437 
438     /**
439      * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible.
440      * @return true if the page could be bound synchronously.
441      */
startLoader(int synchronousBindPage)442     public boolean startLoader(int synchronousBindPage) {
443         // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
444         InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_LOADER_RUNNING);
445         synchronized (mLock) {
446             // Don't bother to start the thread if we know it's not going to do anything
447             if (mCallbacks != null && mCallbacks.get() != null) {
448                 final Callbacks oldCallbacks = mCallbacks.get();
449                 // Clear any pending bind-runnables from the synchronized load process.
450                 mUiExecutor.execute(oldCallbacks::clearPendingBinds);
451 
452                 // If there is already one running, tell it to stop.
453                 stopLoader();
454                 LoaderResults loaderResults = new LoaderResults(mApp, sBgDataModel,
455                         mBgAllAppsList, synchronousBindPage, mCallbacks);
456                 if (mModelLoaded && !mIsLoaderTaskRunning) {
457                     // Divide the set of loaded items into those that we are binding synchronously,
458                     // and everything else that is to be bound normally (asynchronously).
459                     loaderResults.bindWorkspace();
460                     // For now, continue posting the binding of AllApps as there are other
461                     // issues that arise from that.
462                     loaderResults.bindAllApps();
463                     loaderResults.bindDeepShortcuts();
464                     loaderResults.bindWidgets();
465                     return true;
466                 } else {
467                     startLoaderForResults(loaderResults);
468                 }
469             }
470         }
471         return false;
472     }
473 
474     /**
475      * If there is already a loader task running, tell it to stop.
476      */
stopLoader()477     public void stopLoader() {
478         synchronized (mLock) {
479             LoaderTask oldTask = mLoaderTask;
480             mLoaderTask = null;
481             if (oldTask != null) {
482                 oldTask.stopLocked();
483             }
484         }
485     }
486 
startLoaderForResults(LoaderResults results)487     public void startLoaderForResults(LoaderResults results) {
488         synchronized (mLock) {
489             stopLoader();
490             mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, sBgDataModel, results);
491             runOnWorkerThread(mLoaderTask);
492         }
493     }
494 
startLoaderForResultsIfNotLoaded(LoaderResults results)495     public void startLoaderForResultsIfNotLoaded(LoaderResults results) {
496         synchronized (mLock) {
497             if (!isModelLoaded()) {
498                 Log.d(TAG, "Workspace not loaded, loading now");
499                 startLoaderForResults(results);
500             }
501         }
502     }
503 
504     /**
505      * Loads the workspace screen ids in an ordered list.
506      */
loadWorkspaceScreensDb(Context context)507     public static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
508         final ContentResolver contentResolver = context.getContentResolver();
509         final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
510 
511         // Get screens ordered by rank.
512         return LauncherDbUtils.getScreenIdsFromCursor(contentResolver.query(
513                 screensUri, null, null, null, LauncherSettings.WorkspaceScreens.SCREEN_RANK));
514     }
515 
onInstallSessionCreated(final PackageInstallInfo sessionInfo)516     public void onInstallSessionCreated(final PackageInstallInfo sessionInfo) {
517         enqueueModelUpdateTask(new BaseModelUpdateTask() {
518             @Override
519             public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
520                 apps.addPromiseApp(app.getContext(), sessionInfo);
521                 if (!apps.added.isEmpty()) {
522                     final ArrayList<AppInfo> arrayList = new ArrayList<>(apps.added);
523                     apps.added.clear();
524                     scheduleCallbackTask(new CallbackTask() {
525                         @Override
526                         public void execute(Callbacks callbacks) {
527                             callbacks.bindAppsAddedOrUpdated(arrayList);
528                         }
529                     });
530                 }
531             }
532         });
533     }
534 
535     public class LoaderTransaction implements AutoCloseable {
536 
537         private final LoaderTask mTask;
538 
LoaderTransaction(LoaderTask task)539         private LoaderTransaction(LoaderTask task) throws CancellationException {
540             synchronized (mLock) {
541                 if (mLoaderTask != task) {
542                     throw new CancellationException("Loader already stopped");
543                 }
544                 mTask = task;
545                 mIsLoaderTaskRunning = true;
546                 mModelLoaded = false;
547             }
548         }
549 
commit()550         public void commit() {
551             synchronized (mLock) {
552                 // Everything loaded bind the data.
553                 mModelLoaded = true;
554             }
555         }
556 
557         @Override
close()558         public void close() {
559             synchronized (mLock) {
560                 // If we are still the last one to be scheduled, remove ourselves.
561                 if (mLoaderTask == mTask) {
562                     mLoaderTask = null;
563                 }
564                 mIsLoaderTaskRunning = false;
565             }
566         }
567     }
568 
beginLoader(LoaderTask task)569     public LoaderTransaction beginLoader(LoaderTask task) throws CancellationException {
570         return new LoaderTransaction(task);
571     }
572 
573     /**
574      * Refreshes the cached shortcuts if the shortcut permission has changed.
575      * Current implementation simply reloads the workspace, but it can be optimized to
576      * use partial updates similar to {@link UserManagerCompat}
577      */
refreshShortcutsIfRequired()578     public void refreshShortcutsIfRequired() {
579         if (Utilities.ATLEAST_NOUGAT_MR1) {
580             sWorker.removeCallbacks(mShortcutPermissionCheckRunnable);
581             sWorker.post(mShortcutPermissionCheckRunnable);
582         }
583     }
584 
585     /**
586      * Called when the icons for packages have been updated in the icon cache.
587      */
onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandle user)588     public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandle user) {
589         // If any package icon has changed (app was updated while launcher was dead),
590         // update the corresponding shortcuts.
591         enqueueModelUpdateTask(new CacheDataUpdatedTask(
592                 CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages));
593     }
594 
enqueueModelUpdateTask(ModelUpdateTask task)595     public void enqueueModelUpdateTask(ModelUpdateTask task) {
596         task.init(mApp, this, sBgDataModel, mBgAllAppsList, mUiExecutor);
597         runOnWorkerThread(task);
598     }
599 
600     /**
601      * A task to be executed on the current callbacks on the UI thread.
602      * If there is no current callbacks, the task is ignored.
603      */
604     public interface CallbackTask {
605 
execute(Callbacks callbacks)606         void execute(Callbacks callbacks);
607     }
608 
609     /**
610      * A runnable which changes/updates the data model of the launcher based on certain events.
611      */
612     public interface ModelUpdateTask extends Runnable {
613 
614         /**
615          * Called before the task is posted to initialize the internal state.
616          */
init(LauncherAppState app, LauncherModel model, BgDataModel dataModel, AllAppsList allAppsList, Executor uiExecutor)617         void init(LauncherAppState app, LauncherModel model,
618                 BgDataModel dataModel, AllAppsList allAppsList, Executor uiExecutor);
619 
620     }
621 
updateAndBindShortcutInfo(final ShortcutInfo si, final ShortcutInfoCompat info)622     public void updateAndBindShortcutInfo(final ShortcutInfo si, final ShortcutInfoCompat info) {
623         updateAndBindShortcutInfo(new Provider<ShortcutInfo>() {
624             @Override
625             public ShortcutInfo get() {
626                 si.updateFromDeepShortcutInfo(info, mApp.getContext());
627                 LauncherIcons li = LauncherIcons.obtain(mApp.getContext());
628                 li.createShortcutIcon(info).applyTo(si);
629                 li.recycle();
630                 return si;
631             }
632         });
633     }
634 
635     /**
636      * Utility method to update a shortcut on the background thread.
637      */
updateAndBindShortcutInfo(final Provider<ShortcutInfo> shortcutProvider)638     public void updateAndBindShortcutInfo(final Provider<ShortcutInfo> shortcutProvider) {
639         enqueueModelUpdateTask(new BaseModelUpdateTask() {
640             @Override
641             public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
642                 ShortcutInfo info = shortcutProvider.get();
643                 ArrayList<ShortcutInfo> update = new ArrayList<>();
644                 update.add(info);
645                 bindUpdatedShortcuts(update, info.user);
646             }
647         });
648     }
649 
refreshAndBindWidgetsAndShortcuts(@ullable final PackageUserKey packageUser)650     public void refreshAndBindWidgetsAndShortcuts(@Nullable final PackageUserKey packageUser) {
651         enqueueModelUpdateTask(new BaseModelUpdateTask() {
652             @Override
653             public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
654                 dataModel.widgetsModel.update(app, packageUser);
655                 bindUpdatedWidgets(dataModel);
656             }
657         });
658     }
659 
dumpState(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)660     public void dumpState(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
661         if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
662             writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size());
663             for (AppInfo info : mBgAllAppsList.data) {
664                 writer.println(prefix + "   title=\"" + info.title + "\" iconBitmap=" + info.iconBitmap
665                         + " componentName=" + info.componentName.getPackageName());
666             }
667         }
668         sBgDataModel.dump(prefix, fd, writer, args);
669     }
670 
getCallback()671     public Callbacks getCallback() {
672         return mCallbacks != null ? mCallbacks.get() : null;
673     }
674 
675     /**
676      * @return the looper for the worker thread which can be used to start background tasks.
677      */
getWorkerLooper()678     public static Looper getWorkerLooper() {
679         return sWorkerThread.getLooper();
680     }
681 
setWorkerPriority(final int priority)682     public static void setWorkerPriority(final int priority) {
683         Process.setThreadPriority(sWorkerThread.getThreadId(), priority);
684     }
685 }
686