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