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