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 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 {
     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 {
    138         public void rebindModel();
    139 
    140         public int getCurrentWorkspaceScreen();
    141         public void clearPendingBinds();
    142         public void startBinding();
    143         public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons);
    144         public void bindScreens(ArrayList<Long> orderedScreenIds);
    145         public void finishFirstPageBind(ViewOnDrawExecutor executor);
    146         public void finishBindingItems();
    147         public void bindAllApplications(ArrayList<AppInfo> apps);
    148         public void bindAppsAddedOrUpdated(ArrayList<AppInfo> apps);
    149         public void bindAppsAdded(ArrayList<Long> newScreens,
    150                                   ArrayList<ItemInfo> addNotAnimated,
    151                                   ArrayList<ItemInfo> addAnimated);
    152         public void bindPromiseAppProgressUpdated(PromiseAppInfo app);
    153         public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated, UserHandle user);
    154         public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
    155         public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
    156         public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher);
    157         public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos);
    158         public void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets);
    159         public void onPageBoundSynchronously(int page);
    160         public void executeOnNextDraw(ViewOnDrawExecutor executor);
    161         public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap);
    162     }
    163 
    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. */
    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 
    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      */
    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      */
    197     public void addAndBindAddedWorkspaceItems(List<Pair<ItemInfo, Object>> itemList) {
    198         enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList));
    199     }
    200 
    201     public ModelWriter getWriter(boolean hasVerticalHotseat, boolean verifyChanges) {
    202         return new ModelWriter(mApp.getContext(), this, sBgDataModel,
    203                 hasVerticalHotseat, verifyChanges);
    204     }
    205 
    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 
    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      */
    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      */
    308     public void initialize(Callbacks callbacks) {
    309         synchronized (mLock) {
    310             Preconditions.assertUIThread();
    311             mCallbacks = new WeakReference<>(callbacks);
    312         }
    313     }
    314 
    315     @Override
    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
    322     public void onPackageRemoved(String packageName, UserHandle user) {
    323         onPackagesRemoved(user, packageName);
    324     }
    325 
    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
    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
    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
    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
    354     public void onPackagesSuspended(String[] packageNames, UserHandle user) {
    355         enqueueModelUpdateTask(new PackageUpdatedTask(
    356                 PackageUpdatedTask.OP_SUSPEND, user, packageNames));
    357     }
    358 
    359     @Override
    360     public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
    361         enqueueModelUpdateTask(new PackageUpdatedTask(
    362                 PackageUpdatedTask.OP_UNSUSPEND, user, packageNames));
    363     }
    364 
    365     @Override
    366     public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
    367             UserHandle user) {
    368         enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true));
    369     }
    370 
    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
    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      */
    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 
    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      */
    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      */
    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 
    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 
    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      */
    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 
    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 
    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 
    550         public void commit() {
    551             synchronized (mLock) {
    552                 // Everything loaded bind the data.
    553                 mModelLoaded = true;
    554             }
    555         }
    556 
    557         @Override
    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 
    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      */
    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      */
    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 
    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 
    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          */
    617         void init(LauncherAppState app, LauncherModel model,
    618                 BgDataModel dataModel, AllAppsList allAppsList, Executor uiExecutor);
    619 
    620     }
    621 
    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      */
    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 
    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 
    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 
    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      */
    678     public static Looper getWorkerLooper() {
    679         return sWorkerThread.getLooper();
    680     }
    681 
    682     public static void setWorkerPriority(final int priority) {
    683         Process.setThreadPriority(sWorkerThread.getThreadId(), priority);
    684     }
    685 }
    686