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