LauncherModel.java revision ad2e91a216edae0f25bce83a2b058c81953981af
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
17package com.android.launcher3;
18
19import android.appwidget.AppWidgetProviderInfo;
20import android.content.BroadcastReceiver;
21import android.content.ComponentName;
22import android.content.ContentProviderOperation;
23import android.content.ContentResolver;
24import android.content.ContentValues;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.content.pm.LauncherActivityInfo;
29import android.net.Uri;
30import android.os.Handler;
31import android.os.HandlerThread;
32import android.os.Looper;
33import android.os.Process;
34import android.os.SystemClock;
35import android.os.Trace;
36import android.os.UserHandle;
37import android.text.TextUtils;
38import android.util.Log;
39import android.util.LongSparseArray;
40import android.util.MutableInt;
41
42import com.android.launcher3.compat.AppWidgetManagerCompat;
43import com.android.launcher3.compat.LauncherAppsCompat;
44import com.android.launcher3.compat.PackageInstallerCompat;
45import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
46import com.android.launcher3.compat.UserManagerCompat;
47import com.android.launcher3.config.ProviderConfig;
48import com.android.launcher3.dynamicui.ExtractionUtils;
49import com.android.launcher3.folder.Folder;
50import com.android.launcher3.folder.FolderIcon;
51import com.android.launcher3.graphics.LauncherIcons;
52import com.android.launcher3.logging.FileLog;
53import com.android.launcher3.model.AddWorkspaceItemsTask;
54import com.android.launcher3.model.BgDataModel;
55import com.android.launcher3.model.CacheDataUpdatedTask;
56import com.android.launcher3.model.ExtendedModelTask;
57import com.android.launcher3.model.GridSizeMigrationTask;
58import com.android.launcher3.model.LoaderCursor;
59import com.android.launcher3.model.ModelWriter;
60import com.android.launcher3.model.PackageInstallStateChangedTask;
61import com.android.launcher3.model.PackageItemInfo;
62import com.android.launcher3.model.PackageUpdatedTask;
63import com.android.launcher3.model.SdCardAvailableReceiver;
64import com.android.launcher3.model.ShortcutsChangedTask;
65import com.android.launcher3.model.UserLockStateChangedTask;
66import com.android.launcher3.model.WidgetItem;
67import com.android.launcher3.model.WidgetsModel;
68import com.android.launcher3.provider.ImportDataTask;
69import com.android.launcher3.provider.LauncherDbUtils;
70import com.android.launcher3.shortcuts.DeepShortcutManager;
71import com.android.launcher3.shortcuts.ShortcutInfoCompat;
72import com.android.launcher3.shortcuts.ShortcutKey;
73import com.android.launcher3.util.ComponentKey;
74import com.android.launcher3.util.ManagedProfileHeuristic;
75import com.android.launcher3.util.MultiHashMap;
76import com.android.launcher3.util.PackageManagerHelper;
77import com.android.launcher3.util.Preconditions;
78import com.android.launcher3.util.Provider;
79import com.android.launcher3.util.Thunk;
80import com.android.launcher3.util.ViewOnDrawExecutor;
81
82import java.io.FileDescriptor;
83import java.io.PrintWriter;
84import java.lang.ref.WeakReference;
85import java.util.ArrayList;
86import java.util.Collections;
87import java.util.Comparator;
88import java.util.HashMap;
89import java.util.HashSet;
90import java.util.Iterator;
91import java.util.List;
92import java.util.Map;
93import java.util.Set;
94import java.util.concurrent.CancellationException;
95import java.util.concurrent.Executor;
96
97/**
98 * Maintains in-memory state of the Launcher. It is expected that there should be only one
99 * LauncherModel object held in a static. Also provide APIs for updating the database state
100 * for the Launcher.
101 */
102public class LauncherModel extends BroadcastReceiver
103        implements LauncherAppsCompat.OnAppsChangedCallbackCompat {
104    static final boolean DEBUG_LOADERS = false;
105    private static final boolean DEBUG_RECEIVER = false;
106
107    static final String TAG = "Launcher.Model";
108
109    private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
110    private static final long INVALID_SCREEN_ID = -1L;
111
112    @Thunk final LauncherAppState mApp;
113    @Thunk final Object mLock = new Object();
114    @Thunk DeferredHandler mHandler = new DeferredHandler();
115    @Thunk LoaderTask mLoaderTask;
116    @Thunk boolean mIsLoaderTaskRunning;
117    @Thunk boolean mHasLoaderCompletedOnce;
118
119    @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
120    static {
121        sWorkerThread.start();
122    }
123    @Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());
124
125    // Indicates whether the current model data is valid or not.
126    // We start off with everything not loaded. After that, we assume that
127    // our monitoring of the package manager provides all updates and we never
128    // need to do a requery. This is only ever touched from the loader thread.
129    private boolean mModelLoaded;
130
131    /**
132     * Set of runnables to be called on the background thread after the workspace binding
133     * is complete.
134     */
135    static final ArrayList<Runnable> mBindCompleteRunnables = new ArrayList<Runnable>();
136
137    @Thunk WeakReference<Callbacks> mCallbacks;
138
139    // < only access in worker thread >
140    private final AllAppsList mBgAllAppsList;
141    // Entire list of widgets.
142    private final WidgetsModel mBgWidgetsModel;
143
144    private boolean mHasShortcutHostPermission;
145    // Runnable to check if the shortcuts permission has changed.
146    private final Runnable mShortcutPermissionCheckRunnable = new Runnable() {
147        @Override
148        public void run() {
149            if (mModelLoaded) {
150                boolean hasShortcutHostPermission =
151                        DeepShortcutManager.getInstance(mApp.getContext()).hasHostPermission();
152                if (hasShortcutHostPermission != mHasShortcutHostPermission) {
153                    forceReload();
154                }
155            }
156        }
157    };
158
159    /**
160     * All the static data should be accessed on the background thread, A lock should be acquired
161     * on this object when accessing any data from this model.
162     */
163    static final BgDataModel sBgDataModel = new BgDataModel();
164
165    // </ only access in worker thread >
166
167    private final IconCache mIconCache;
168
169    private final LauncherAppsCompat mLauncherApps;
170    private final UserManagerCompat mUserManager;
171
172    public interface Callbacks {
173        public boolean setLoadOnResume();
174        public int getCurrentWorkspaceScreen();
175        public void clearPendingBinds();
176        public void startBinding();
177        public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
178                              boolean forceAnimateIcons);
179        public void bindScreens(ArrayList<Long> orderedScreenIds);
180        public void finishFirstPageBind(ViewOnDrawExecutor executor);
181        public void finishBindingItems();
182        public void bindAppWidget(LauncherAppWidgetInfo info);
183        public void bindAllApplications(ArrayList<AppInfo> apps);
184        public void bindAppsAdded(ArrayList<Long> newScreens,
185                                  ArrayList<ItemInfo> addNotAnimated,
186                                  ArrayList<ItemInfo> addAnimated,
187                                  ArrayList<AppInfo> addedApps);
188        public void bindAppsUpdated(ArrayList<AppInfo> apps);
189        public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,
190                ArrayList<ShortcutInfo> removed, UserHandle user);
191        public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
192        public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
193        public void bindWorkspaceComponentsRemoved(
194                HashSet<String> packageNames, HashSet<ComponentName> components,
195                UserHandle user);
196        public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos);
197        public void notifyWidgetProvidersChanged();
198        public void bindAllWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets);
199        public void onPageBoundSynchronously(int page);
200        public void executeOnNextDraw(ViewOnDrawExecutor executor);
201        public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap);
202    }
203
204    LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
205        Context context = app.getContext();
206        mApp = app;
207        mBgAllAppsList = new AllAppsList(iconCache, appFilter);
208        mBgWidgetsModel = new WidgetsModel(iconCache, appFilter);
209        mIconCache = iconCache;
210
211        mLauncherApps = LauncherAppsCompat.getInstance(context);
212        mUserManager = UserManagerCompat.getInstance(context);
213    }
214
215    /** Runs the specified runnable immediately if called from the main thread, otherwise it is
216     * posted on the main thread handler. */
217    private void runOnMainThread(Runnable r) {
218        if (sWorkerThread.getThreadId() == Process.myTid()) {
219            // If we are on the worker thread, post onto the main handler
220            mHandler.post(r);
221        } else {
222            r.run();
223        }
224    }
225
226    /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
227     * posted on the worker thread handler. */
228    private static void runOnWorkerThread(Runnable r) {
229        if (sWorkerThread.getThreadId() == Process.myTid()) {
230            r.run();
231        } else {
232            // If we are not on the worker thread, then post to the worker handler
233            sWorker.post(r);
234        }
235    }
236
237    public void setPackageState(PackageInstallInfo installInfo) {
238        enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo));
239    }
240
241    /**
242     * Updates the icons and label of all pending icons for the provided package name.
243     */
244    public void updateSessionDisplayInfo(final String packageName) {
245        HashSet<String> packages = new HashSet<>();
246        packages.add(packageName);
247        enqueueModelUpdateTask(new CacheDataUpdatedTask(
248                CacheDataUpdatedTask.OP_SESSION_UPDATE, Process.myUserHandle(), packages));
249    }
250
251    /**
252     * Adds the provided items to the workspace.
253     */
254    public void addAndBindAddedWorkspaceItems(List<ItemInfo> workspaceApps) {
255        addAndBindAddedWorkspaceItems(Provider.of(workspaceApps));
256    }
257
258    /**
259     * Adds the provided items to the workspace.
260     */
261    public void addAndBindAddedWorkspaceItems(
262            Provider<List<ItemInfo>> appsProvider) {
263        enqueueModelUpdateTask(new AddWorkspaceItemsTask(appsProvider));
264    }
265
266    public ModelWriter getWriter(boolean hasVerticalHotseat) {
267        return new ModelWriter(mApp.getContext(), sBgDataModel, hasVerticalHotseat);
268    }
269
270    static void checkItemInfoLocked(
271            final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
272        ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId);
273        if (modelItem != null && item != modelItem) {
274            // check all the data is consistent
275            if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
276                ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
277                ShortcutInfo shortcut = (ShortcutInfo) item;
278                if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
279                        modelShortcut.intent.filterEquals(shortcut.intent) &&
280                        modelShortcut.id == shortcut.id &&
281                        modelShortcut.itemType == shortcut.itemType &&
282                        modelShortcut.container == shortcut.container &&
283                        modelShortcut.screenId == shortcut.screenId &&
284                        modelShortcut.cellX == shortcut.cellX &&
285                        modelShortcut.cellY == shortcut.cellY &&
286                        modelShortcut.spanX == shortcut.spanX &&
287                        modelShortcut.spanY == shortcut.spanY) {
288                    // For all intents and purposes, this is the same object
289                    return;
290                }
291            }
292
293            // the modelItem needs to match up perfectly with item if our model is
294            // to be consistent with the database-- for now, just require
295            // modelItem == item or the equality check above
296            String msg = "item: " + ((item != null) ? item.toString() : "null") +
297                    "modelItem: " +
298                    ((modelItem != null) ? modelItem.toString() : "null") +
299                    "Error: ItemInfo passed to checkItemInfo doesn't match original";
300            RuntimeException e = new RuntimeException(msg);
301            if (stackTrace != null) {
302                e.setStackTrace(stackTrace);
303            }
304            throw e;
305        }
306    }
307
308    static void checkItemInfo(final ItemInfo item) {
309        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
310        final long itemId = item.id;
311        Runnable r = new Runnable() {
312            public void run() {
313                synchronized (sBgDataModel) {
314                    checkItemInfoLocked(itemId, item, stackTrace);
315                }
316            }
317        };
318        runOnWorkerThread(r);
319    }
320
321    /**
322     * Update the order of the workspace screens in the database. The array list contains
323     * a list of screen ids in the order that they should appear.
324     */
325    public static void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
326        final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
327        final ContentResolver cr = context.getContentResolver();
328        final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
329
330        // Remove any negative screen ids -- these aren't persisted
331        Iterator<Long> iter = screensCopy.iterator();
332        while (iter.hasNext()) {
333            long id = iter.next();
334            if (id < 0) {
335                iter.remove();
336            }
337        }
338
339        Runnable r = new Runnable() {
340            @Override
341            public void run() {
342                ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
343                // Clear the table
344                ops.add(ContentProviderOperation.newDelete(uri).build());
345                int count = screensCopy.size();
346                for (int i = 0; i < count; i++) {
347                    ContentValues v = new ContentValues();
348                    long screenId = screensCopy.get(i);
349                    v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
350                    v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
351                    ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
352                }
353
354                try {
355                    cr.applyBatch(LauncherProvider.AUTHORITY, ops);
356                } catch (Exception ex) {
357                    throw new RuntimeException(ex);
358                }
359
360                synchronized (sBgDataModel) {
361                    sBgDataModel.workspaceScreens.clear();
362                    sBgDataModel.workspaceScreens.addAll(screensCopy);
363                }
364            }
365        };
366        runOnWorkerThread(r);
367    }
368
369    /**
370     * Set this as the current Launcher activity object for the loader.
371     */
372    public void initialize(Callbacks callbacks) {
373        synchronized (mLock) {
374            Preconditions.assertUIThread();
375            // Remove any queued UI runnables
376            mHandler.cancelAll();
377            mCallbacks = new WeakReference<>(callbacks);
378        }
379    }
380
381    @Override
382    public void onPackageChanged(String packageName, UserHandle user) {
383        int op = PackageUpdatedTask.OP_UPDATE;
384        enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
385    }
386
387    @Override
388    public void onPackageRemoved(String packageName, UserHandle user) {
389        onPackagesRemoved(user, packageName);
390    }
391
392    public void onPackagesRemoved(UserHandle user, String... packages) {
393        int op = PackageUpdatedTask.OP_REMOVE;
394        enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages));
395    }
396
397    @Override
398    public void onPackageAdded(String packageName, UserHandle user) {
399        int op = PackageUpdatedTask.OP_ADD;
400        enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
401    }
402
403    @Override
404    public void onPackagesAvailable(String[] packageNames, UserHandle user,
405            boolean replacing) {
406        enqueueModelUpdateTask(
407                new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, user, packageNames));
408    }
409
410    @Override
411    public void onPackagesUnavailable(String[] packageNames, UserHandle user,
412            boolean replacing) {
413        if (!replacing) {
414            enqueueModelUpdateTask(new PackageUpdatedTask(
415                    PackageUpdatedTask.OP_UNAVAILABLE, user, packageNames));
416        }
417    }
418
419    @Override
420    public void onPackagesSuspended(String[] packageNames, UserHandle user) {
421        enqueueModelUpdateTask(new PackageUpdatedTask(
422                PackageUpdatedTask.OP_SUSPEND, user, packageNames));
423    }
424
425    @Override
426    public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
427        enqueueModelUpdateTask(new PackageUpdatedTask(
428                PackageUpdatedTask.OP_UNSUSPEND, user, packageNames));
429    }
430
431    @Override
432    public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
433            UserHandle user) {
434        enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true));
435    }
436
437    public void updatePinnedShortcuts(String packageName, List<ShortcutInfoCompat> shortcuts,
438            UserHandle user) {
439        enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, false));
440    }
441
442    /**
443     * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
444     * ACTION_PACKAGE_CHANGED.
445     */
446    @Override
447    public void onReceive(Context context, Intent intent) {
448        if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
449
450        final String action = intent.getAction();
451        if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
452            // If we have changed locale we need to clear out the labels in all apps/workspace.
453            forceReload();
454        } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)
455                || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
456            UserManagerCompat.getInstance(context).enableAndResetCache();
457            forceReload();
458        } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
459                Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
460                Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
461            UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
462            if (user != null) {
463                if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
464                        Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
465                    enqueueModelUpdateTask(new PackageUpdatedTask(
466                            PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
467                }
468
469                // ACTION_MANAGED_PROFILE_UNAVAILABLE sends the profile back to locked mode, so
470                // we need to run the state change task again.
471                if (Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
472                        Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
473                    enqueueModelUpdateTask(new UserLockStateChangedTask(user));
474                }
475            }
476        } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(action)) {
477            ExtractionUtils.startColorExtractionServiceIfNecessary(context);
478        }
479    }
480
481    /**
482     * Reloads the workspace items from the DB and re-binds the workspace. This should generally
483     * not be called as DB updates are automatically followed by UI update
484     */
485    public void forceReload() {
486        synchronized (mLock) {
487            // Stop any existing loaders first, so they don't set mModelLoaded to true later
488            stopLoaderLocked();
489            mModelLoaded = false;
490        }
491
492        // Do this here because if the launcher activity is running it will be restarted.
493        // If it's not running startLoaderFromBackground will merely tell it that it needs
494        // to reload.
495        startLoaderFromBackground();
496    }
497
498    /**
499     * When the launcher is in the background, it's possible for it to miss paired
500     * configuration changes.  So whenever we trigger the loader from the background
501     * tell the launcher that it needs to re-run the loader when it comes back instead
502     * of doing it now.
503     */
504    public void startLoaderFromBackground() {
505        Callbacks callbacks = getCallback();
506        if (callbacks != null) {
507            // Only actually run the loader if they're not paused.
508            if (!callbacks.setLoadOnResume()) {
509                startLoader(callbacks.getCurrentWorkspaceScreen());
510            }
511        }
512    }
513
514    /**
515     * If there is already a loader task running, tell it to stop.
516     */
517    private void stopLoaderLocked() {
518        LoaderTask oldTask = mLoaderTask;
519        if (oldTask != null) {
520            oldTask.stopLocked();
521        }
522    }
523
524    public boolean isCurrentCallbacks(Callbacks callbacks) {
525        return (mCallbacks != null && mCallbacks.get() == callbacks);
526    }
527
528    /**
529     * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible.
530     * @return true if the page could be bound synchronously.
531     */
532    public boolean startLoader(int synchronousBindPage) {
533        // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
534        InstallShortcutReceiver.enableInstallQueue();
535        synchronized (mLock) {
536            // Don't bother to start the thread if we know it's not going to do anything
537            if (mCallbacks != null && mCallbacks.get() != null) {
538                final Callbacks oldCallbacks = mCallbacks.get();
539                // Clear any pending bind-runnables from the synchronized load process.
540                runOnMainThread(new Runnable() {
541                    public void run() {
542                        oldCallbacks.clearPendingBinds();
543                    }
544                });
545
546                // If there is already one running, tell it to stop.
547                stopLoaderLocked();
548                mLoaderTask = new LoaderTask(mApp.getContext(), synchronousBindPage);
549                if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
550                        && mModelLoaded && !mIsLoaderTaskRunning) {
551                    mLoaderTask.runBindSynchronousPage(synchronousBindPage);
552                    return true;
553                } else {
554                    sWorkerThread.setPriority(Thread.NORM_PRIORITY);
555                    sWorker.post(mLoaderTask);
556                }
557            }
558        }
559        return false;
560    }
561
562    public void stopLoader() {
563        synchronized (mLock) {
564            if (mLoaderTask != null) {
565                mLoaderTask.stopLocked();
566            }
567        }
568    }
569
570    /**
571     * Loads the workspace screen ids in an ordered list.
572     */
573    public static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
574        final ContentResolver contentResolver = context.getContentResolver();
575        final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
576
577        // Get screens ordered by rank.
578        return LauncherDbUtils.getScreenIdsFromCursor(contentResolver.query(
579                screensUri, null, null, null, LauncherSettings.WorkspaceScreens.SCREEN_RANK));
580    }
581
582    /**
583     * Runnable for the thread that loads the contents of the launcher:
584     *   - workspace icons
585     *   - widgets
586     *   - all apps icons
587     *   - deep shortcuts within apps
588     */
589    private class LoaderTask implements Runnable {
590        private Context mContext;
591        private int mPageToBindFirst;
592
593        @Thunk boolean mIsLoadingAndBindingWorkspace;
594        private boolean mStopped;
595        @Thunk boolean mLoadAndBindStepFinished;
596
597        LoaderTask(Context context, int pageToBindFirst) {
598            mContext = context;
599            mPageToBindFirst = pageToBindFirst;
600        }
601
602        private void waitForIdle() {
603            // Wait until the either we're stopped or the other threads are done.
604            // This way we don't start loading all apps until the workspace has settled
605            // down.
606            synchronized (LoaderTask.this) {
607                final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
608
609                mHandler.postIdle(new Runnable() {
610                        public void run() {
611                            synchronized (LoaderTask.this) {
612                                mLoadAndBindStepFinished = true;
613                                if (DEBUG_LOADERS) {
614                                    Log.d(TAG, "done with previous binding step");
615                                }
616                                LoaderTask.this.notify();
617                            }
618                        }
619                    });
620
621                while (!mStopped && !mLoadAndBindStepFinished) {
622                    try {
623                        // Just in case mFlushingWorkerThread changes but we aren't woken up,
624                        // wait no longer than 1sec at a time
625                        this.wait(1000);
626                    } catch (InterruptedException ex) {
627                        // Ignore
628                    }
629                }
630                if (DEBUG_LOADERS) {
631                    Log.d(TAG, "waited "
632                            + (SystemClock.uptimeMillis()-workspaceWaitTime)
633                            + "ms for previous step to finish binding");
634                }
635            }
636        }
637
638        void runBindSynchronousPage(int synchronousBindPage) {
639            if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) {
640                // Ensure that we have a valid page index to load synchronously
641                throw new RuntimeException("Should not call runBindSynchronousPage() without " +
642                        "valid page index");
643            }
644            if (!mModelLoaded) {
645                // Ensure that we don't try and bind a specified page when the pages have not been
646                // loaded already (we should load everything asynchronously in that case)
647                throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
648            }
649            synchronized (mLock) {
650                if (mIsLoaderTaskRunning) {
651                    // Ensure that we are never running the background loading at this point since
652                    // we also touch the background collections
653                    throw new RuntimeException("Error! Background loading is already running");
654                }
655            }
656
657            // XXX: Throw an exception if we are already loading (since we touch the worker thread
658            //      data structures, we can't allow any other thread to touch that data, but because
659            //      this call is synchronous, we can get away with not locking).
660
661            // The LauncherModel is static in the LauncherAppState and mHandler may have queued
662            // operations from the previous activity.  We need to ensure that all queued operations
663            // are executed before any synchronous binding work is done.
664            mHandler.flush();
665
666            // Divide the set of loaded items into those that we are binding synchronously, and
667            // everything else that is to be bound normally (asynchronously).
668            bindWorkspace(synchronousBindPage);
669            // XXX: For now, continue posting the binding of AllApps as there are other issues that
670            //      arise from that.
671            onlyBindAllApps();
672
673            bindDeepShortcuts();
674        }
675
676        private void verifyNotStopped() throws CancellationException {
677            synchronized (LoaderTask.this) {
678                if (mStopped) {
679                    throw new CancellationException("Loader stopped");
680                }
681            }
682        }
683
684        public void run() {
685            synchronized (mLock) {
686                if (mStopped) {
687                    return;
688                }
689                mIsLoaderTaskRunning = true;
690            }
691
692            try {
693                if (DEBUG_LOADERS) Log.d(TAG, "step 1.1: loading workspace");
694                // Set to false in bindWorkspace()
695                mIsLoadingAndBindingWorkspace = true;
696                loadWorkspace();
697
698                verifyNotStopped();
699                if (DEBUG_LOADERS) Log.d(TAG, "step 1.2: bind workspace workspace");
700                bindWorkspace(mPageToBindFirst);
701
702                // Take a break
703                if (DEBUG_LOADERS) Log.d(TAG, "step 1 completed, wait for idle");
704                waitForIdle();
705                verifyNotStopped();
706
707                // second step
708                if (DEBUG_LOADERS) Log.d(TAG, "step 2.1: loading all apps");
709                loadAllApps();
710
711                verifyNotStopped();
712                if (DEBUG_LOADERS) Log.d(TAG, "step 2.2: Update icon cache");
713                updateIconCache();
714
715                // Take a break
716                if (DEBUG_LOADERS) Log.d(TAG, "step 2 completed, wait for idle");
717                waitForIdle();
718                verifyNotStopped();
719
720                // third step
721                if (DEBUG_LOADERS) Log.d(TAG, "step 3.1: loading deep shortcuts");
722                loadDeepShortcuts();
723
724                verifyNotStopped();
725                if (DEBUG_LOADERS) Log.d(TAG, "step 3.2: bind deep shortcuts");
726                bindDeepShortcuts();
727
728                synchronized (mLock) {
729                    // Everything loaded bind the data.
730                    mModelLoaded = true;
731                    mHasLoaderCompletedOnce = true;
732                }
733            } catch (CancellationException e) {
734              // Loader stopped, ignore
735            } finally {
736                // Clear out this reference, otherwise we end up holding it until all of the
737                // callback runnables are done.
738                mContext = null;
739
740                synchronized (mLock) {
741                    // If we are still the last one to be scheduled, remove ourselves.
742                    if (mLoaderTask == this) {
743                        mLoaderTask = null;
744                    }
745                    mIsLoaderTaskRunning = false;
746                }
747            }
748        }
749
750        public void stopLocked() {
751            synchronized (LoaderTask.this) {
752                mStopped = true;
753                this.notify();
754            }
755        }
756
757        /**
758         * Gets the callbacks object.  If we've been stopped, or if the launcher object
759         * has somehow been garbage collected, return null instead.  Pass in the Callbacks
760         * object that was around when the deferred message was scheduled, and if there's
761         * a new Callbacks object around then also return null.  This will save us from
762         * calling onto it with data that will be ignored.
763         */
764        Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
765            synchronized (mLock) {
766                if (mStopped) {
767                    return null;
768                }
769
770                if (mCallbacks == null) {
771                    return null;
772                }
773
774                final Callbacks callbacks = mCallbacks.get();
775                if (callbacks != oldCallbacks) {
776                    return null;
777                }
778                if (callbacks == null) {
779                    Log.w(TAG, "no mCallbacks");
780                    return null;
781                }
782
783                return callbacks;
784            }
785        }
786
787        private void loadWorkspace() {
788            if (LauncherAppState.PROFILE_STARTUP) {
789                Trace.beginSection("Loading Workspace");
790            }
791
792            final Context context = mContext;
793            final ContentResolver contentResolver = context.getContentResolver();
794            final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
795            final boolean isSafeMode = pmHelper.isSafeMode();
796            final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
797            final DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(context);
798            final boolean isSdCardReady = Utilities.isBootCompleted();
799            final MultiHashMap<UserHandle, String> pendingPackages = new MultiHashMap<>();
800
801            boolean clearDb = false;
802            try {
803                ImportDataTask.performImportIfPossible(context);
804            } catch (Exception e) {
805                // Migration failed. Clear workspace.
806                clearDb = true;
807            }
808
809            if (!clearDb && GridSizeMigrationTask.ENABLED &&
810                    !GridSizeMigrationTask.migrateGridIfNeeded(mContext)) {
811                // Migration failed. Clear workspace.
812                clearDb = true;
813            }
814
815            if (clearDb) {
816                Log.d(TAG, "loadWorkspace: resetting launcher database");
817                LauncherSettings.Settings.call(contentResolver,
818                        LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
819            }
820
821            Log.d(TAG, "loadWorkspace: loading default favorites");
822            LauncherSettings.Settings.call(contentResolver,
823                    LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES);
824
825            synchronized (sBgDataModel) {
826                sBgDataModel.clear();
827
828                final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
829                        .getInstance(mContext).updateAndGetActiveSessionCache();
830                sBgDataModel.workspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
831
832                Map<ShortcutKey, ShortcutInfoCompat> shortcutKeyToPinnedShortcuts = new HashMap<>();
833                final LoaderCursor c = new LoaderCursor(contentResolver.query(
834                        LauncherSettings.Favorites.CONTENT_URI, null, null, null, null), mApp);
835
836                HashMap<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null;
837
838                try {
839                    final int appWidgetIdIndex = c.getColumnIndexOrThrow(
840                            LauncherSettings.Favorites.APPWIDGET_ID);
841                    final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
842                            LauncherSettings.Favorites.APPWIDGET_PROVIDER);
843                    final int spanXIndex = c.getColumnIndexOrThrow
844                            (LauncherSettings.Favorites.SPANX);
845                    final int spanYIndex = c.getColumnIndexOrThrow(
846                            LauncherSettings.Favorites.SPANY);
847                    final int rankIndex = c.getColumnIndexOrThrow(
848                            LauncherSettings.Favorites.RANK);
849                    final int optionsIndex = c.getColumnIndexOrThrow(
850                            LauncherSettings.Favorites.OPTIONS);
851
852                    final LongSparseArray<UserHandle> allUsers = c.allUsers;
853                    final LongSparseArray<Boolean> quietMode = new LongSparseArray<>();
854                    final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
855                    for (UserHandle user : mUserManager.getUserProfiles()) {
856                        long serialNo = mUserManager.getSerialNumberForUser(user);
857                        allUsers.put(serialNo, user);
858                        quietMode.put(serialNo, mUserManager.isQuietModeEnabled(user));
859
860                        boolean userUnlocked = mUserManager.isUserUnlocked(user);
861
862                        // We can only query for shortcuts when the user is unlocked.
863                        if (userUnlocked) {
864                            List<ShortcutInfoCompat> pinnedShortcuts =
865                                    shortcutManager.queryForPinnedShortcuts(null, user);
866                            if (shortcutManager.wasLastCallSuccess()) {
867                                for (ShortcutInfoCompat shortcut : pinnedShortcuts) {
868                                    shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
869                                            shortcut);
870                                }
871                            } else {
872                                // Shortcut manager can fail due to some race condition when the
873                                // lock state changes too frequently. For the purpose of the loading
874                                // shortcuts, consider the user is still locked.
875                                userUnlocked = false;
876                            }
877                        }
878                        unlockedUsers.put(serialNo, userUnlocked);
879                    }
880
881                    ShortcutInfo info;
882                    LauncherAppWidgetInfo appWidgetInfo;
883                    Intent intent;
884                    String targetPkg;
885
886                    while (!mStopped && c.moveToNext()) {
887                        try {
888                            if (c.user == null) {
889                                // User has been deleted, remove the item.
890                                c.markDeleted("User has been deleted");
891                                continue;
892                            }
893
894                            boolean allowMissingTarget = false;
895                            switch (c.itemType) {
896                            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
897                            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
898                            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
899                                intent = c.parseIntent();
900                                if (intent == null) {
901                                    c.markDeleted("Invalid or null intent");
902                                    continue;
903                                }
904
905                                int disabledState = quietMode.get(c.serialNumber) ?
906                                        ShortcutInfo.FLAG_DISABLED_QUIET_USER : 0;
907                                ComponentName cn = intent.getComponent();
908                                targetPkg = cn == null ? intent.getPackage() : cn.getPackageName();
909
910                                if (!Process.myUserHandle().equals(c.user)) {
911                                    if (c.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
912                                        c.markDeleted("Legacy shortcuts are only allowed for default user");
913                                        continue;
914                                    } else if (c.restoreFlag != 0) {
915                                        // Don't restore items for other profiles.
916                                        c.markDeleted("Restore from managed profile not supported");
917                                        continue;
918                                    }
919                                }
920                                if (TextUtils.isEmpty(targetPkg) &&
921                                        c.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
922                                    c.markDeleted("Only legacy shortcuts can have null package");
923                                    continue;
924                                }
925
926                                // If there is no target package, its an implicit intent
927                                // (legacy shortcut) which is always valid
928                                boolean validTarget = TextUtils.isEmpty(targetPkg) ||
929                                        launcherApps.isPackageEnabledForProfile(targetPkg, c.user);
930
931                                if (cn != null && validTarget) {
932                                    // If the apk is present and the shortcut points to a specific
933                                    // component.
934
935                                    // If the component is already present
936                                    if (launcherApps.isActivityEnabledForProfile(cn, c.user)) {
937                                        // no special handling necessary for this item
938                                        c.markRestored();
939                                    } else {
940                                        if (c.hasRestoreFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
941                                            // We allow auto install apps to have their intent
942                                            // updated after an install.
943                                            intent = pmHelper.getAppLaunchIntent(targetPkg, c.user);
944                                            if (intent != null) {
945                                                c.restoreFlag = 0;
946                                                c.updater().put(
947                                                        LauncherSettings.Favorites.INTENT,
948                                                        intent.toUri(0)).commit();
949                                                cn = intent.getComponent();
950                                            } else {
951                                                c.markDeleted("Unable to find a launch target");
952                                                continue;
953                                            }
954                                        } else {
955                                            // The app is installed but the component is no
956                                            // longer available.
957                                            c.markDeleted("Invalid component removed: " + cn);
958                                            continue;
959                                        }
960                                    }
961                                }
962                                // else if cn == null => can't infer much, leave it
963                                // else if !validPkg => could be restored icon or missing sd-card
964
965                                if (!TextUtils.isEmpty(targetPkg) && !validTarget) {
966                                    // Points to a valid app (superset of cn != null) but the apk
967                                    // is not available.
968
969                                    if (c.restoreFlag != 0) {
970                                        // Package is not yet available but might be
971                                        // installed later.
972                                        FileLog.d(TAG, "package not yet restored: " + targetPkg);
973
974                                        if (c.hasRestoreFlag(ShortcutInfo.FLAG_RESTORE_STARTED)) {
975                                            // Restore has started once.
976                                        } else if (installingPkgs.containsKey(targetPkg)) {
977                                            // App restore has started. Update the flag
978                                            c.restoreFlag |= ShortcutInfo.FLAG_RESTORE_STARTED;
979                                            c.updater().commit();
980                                        } else {
981                                            c.markDeleted("Unrestored app removed: " + targetPkg);
982                                            continue;
983                                        }
984                                    } else if (pmHelper.isAppOnSdcard(targetPkg, c.user)) {
985                                        // Package is present but not available.
986                                        disabledState |= ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
987                                        // Add the icon on the workspace anyway.
988                                        allowMissingTarget = true;
989                                    } else if (!isSdCardReady) {
990                                        // SdCard is not ready yet. Package might get available,
991                                        // once it is ready.
992                                        Log.d(TAG, "Missing pkg, will check later: " + targetPkg);
993                                        pendingPackages.addToList(c.user, targetPkg);
994                                        // Add the icon on the workspace anyway.
995                                        allowMissingTarget = true;
996                                    } else {
997                                        // Do not wait for external media load anymore.
998                                        c.markDeleted("Invalid package removed: " + targetPkg);
999                                        continue;
1000                                    }
1001                                }
1002
1003                                if (validTarget) {
1004                                    // The shortcut points to a valid target (either no target
1005                                    // or something which is ready to be used)
1006                                    c.markRestored();
1007                                }
1008
1009                                boolean useLowResIcon = !c.isOnWorkspaceOrHotseat() &&
1010                                        c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
1011
1012                                if (c.restoreFlag != 0) {
1013                                    // Already verified above that user is same as default user
1014                                    info = c.getRestoredItemInfo(intent);
1015                                } else if (c.itemType ==
1016                                        LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
1017                                    info = c.getAppShortcutInfo(
1018                                            intent, allowMissingTarget, useLowResIcon);
1019                                } else if (c.itemType ==
1020                                        LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
1021
1022                                    ShortcutKey key = ShortcutKey.fromIntent(intent, c.user);
1023                                    if (unlockedUsers.get(c.serialNumber)) {
1024                                        ShortcutInfoCompat pinnedShortcut =
1025                                                shortcutKeyToPinnedShortcuts.get(key);
1026                                        if (pinnedShortcut == null) {
1027                                            // The shortcut is no longer valid.
1028                                            c.markDeleted("Pinned shortcut not found");
1029                                            continue;
1030                                        }
1031                                        info = new ShortcutInfo(pinnedShortcut, context);
1032                                        info.iconBitmap = LauncherIcons
1033                                                .createShortcutIcon(pinnedShortcut, context);
1034                                        if (pmHelper.isAppSuspended(
1035                                                pinnedShortcut.getPackage(), info.user)) {
1036                                            info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
1037                                        }
1038                                        intent = info.intent;
1039                                    } else {
1040                                        // Create a shortcut info in disabled mode for now.
1041                                        info = c.loadSimpleShortcut();
1042                                        info.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
1043                                    }
1044                                } else { // item type == ITEM_TYPE_SHORTCUT
1045                                    info = c.loadSimpleShortcut();
1046
1047                                    // Shortcuts are only available on the primary profile
1048                                    if (pmHelper.isAppSuspended(targetPkg, c.user)) {
1049                                        disabledState |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
1050                                    }
1051
1052                                    // App shortcuts that used to be automatically added to Launcher
1053                                    // didn't always have the correct intent flags set, so do that
1054                                    // here
1055                                    if (intent.getAction() != null &&
1056                                        intent.getCategories() != null &&
1057                                        intent.getAction().equals(Intent.ACTION_MAIN) &&
1058                                        intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
1059                                        intent.addFlags(
1060                                            Intent.FLAG_ACTIVITY_NEW_TASK |
1061                                            Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
1062                                    }
1063                                }
1064
1065                                if (info != null) {
1066                                    c.applyCommonProperties(info);
1067
1068                                    info.intent = intent;
1069                                    info.rank = c.getInt(rankIndex);
1070                                    info.spanX = 1;
1071                                    info.spanY = 1;
1072                                    // TODO: Remove this extra. Instead we should be using
1073                                    // itemInfo#user.
1074                                    info.intent.putExtra(ItemInfo.EXTRA_PROFILE, c.serialNumber);
1075                                    info.isDisabled |= disabledState;
1076                                    if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
1077                                        info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
1078                                    }
1079
1080                                    if (c.restoreFlag != 0 && !TextUtils.isEmpty(targetPkg)) {
1081                                        Integer progress = installingPkgs.get(targetPkg);
1082                                        if (progress != null) {
1083                                            info.setInstallProgress(progress);
1084                                        } else {
1085                                            info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
1086                                        }
1087                                    }
1088
1089                                    c.checkAndAddItem(info, sBgDataModel);
1090                                } else {
1091                                    throw new RuntimeException("Unexpected null ShortcutInfo");
1092                                }
1093                                break;
1094
1095                            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1096                                FolderInfo folderInfo = sBgDataModel.findOrMakeFolder(c.id);
1097                                c.applyCommonProperties(folderInfo);
1098
1099                                // Do not trim the folder label, as is was set by the user.
1100                                folderInfo.title = c.getString(c.titleIndex);
1101                                folderInfo.spanX = 1;
1102                                folderInfo.spanY = 1;
1103                                folderInfo.options = c.getInt(optionsIndex);
1104
1105                                // no special handling required for restored folders
1106                                c.markRestored();
1107
1108                                c.checkAndAddItem(folderInfo, sBgDataModel);
1109                                break;
1110
1111                            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1112                            case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
1113                                // Read all Launcher-specific widget details
1114                                boolean customWidget = c.itemType ==
1115                                    LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
1116
1117                                int appWidgetId = c.getInt(appWidgetIdIndex);
1118                                String savedProvider = c.getString(appWidgetProviderIndex);
1119
1120                                final ComponentName component =
1121                                        ComponentName.unflattenFromString(savedProvider);
1122
1123                                final boolean isIdValid = !c.hasRestoreFlag(
1124                                        LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
1125                                final boolean wasProviderReady = !c.hasRestoreFlag(
1126                                        LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY);
1127
1128                                if (widgetProvidersMap == null) {
1129                                    widgetProvidersMap = AppWidgetManagerCompat
1130                                            .getInstance(mContext).getAllProvidersMap();
1131                                }
1132                                final AppWidgetProviderInfo provider = widgetProvidersMap.get(
1133                                        new ComponentKey(
1134                                                ComponentName.unflattenFromString(savedProvider),
1135                                                c.user));
1136
1137                                final boolean isProviderReady = isValidProvider(provider);
1138                                if (!isSafeMode && !customWidget &&
1139                                        wasProviderReady && !isProviderReady) {
1140                                    c.markDeleted(
1141                                            "Deleting widget that isn't installed anymore: "
1142                                            + provider);
1143                                } else {
1144                                    if (isProviderReady) {
1145                                        appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
1146                                                provider.provider);
1147
1148                                        // The provider is available. So the widget is either
1149                                        // available or not available. We do not need to track
1150                                        // any future restore updates.
1151                                        int status = c.restoreFlag &
1152                                                ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
1153                                        if (!wasProviderReady) {
1154                                            // If provider was not previously ready, update the
1155                                            // status and UI flag.
1156
1157                                            // Id would be valid only if the widget restore broadcast was received.
1158                                            if (isIdValid) {
1159                                                status |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
1160                                            } else {
1161                                                status &= ~LauncherAppWidgetInfo
1162                                                        .FLAG_PROVIDER_NOT_READY;
1163                                            }
1164                                        }
1165                                        appWidgetInfo.restoreStatus = status;
1166                                    } else {
1167                                        Log.v(TAG, "Widget restore pending id=" + c.id
1168                                                + " appWidgetId=" + appWidgetId
1169                                                + " status =" + c.restoreFlag);
1170                                        appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
1171                                                component);
1172                                        appWidgetInfo.restoreStatus = c.restoreFlag;
1173                                        Integer installProgress = installingPkgs.get(component.getPackageName());
1174
1175                                        if (c.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_RESTORE_STARTED)) {
1176                                            // Restore has started once.
1177                                        } else if (installProgress != null) {
1178                                            // App restore has started. Update the flag
1179                                            appWidgetInfo.restoreStatus |=
1180                                                    LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
1181                                        } else if (!isSafeMode) {
1182                                            c.markDeleted("Unrestored widget removed: " + component);
1183                                            continue;
1184                                        }
1185
1186                                        appWidgetInfo.installProgress =
1187                                                installProgress == null ? 0 : installProgress;
1188                                    }
1189                                    if (appWidgetInfo.hasRestoreFlag(
1190                                            LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG)) {
1191                                        appWidgetInfo.bindOptions = c.parseIntent();
1192                                    }
1193
1194                                    c.applyCommonProperties(appWidgetInfo);
1195                                    appWidgetInfo.spanX = c.getInt(spanXIndex);
1196                                    appWidgetInfo.spanY = c.getInt(spanYIndex);
1197                                    appWidgetInfo.user = c.user;
1198
1199                                    if (!c.isOnWorkspaceOrHotseat()) {
1200                                        c.markDeleted("Widget found where container != " +
1201                                                "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
1202                                        continue;
1203                                    }
1204
1205                                    if (!customWidget) {
1206                                        String providerName =
1207                                                appWidgetInfo.providerName.flattenToString();
1208                                        if (!providerName.equals(savedProvider) ||
1209                                                (appWidgetInfo.restoreStatus != c.restoreFlag)) {
1210                                            c.updater()
1211                                                    .put(LauncherSettings.Favorites.APPWIDGET_PROVIDER,
1212                                                            providerName)
1213                                                    .put(LauncherSettings.Favorites.RESTORED,
1214                                                            appWidgetInfo.restoreStatus)
1215                                                    .commit();
1216                                        }
1217                                    }
1218                                    c.checkAndAddItem(appWidgetInfo, sBgDataModel);
1219                                }
1220                                break;
1221                            }
1222                        } catch (Exception e) {
1223                            Log.e(TAG, "Desktop items loading interrupted", e);
1224                        }
1225                    }
1226                } finally {
1227                    Utilities.closeSilently(c);
1228                }
1229
1230                // Break early if we've stopped loading
1231                if (mStopped) {
1232                    sBgDataModel.clear();
1233                    return;
1234                }
1235
1236                // Remove dead items
1237                if (c.commitDeleted()) {
1238                    // Remove any empty folder
1239                    ArrayList<Long> deletedFolderIds = (ArrayList<Long>) LauncherSettings.Settings
1240                            .call(contentResolver,
1241                                    LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS)
1242                            .getSerializable(LauncherSettings.Settings.EXTRA_VALUE);
1243                    for (long folderId : deletedFolderIds) {
1244                        sBgDataModel.workspaceItems.remove(sBgDataModel.folders.get(folderId));
1245                        sBgDataModel.folders.remove(folderId);
1246                        sBgDataModel.itemsIdMap.remove(folderId);
1247                    }
1248                }
1249
1250                // Unpin shortcuts that don't exist on the workspace.
1251                HashSet<ShortcutKey> pendingShortcuts =
1252                        InstallShortcutReceiver.getPendingShortcuts(context);
1253                for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) {
1254                    MutableInt numTimesPinned = sBgDataModel.pinnedShortcutCounts.get(key);
1255                    if ((numTimesPinned == null || numTimesPinned.value == 0)
1256                            && !pendingShortcuts.contains(key)) {
1257                        // Shortcut is pinned but doesn't exist on the workspace; unpin it.
1258                        shortcutManager.unpinShortcut(key);
1259                    }
1260                }
1261
1262                // Sort all the folder items and make sure the first 3 items are high resolution.
1263                for (FolderInfo folder : sBgDataModel.folders) {
1264                    Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
1265                    int pos = 0;
1266                    for (ShortcutInfo info : folder.contents) {
1267                        if (info.usingLowResIcon &&
1268                                info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
1269                            mIconCache.getTitleAndIcon(info, false);
1270                        }
1271                        pos ++;
1272                        if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
1273                            break;
1274                        }
1275                    }
1276                }
1277
1278                c.commitRestoredItems();
1279                if (!isSdCardReady && !pendingPackages.isEmpty()) {
1280                    context.registerReceiver(
1281                            new SdCardAvailableReceiver(
1282                                    LauncherModel.this, mContext, pendingPackages),
1283                            new IntentFilter(Intent.ACTION_BOOT_COMPLETED),
1284                            null,
1285                            sWorker);
1286                }
1287
1288                // Remove any empty screens
1289                ArrayList<Long> unusedScreens = new ArrayList<>(sBgDataModel.workspaceScreens);
1290                for (ItemInfo item: sBgDataModel.itemsIdMap) {
1291                    long screenId = item.screenId;
1292                    if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1293                            unusedScreens.contains(screenId)) {
1294                        unusedScreens.remove(screenId);
1295                    }
1296                }
1297
1298                // If there are any empty screens remove them, and update.
1299                if (unusedScreens.size() != 0) {
1300                    sBgDataModel.workspaceScreens.removeAll(unusedScreens);
1301                    updateWorkspaceScreenOrder(context, sBgDataModel.workspaceScreens);
1302                }
1303            }
1304            if (LauncherAppState.PROFILE_STARTUP) {
1305                Trace.endSection();
1306            }
1307        }
1308
1309        /** Filters the set of items who are directly or indirectly (via another container) on the
1310         * specified screen. */
1311        private void filterCurrentWorkspaceItems(long currentScreenId,
1312                ArrayList<ItemInfo> allWorkspaceItems,
1313                ArrayList<ItemInfo> currentScreenItems,
1314                ArrayList<ItemInfo> otherScreenItems) {
1315            // Purge any null ItemInfos
1316            Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
1317            while (iter.hasNext()) {
1318                ItemInfo i = iter.next();
1319                if (i == null) {
1320                    iter.remove();
1321                }
1322            }
1323
1324            // Order the set of items by their containers first, this allows use to walk through the
1325            // list sequentially, build up a list of containers that are in the specified screen,
1326            // as well as all items in those containers.
1327            Set<Long> itemsOnScreen = new HashSet<Long>();
1328            Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
1329                @Override
1330                public int compare(ItemInfo lhs, ItemInfo rhs) {
1331                    return Utilities.longCompare(lhs.container, rhs.container);
1332                }
1333            });
1334            for (ItemInfo info : allWorkspaceItems) {
1335                if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1336                    if (info.screenId == currentScreenId) {
1337                        currentScreenItems.add(info);
1338                        itemsOnScreen.add(info.id);
1339                    } else {
1340                        otherScreenItems.add(info);
1341                    }
1342                } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1343                    currentScreenItems.add(info);
1344                    itemsOnScreen.add(info.id);
1345                } else {
1346                    if (itemsOnScreen.contains(info.container)) {
1347                        currentScreenItems.add(info);
1348                        itemsOnScreen.add(info.id);
1349                    } else {
1350                        otherScreenItems.add(info);
1351                    }
1352                }
1353            }
1354        }
1355
1356        /** Filters the set of widgets which are on the specified screen. */
1357        private void filterCurrentAppWidgets(long currentScreenId,
1358                ArrayList<LauncherAppWidgetInfo> appWidgets,
1359                ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
1360                ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
1361
1362            for (LauncherAppWidgetInfo widget : appWidgets) {
1363                if (widget == null) continue;
1364                if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1365                        widget.screenId == currentScreenId) {
1366                    currentScreenWidgets.add(widget);
1367                } else {
1368                    otherScreenWidgets.add(widget);
1369                }
1370            }
1371        }
1372
1373        /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
1374         * right) */
1375        private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
1376            final InvariantDeviceProfile profile = mApp.getInvariantDeviceProfile();
1377            final int screenCols = profile.numColumns;
1378            final int screenCellCount = profile.numColumns * profile.numRows;
1379            Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
1380                @Override
1381                public int compare(ItemInfo lhs, ItemInfo rhs) {
1382                    if (lhs.container == rhs.container) {
1383                        // Within containers, order by their spatial position in that container
1384                        switch ((int) lhs.container) {
1385                            case LauncherSettings.Favorites.CONTAINER_DESKTOP: {
1386                                long lr = (lhs.screenId * screenCellCount +
1387                                        lhs.cellY * screenCols + lhs.cellX);
1388                                long rr = (rhs.screenId * screenCellCount +
1389                                        rhs.cellY * screenCols + rhs.cellX);
1390                                return Utilities.longCompare(lr, rr);
1391                            }
1392                            case LauncherSettings.Favorites.CONTAINER_HOTSEAT: {
1393                                // We currently use the screen id as the rank
1394                                return Utilities.longCompare(lhs.screenId, rhs.screenId);
1395                            }
1396                            default:
1397                                if (ProviderConfig.IS_DOGFOOD_BUILD) {
1398                                    throw new RuntimeException("Unexpected container type when " +
1399                                            "sorting workspace items.");
1400                                }
1401                                return 0;
1402                        }
1403                    } else {
1404                        // Between containers, order by hotseat, desktop
1405                        return Utilities.longCompare(lhs.container, rhs.container);
1406                    }
1407                }
1408            });
1409        }
1410
1411        private void bindWorkspaceScreens(final Callbacks oldCallbacks,
1412                final ArrayList<Long> orderedScreens) {
1413            final Runnable r = new Runnable() {
1414                @Override
1415                public void run() {
1416                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1417                    if (callbacks != null) {
1418                        callbacks.bindScreens(orderedScreens);
1419                    }
1420                }
1421            };
1422            runOnMainThread(r);
1423        }
1424
1425        private void bindWorkspaceItems(final Callbacks oldCallbacks,
1426                final ArrayList<ItemInfo> workspaceItems,
1427                final ArrayList<LauncherAppWidgetInfo> appWidgets,
1428                final Executor executor) {
1429
1430            // Bind the workspace items
1431            int N = workspaceItems.size();
1432            for (int i = 0; i < N; i += ITEMS_CHUNK) {
1433                final int start = i;
1434                final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
1435                final Runnable r = new Runnable() {
1436                    @Override
1437                    public void run() {
1438                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1439                        if (callbacks != null) {
1440                            callbacks.bindItems(workspaceItems, start, start+chunkSize,
1441                                    false);
1442                        }
1443                    }
1444                };
1445                executor.execute(r);
1446            }
1447
1448            // Bind the widgets, one at a time
1449            N = appWidgets.size();
1450            for (int i = 0; i < N; i++) {
1451                final LauncherAppWidgetInfo widget = appWidgets.get(i);
1452                final Runnable r = new Runnable() {
1453                    public void run() {
1454                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1455                        if (callbacks != null) {
1456                            callbacks.bindAppWidget(widget);
1457                        }
1458                    }
1459                };
1460                executor.execute(r);
1461            }
1462        }
1463
1464        /**
1465         * Binds all loaded data to actual views on the main thread.
1466         */
1467        private void bindWorkspace(int synchronizeBindPage) {
1468            final long t = SystemClock.uptimeMillis();
1469            Runnable r;
1470
1471            // Don't use these two variables in any of the callback runnables.
1472            // Otherwise we hold a reference to them.
1473            final Callbacks oldCallbacks = mCallbacks.get();
1474            if (oldCallbacks == null) {
1475                // This launcher has exited and nobody bothered to tell us.  Just bail.
1476                Log.w(TAG, "LoaderTask running with no launcher");
1477                return;
1478            }
1479
1480            // Save a copy of all the bg-thread collections
1481            ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
1482            ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
1483            ArrayList<Long> orderedScreenIds = new ArrayList<>();
1484
1485            synchronized (sBgDataModel) {
1486                workspaceItems.addAll(sBgDataModel.workspaceItems);
1487                appWidgets.addAll(sBgDataModel.appWidgets);
1488                orderedScreenIds.addAll(sBgDataModel.workspaceScreens);
1489            }
1490
1491            final int currentScreen;
1492            {
1493                int currScreen = synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE
1494                        ? synchronizeBindPage : oldCallbacks.getCurrentWorkspaceScreen();
1495                if (currScreen >= orderedScreenIds.size()) {
1496                    // There may be no workspace screens (just hotseat items and an empty page).
1497                    currScreen = PagedView.INVALID_RESTORE_PAGE;
1498                }
1499                currentScreen = currScreen;
1500            }
1501            final boolean validFirstPage = currentScreen >= 0;
1502            final long currentScreenId =
1503                    validFirstPage ? orderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID;
1504
1505            // Separate the items that are on the current screen, and all the other remaining items
1506            ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
1507            ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
1508            ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
1509            ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
1510
1511            filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
1512                    otherWorkspaceItems);
1513            filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
1514                    otherAppWidgets);
1515            sortWorkspaceItemsSpatially(currentWorkspaceItems);
1516            sortWorkspaceItemsSpatially(otherWorkspaceItems);
1517
1518            // Tell the workspace that we're about to start binding items
1519            r = new Runnable() {
1520                public void run() {
1521                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1522                    if (callbacks != null) {
1523                        callbacks.clearPendingBinds();
1524                        callbacks.startBinding();
1525                    }
1526                }
1527            };
1528            runOnMainThread(r);
1529
1530            bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
1531
1532            Executor mainExecutor = new DeferredMainThreadExecutor();
1533            // Load items on the current page.
1534            bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, mainExecutor);
1535
1536            // In case of validFirstPage, only bind the first screen, and defer binding the
1537            // remaining screens after first onDraw (and an optional the fade animation whichever
1538            // happens later).
1539            // This ensures that the first screen is immediately visible (eg. during rotation)
1540            // In case of !validFirstPage, bind all pages one after other.
1541            final Executor deferredExecutor =
1542                    validFirstPage ? new ViewOnDrawExecutor(mHandler) : mainExecutor;
1543
1544            mainExecutor.execute(new Runnable() {
1545                @Override
1546                public void run() {
1547                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1548                    if (callbacks != null) {
1549                        callbacks.finishFirstPageBind(
1550                                validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null);
1551                    }
1552                }
1553            });
1554
1555            bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, deferredExecutor);
1556
1557            // Tell the workspace that we're done binding items
1558            r = new Runnable() {
1559                public void run() {
1560                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1561                    if (callbacks != null) {
1562                        callbacks.finishBindingItems();
1563                    }
1564
1565                    mIsLoadingAndBindingWorkspace = false;
1566
1567                    // Run all the bind complete runnables after workspace is bound.
1568                    if (!mBindCompleteRunnables.isEmpty()) {
1569                        synchronized (mBindCompleteRunnables) {
1570                            for (final Runnable r : mBindCompleteRunnables) {
1571                                runOnWorkerThread(r);
1572                            }
1573                            mBindCompleteRunnables.clear();
1574                        }
1575                    }
1576
1577                    // If we're profiling, ensure this is the last thing in the queue.
1578                    if (DEBUG_LOADERS) {
1579                        Log.d(TAG, "bound workspace in "
1580                            + (SystemClock.uptimeMillis()-t) + "ms");
1581                    }
1582
1583                }
1584            };
1585            deferredExecutor.execute(r);
1586
1587            if (validFirstPage) {
1588                r = new Runnable() {
1589                    public void run() {
1590                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1591                        if (callbacks != null) {
1592                            // We are loading synchronously, which means, some of the pages will be
1593                            // bound after first draw. Inform the callbacks that page binding is
1594                            // not complete, and schedule the remaining pages.
1595                            if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
1596                                callbacks.onPageBoundSynchronously(currentScreen);
1597                            }
1598                            callbacks.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor);
1599                        }
1600                    }
1601                };
1602                runOnMainThread(r);
1603            }
1604        }
1605
1606        private void updateIconCache() {
1607            // Ignore packages which have a promise icon.
1608            HashSet<String> packagesToIgnore = new HashSet<>();
1609            synchronized (sBgDataModel) {
1610                for (ItemInfo info : sBgDataModel.itemsIdMap) {
1611                    if (info instanceof ShortcutInfo) {
1612                        ShortcutInfo si = (ShortcutInfo) info;
1613                        if (si.isPromise() && si.getTargetComponent() != null) {
1614                            packagesToIgnore.add(si.getTargetComponent().getPackageName());
1615                        }
1616                    } else if (info instanceof LauncherAppWidgetInfo) {
1617                        LauncherAppWidgetInfo lawi = (LauncherAppWidgetInfo) info;
1618                        if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
1619                            packagesToIgnore.add(lawi.providerName.getPackageName());
1620                        }
1621                    }
1622                }
1623            }
1624            mIconCache.updateDbIcons(packagesToIgnore);
1625        }
1626
1627        private void onlyBindAllApps() {
1628            final Callbacks oldCallbacks = mCallbacks.get();
1629            if (oldCallbacks == null) {
1630                // This launcher has exited and nobody bothered to tell us.  Just bail.
1631                Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
1632                return;
1633            }
1634
1635            // shallow copy
1636            @SuppressWarnings("unchecked")
1637            final ArrayList<AppInfo> list
1638                    = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
1639            Runnable r = new Runnable() {
1640                public void run() {
1641                    final long t = SystemClock.uptimeMillis();
1642                    final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1643                    if (callbacks != null) {
1644                        callbacks.bindAllApplications(list);
1645                    }
1646                    if (DEBUG_LOADERS) {
1647                        Log.d(TAG, "bound all " + list.size() + " apps from cache in "
1648                                + (SystemClock.uptimeMillis() - t) + "ms");
1649                    }
1650                }
1651            };
1652            runOnMainThread(r);
1653        }
1654
1655        private void loadAllApps() {
1656            final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1657
1658            final Callbacks oldCallbacks = mCallbacks.get();
1659            if (oldCallbacks == null) {
1660                // This launcher has exited and nobody bothered to tell us.  Just bail.
1661                Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
1662                return;
1663            }
1664
1665            final List<UserHandle> profiles = mUserManager.getUserProfiles();
1666
1667            // Clear the list of apps
1668            mBgAllAppsList.clear();
1669            for (UserHandle user : profiles) {
1670                // Query for the set of apps
1671                final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1672                final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user);
1673                if (DEBUG_LOADERS) {
1674                    Log.d(TAG, "getActivityList took "
1675                            + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
1676                    Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
1677                }
1678                // Fail if we don't have any apps
1679                // TODO: Fix this. Only fail for the current user.
1680                if (apps == null || apps.isEmpty()) {
1681                    return;
1682                }
1683                boolean quietMode = mUserManager.isQuietModeEnabled(user);
1684                // Create the ApplicationInfos
1685                for (int i = 0; i < apps.size(); i++) {
1686                    LauncherActivityInfo app = apps.get(i);
1687                    // This builds the icon bitmaps.
1688                    mBgAllAppsList.add(new AppInfo(mContext, app, user, quietMode), app);
1689                }
1690
1691                final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
1692                if (heuristic != null) {
1693                    final Runnable r = new Runnable() {
1694
1695                        @Override
1696                        public void run() {
1697                            heuristic.processUserApps(apps);
1698                        }
1699                    };
1700                    runOnMainThread(new Runnable() {
1701
1702                        @Override
1703                        public void run() {
1704                            // Check isLoadingWorkspace on the UI thread, as it is updated on
1705                            // the UI thread.
1706                            if (mIsLoadingAndBindingWorkspace) {
1707                                synchronized (mBindCompleteRunnables) {
1708                                    mBindCompleteRunnables.add(r);
1709                                }
1710                            } else {
1711                                runOnWorkerThread(r);
1712                            }
1713                        }
1714                    });
1715                }
1716            }
1717            // Huh? Shouldn't this be inside the Runnable below?
1718            final ArrayList<AppInfo> added = mBgAllAppsList.added;
1719            mBgAllAppsList.added = new ArrayList<AppInfo>();
1720
1721            // Post callback on main thread
1722            mHandler.post(new Runnable() {
1723                public void run() {
1724
1725                    final long bindTime = SystemClock.uptimeMillis();
1726                    final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1727                    if (callbacks != null) {
1728                        callbacks.bindAllApplications(added);
1729                        if (DEBUG_LOADERS) {
1730                            Log.d(TAG, "bound " + added.size() + " apps in "
1731                                    + (SystemClock.uptimeMillis() - bindTime) + "ms");
1732                        }
1733                    } else {
1734                        Log.i(TAG, "not binding apps: no Launcher activity");
1735                    }
1736                }
1737            });
1738            // Cleanup any data stored for a deleted user.
1739            ManagedProfileHeuristic.processAllUsers(profiles, mContext);
1740            if (DEBUG_LOADERS) {
1741                Log.d(TAG, "Icons processed in "
1742                        + (SystemClock.uptimeMillis() - loadTime) + "ms");
1743            }
1744        }
1745
1746        private void loadDeepShortcuts() {
1747            if (!mModelLoaded) {
1748                sBgDataModel.deepShortcutMap.clear();
1749                DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(mContext);
1750                mHasShortcutHostPermission = shortcutManager.hasHostPermission();
1751                if (mHasShortcutHostPermission) {
1752                    for (UserHandle user : mUserManager.getUserProfiles()) {
1753                        if (mUserManager.isUserUnlocked(user)) {
1754                            List<ShortcutInfoCompat> shortcuts =
1755                                    shortcutManager.queryForAllShortcuts(user);
1756                            sBgDataModel.updateDeepShortcutMap(null, user, shortcuts);
1757                        }
1758                    }
1759                }
1760            }
1761        }
1762    }
1763
1764    public void bindDeepShortcuts() {
1765        final MultiHashMap<ComponentKey, String> shortcutMapCopy =
1766                sBgDataModel.deepShortcutMap.clone();
1767        Runnable r = new Runnable() {
1768            @Override
1769            public void run() {
1770                Callbacks callbacks = getCallback();
1771                if (callbacks != null) {
1772                    callbacks.bindDeepShortcutMap(shortcutMapCopy);
1773                }
1774            }
1775        };
1776        runOnMainThread(r);
1777    }
1778
1779    /**
1780     * Refreshes the cached shortcuts if the shortcut permission has changed.
1781     * Current implementation simply reloads the workspace, but it can be optimized to
1782     * use partial updates similar to {@link UserManagerCompat}
1783     */
1784    public void refreshShortcutsIfRequired() {
1785        if (Utilities.ATLEAST_NOUGAT_MR1) {
1786            sWorker.removeCallbacks(mShortcutPermissionCheckRunnable);
1787            sWorker.post(mShortcutPermissionCheckRunnable);
1788        }
1789    }
1790
1791    /**
1792     * Called when the icons for packages have been updated in the icon cache.
1793     */
1794    public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandle user) {
1795        // If any package icon has changed (app was updated while launcher was dead),
1796        // update the corresponding shortcuts.
1797        enqueueModelUpdateTask(new CacheDataUpdatedTask(
1798                CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages));
1799    }
1800
1801    void enqueueModelUpdateTask(BaseModelUpdateTask task) {
1802        if (!mModelLoaded && mLoaderTask == null) {
1803            if (DEBUG_LOADERS) {
1804                Log.d(TAG, "enqueueModelUpdateTask Ignoring task since loader is pending=" + task);
1805            }
1806            return;
1807        }
1808        task.init(this);
1809        runOnWorkerThread(task);
1810    }
1811
1812    /**
1813     * A task to be executed on the current callbacks on the UI thread.
1814     * If there is no current callbacks, the task is ignored.
1815     */
1816    public interface CallbackTask {
1817
1818        void execute(Callbacks callbacks);
1819    }
1820
1821    /**
1822     * A runnable which changes/updates the data model of the launcher based on certain events.
1823     */
1824    public static abstract class BaseModelUpdateTask implements Runnable {
1825
1826        private LauncherModel mModel;
1827        private DeferredHandler mUiHandler;
1828
1829        /* package private */
1830        void init(LauncherModel model) {
1831            mModel = model;
1832            mUiHandler = mModel.mHandler;
1833        }
1834
1835        @Override
1836        public void run() {
1837            if (!mModel.mHasLoaderCompletedOnce) {
1838                // Loader has not yet run.
1839                return;
1840            }
1841            execute(mModel.mApp, sBgDataModel, mModel.mBgAllAppsList);
1842        }
1843
1844        /**
1845         * Execute the actual task. Called on the worker thread.
1846         */
1847        public abstract void execute(
1848                LauncherAppState app, BgDataModel dataModel, AllAppsList apps);
1849
1850        /**
1851         * Schedules a {@param task} to be executed on the current callbacks.
1852         */
1853        public final void scheduleCallbackTask(final CallbackTask task) {
1854            final Callbacks callbacks = mModel.getCallback();
1855            mUiHandler.post(new Runnable() {
1856                public void run() {
1857                    Callbacks cb = mModel.getCallback();
1858                    if (callbacks == cb && cb != null) {
1859                        task.execute(callbacks);
1860                    }
1861                }
1862            });
1863        }
1864
1865        public ModelWriter getModelWriter() {
1866            // Updates from model task, do not deal with icon position in hotseat.
1867            return mModel.getWriter(false /* hasVerticalHotseat */);
1868        }
1869    }
1870
1871    public void updateAndBindShortcutInfo(final ShortcutInfo si, final ShortcutInfoCompat info) {
1872        updateAndBindShortcutInfo(new Provider<ShortcutInfo>() {
1873            @Override
1874            public ShortcutInfo get() {
1875                si.updateFromDeepShortcutInfo(info, mApp.getContext());
1876                si.iconBitmap = LauncherIcons.createShortcutIcon(info, mApp.getContext());
1877                return si;
1878            }
1879        });
1880    }
1881
1882    /**
1883     * Utility method to update a shortcut on the background thread.
1884     */
1885    public void updateAndBindShortcutInfo(final Provider<ShortcutInfo> shortcutProvider) {
1886        enqueueModelUpdateTask(new ExtendedModelTask() {
1887            @Override
1888            public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
1889                ShortcutInfo info = shortcutProvider.get();
1890                ArrayList<ShortcutInfo> update = new ArrayList<>();
1891                update.add(info);
1892                bindUpdatedShortcuts(update, info.user);
1893            }
1894        });
1895    }
1896
1897    private void bindWidgetsModel(final Callbacks callbacks) {
1898        final MultiHashMap<PackageItemInfo, WidgetItem> widgets
1899                = mBgWidgetsModel.getWidgetsMap().clone();
1900        mHandler.post(new Runnable() {
1901            @Override
1902            public void run() {
1903                Callbacks cb = getCallback();
1904                if (callbacks == cb && cb != null) {
1905                    callbacks.bindAllWidgets(widgets);
1906                }
1907            }
1908        });
1909    }
1910
1911    public void refreshAndBindWidgetsAndShortcuts(
1912            final Callbacks callbacks, final boolean bindFirst) {
1913        runOnWorkerThread(new Runnable() {
1914            @Override
1915            public void run() {
1916                if (bindFirst && !mBgWidgetsModel.isEmpty()) {
1917                    bindWidgetsModel(callbacks);
1918                }
1919                ArrayList<WidgetItem> allWidgets = mBgWidgetsModel.update(mApp.getContext());
1920                bindWidgetsModel(callbacks);
1921
1922                // update the Widget entries inside DB on the worker thread.
1923                mApp.getWidgetCache().removeObsoletePreviews(allWidgets);
1924            }
1925        });
1926    }
1927
1928    static boolean isValidProvider(AppWidgetProviderInfo provider) {
1929        return (provider != null) && (provider.provider != null)
1930                && (provider.provider.getPackageName() != null);
1931    }
1932
1933    public void dumpState(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
1934        if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
1935            writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size());
1936            for (AppInfo info : mBgAllAppsList.data) {
1937                writer.println(prefix + "   title=\"" + info.title + "\" iconBitmap=" + info.iconBitmap
1938                        + " componentName=" + info.componentName.getPackageName());
1939            }
1940        }
1941        sBgDataModel.dump(prefix, fd, writer, args);
1942    }
1943
1944    public Callbacks getCallback() {
1945        return mCallbacks != null ? mCallbacks.get() : null;
1946    }
1947
1948    /**
1949     * @return {@link FolderInfo} if its already loaded.
1950     */
1951    public FolderInfo findFolderById(Long folderId) {
1952        synchronized (sBgDataModel) {
1953            return sBgDataModel.folders.get(folderId);
1954        }
1955    }
1956
1957    @Thunk class DeferredMainThreadExecutor implements Executor {
1958
1959        @Override
1960        public void execute(Runnable command) {
1961            runOnMainThread(command);
1962        }
1963    }
1964
1965    /**
1966     * @return the looper for the worker thread which can be used to start background tasks.
1967     */
1968    public static Looper getWorkerLooper() {
1969        return sWorkerThread.getLooper();
1970    }
1971}
1972