Home | History | Annotate | Download | only in launcher3
      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 {
     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;
    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 {
    138         public boolean setLoadOnResume();
    139         public int getCurrentWorkspaceScreen();
    140         public void clearPendingBinds();
    141         public void startBinding();
    142         public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons);
    143         public void bindScreens(ArrayList<Long> orderedScreenIds);
    144         public void finishFirstPageBind(ViewOnDrawExecutor executor);
    145         public void finishBindingItems();
    146         public void bindAllApplications(ArrayList<AppInfo> apps);
    147         public void bindAppsAddedOrUpdated(ArrayList<AppInfo> apps);
    148         public void bindAppsAdded(ArrayList<Long> newScreens,
    149                                   ArrayList<ItemInfo> addNotAnimated,
    150                                   ArrayList<ItemInfo> addAnimated);
    151         public void bindPromiseAppProgressUpdated(PromiseAppInfo app);
    152         public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated, UserHandle user);
    153         public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
    154         public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
    155         public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher);
    156         public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos);
    157         public void bindAllWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets);
    158         public void onPageBoundSynchronously(int page);
    159         public void executeOnNextDraw(ViewOnDrawExecutor executor);
    160         public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap);
    161     }
    162 
    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. */
    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 
    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      */
    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      */
    196     public void addAndBindAddedWorkspaceItems(
    197             Provider<List<Pair<ItemInfo, Object>>> appsProvider) {
    198         enqueueModelUpdateTask(new AddWorkspaceItemsTask(appsProvider));
    199     }
    200 
    201     public ModelWriter getWriter(boolean hasVerticalHotseat) {
    202         return new ModelWriter(mApp.getContext(), sBgDataModel, hasVerticalHotseat);
    203     }
    204 
    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 
    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      */
    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      */
    307     public void initialize(Callbacks callbacks) {
    308         synchronized (mLock) {
    309             Preconditions.assertUIThread();
    310             mCallbacks = new WeakReference<>(callbacks);
    311         }
    312     }
    313 
    314     @Override
    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
    321     public void onPackageRemoved(String packageName, UserHandle user) {
    322         onPackagesRemoved(user, packageName);
    323     }
    324 
    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
    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
    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
    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
    353     public void onPackagesSuspended(String[] packageNames, UserHandle user) {
    354         enqueueModelUpdateTask(new PackageUpdatedTask(
    355                 PackageUpdatedTask.OP_SUSPEND, user, packageNames));
    356     }
    357 
    358     @Override
    359     public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
    360         enqueueModelUpdateTask(new PackageUpdatedTask(
    361                 PackageUpdatedTask.OP_UNSUSPEND, user, packageNames));
    362     }
    363 
    364     @Override
    365     public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
    366             UserHandle user) {
    367         enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true));
    368     }
    369 
    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
    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      */
    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      */
    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 
    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      */
    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      */
    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 
    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      */
    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 
    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 
    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 
    558         public void commit() {
    559             synchronized (mLock) {
    560                 // Everything loaded bind the data.
    561                 mModelLoaded = true;
    562             }
    563         }
    564 
    565         @Override
    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 
    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      */
    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      */
    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 
    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 
    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          */
    625         void init(LauncherAppState app, LauncherModel model,
    626                 BgDataModel dataModel, AllAppsList allAppsList, Executor uiExecutor);
    627 
    628     }
    629 
    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      */
    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 
    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 
    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 
    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      */
    684     public static Looper getWorkerLooper() {
    685         return sWorkerThread.getLooper();
    686     }
    687 
    688     public static void setWorkerPriority(final int priority) {
    689         Process.setThreadPriority(sWorkerThread.getThreadId(), priority);
    690     }
    691 }
    692