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