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