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