LauncherModel.java revision d3b87ef1963fb96177ca85bcd6a25879e27e419c
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.Intent.ShortcutIconResource;
28import android.content.IntentFilter;
29import android.content.pm.PackageManager;
30import android.content.pm.ResolveInfo;
31import android.database.Cursor;
32import android.graphics.Bitmap;
33import android.net.Uri;
34import android.os.Handler;
35import android.os.HandlerThread;
36import android.os.Looper;
37import android.os.Parcelable;
38import android.os.Process;
39import android.os.SystemClock;
40import android.os.Trace;
41import android.provider.BaseColumns;
42import android.text.TextUtils;
43import android.util.Log;
44import android.util.LongSparseArray;
45import android.util.MutableInt;
46import android.util.Pair;
47
48import com.android.launcher3.compat.AppWidgetManagerCompat;
49import com.android.launcher3.compat.LauncherActivityInfoCompat;
50import com.android.launcher3.compat.LauncherAppsCompat;
51import com.android.launcher3.compat.PackageInstallerCompat;
52import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
53import com.android.launcher3.compat.UserHandleCompat;
54import com.android.launcher3.compat.UserManagerCompat;
55import com.android.launcher3.config.FeatureFlags;
56import com.android.launcher3.config.ProviderConfig;
57import com.android.launcher3.dynamicui.ExtractionUtils;
58import com.android.launcher3.folder.Folder;
59import com.android.launcher3.folder.FolderIcon;
60import com.android.launcher3.logging.FileLog;
61import com.android.launcher3.model.GridSizeMigrationTask;
62import com.android.launcher3.model.WidgetsModel;
63import com.android.launcher3.provider.ImportDataTask;
64import com.android.launcher3.provider.LauncherDbUtils;
65import com.android.launcher3.shortcuts.DeepShortcutManager;
66import com.android.launcher3.shortcuts.ShortcutInfoCompat;
67import com.android.launcher3.shortcuts.ShortcutKey;
68import com.android.launcher3.util.ComponentKey;
69import com.android.launcher3.util.CursorIconInfo;
70import com.android.launcher3.util.FlagOp;
71import com.android.launcher3.util.GridOccupancy;
72import com.android.launcher3.util.LongArrayMap;
73import com.android.launcher3.util.ManagedProfileHeuristic;
74import com.android.launcher3.util.MultiHashMap;
75import com.android.launcher3.util.PackageManagerHelper;
76import com.android.launcher3.util.Preconditions;
77import com.android.launcher3.util.StringFilter;
78import com.android.launcher3.util.Thunk;
79import com.android.launcher3.util.ViewOnDrawExecutor;
80
81import java.lang.ref.WeakReference;
82import java.net.URISyntaxException;
83import java.security.InvalidParameterException;
84import java.util.ArrayList;
85import java.util.Arrays;
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.Map.Entry;
94import java.util.Set;
95import java.util.concurrent.Executor;
96
97/**
98 * Maintains in-memory state of the Launcher. It is expected that there should be only one
99 * LauncherModel object held in a static. Also provide APIs for updating the database state
100 * for the Launcher.
101 */
102public class LauncherModel extends BroadcastReceiver
103        implements LauncherAppsCompat.OnAppsChangedCallbackCompat {
104    static final boolean DEBUG_LOADERS = false;
105    private static final boolean DEBUG_RECEIVER = false;
106    private static final boolean REMOVE_UNRESTORED_ICONS = true;
107
108    static final String TAG = "Launcher.Model";
109
110    private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
111    private static final long INVALID_SCREEN_ID = -1L;
112
113    @Thunk final LauncherAppState mApp;
114    @Thunk final Object mLock = new Object();
115    @Thunk DeferredHandler mHandler = new DeferredHandler();
116    @Thunk LoaderTask mLoaderTask;
117    @Thunk boolean mIsLoaderTaskRunning;
118    @Thunk boolean mHasLoaderCompletedOnce;
119
120    @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
121    static {
122        sWorkerThread.start();
123    }
124    @Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());
125
126    // We start off with everything not loaded.  After that, we assume that
127    // our monitoring of the package manager provides all updates and we never
128    // need to do a requery.  These are only ever touched from the loader thread.
129    private boolean mWorkspaceLoaded;
130    private boolean mAllAppsLoaded;
131    private boolean mDeepShortcutsLoaded;
132
133    /**
134     * Set of runnables to be called on the background thread after the workspace binding
135     * is complete.
136     */
137    static final ArrayList<Runnable> mBindCompleteRunnables = new ArrayList<Runnable>();
138
139    @Thunk WeakReference<Callbacks> mCallbacks;
140
141    // < only access in worker thread >
142    private final AllAppsList mBgAllAppsList;
143    // Entire list of widgets.
144    private final WidgetsModel mBgWidgetsModel;
145
146    // Maps all launcher activities to the id's of their shortcuts (if they have any).
147    private final MultiHashMap<ComponentKey, String> mBgDeepShortcutMap = new MultiHashMap<>();
148
149    // The lock that must be acquired before referencing any static bg data structures.  Unlike
150    // other locks, this one can generally be held long-term because we never expect any of these
151    // static data structures to be referenced outside of the worker thread except on the first
152    // load after configuration change.
153    static final Object sBgLock = new Object();
154
155    // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
156    // LauncherModel to their ids
157    static final LongArrayMap<ItemInfo> sBgItemsIdMap = new LongArrayMap<>();
158
159    // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
160    //       created by LauncherModel that are directly on the home screen (however, no widgets or
161    //       shortcuts within folders).
162    static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
163
164    // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
165    static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
166        new ArrayList<LauncherAppWidgetInfo>();
167
168    // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
169    static final LongArrayMap<FolderInfo> sBgFolders = new LongArrayMap<>();
170
171    // sBgWorkspaceScreens is the ordered set of workspace screens.
172    static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
173
174    // sBgPinnedShortcutCounts is the ComponentKey representing a pinned shortcut to the number of
175    // times it is pinned.
176    static final Map<ShortcutKey, MutableInt> sBgPinnedShortcutCounts = new HashMap<>();
177
178    // sPendingPackages is a set of packages which could be on sdcard and are not available yet
179    static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages =
180            new HashMap<UserHandleCompat, HashSet<String>>();
181
182    // </ only access in worker thread >
183
184    private IconCache mIconCache;
185    private DeepShortcutManager mDeepShortcutManager;
186
187    private final LauncherAppsCompat mLauncherApps;
188    private final UserManagerCompat mUserManager;
189
190    public interface Callbacks {
191        public boolean setLoadOnResume();
192        public int getCurrentWorkspaceScreen();
193        public void clearPendingBinds();
194        public void startBinding();
195        public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
196                              boolean forceAnimateIcons);
197        public void bindScreens(ArrayList<Long> orderedScreenIds);
198        public void finishFirstPageBind(ViewOnDrawExecutor executor);
199        public void finishBindingItems();
200        public void bindAppWidget(LauncherAppWidgetInfo info);
201        public void bindAllApplications(ArrayList<AppInfo> apps);
202        public void bindAppsAdded(ArrayList<Long> newScreens,
203                                  ArrayList<ItemInfo> addNotAnimated,
204                                  ArrayList<ItemInfo> addAnimated,
205                                  ArrayList<AppInfo> addedApps);
206        public void bindAppsUpdated(ArrayList<AppInfo> apps);
207        public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,
208                ArrayList<ShortcutInfo> removed, UserHandleCompat user);
209        public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
210        public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
211        public void bindWorkspaceComponentsRemoved(
212                HashSet<String> packageNames, HashSet<ComponentName> components,
213                UserHandleCompat user);
214        public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos);
215        public void notifyWidgetProvidersChanged();
216        public void bindWidgetsModel(WidgetsModel model);
217        public void onPageBoundSynchronously(int page);
218        public void executeOnNextDraw(ViewOnDrawExecutor executor);
219        public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap);
220    }
221
222    public interface ItemInfoFilter {
223        public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
224    }
225
226    LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter,
227            DeepShortcutManager deepShortcutManager) {
228        Context context = app.getContext();
229        mApp = app;
230        mBgAllAppsList = new AllAppsList(iconCache, appFilter);
231        mBgWidgetsModel = new WidgetsModel(context, iconCache, appFilter);
232        mIconCache = iconCache;
233        mDeepShortcutManager = deepShortcutManager;
234
235        mLauncherApps = LauncherAppsCompat.getInstance(context);
236        mUserManager = UserManagerCompat.getInstance(context);
237    }
238
239    /** Runs the specified runnable immediately if called from the main thread, otherwise it is
240     * posted on the main thread handler. */
241    private void runOnMainThread(Runnable r) {
242        if (sWorkerThread.getThreadId() == Process.myTid()) {
243            // If we are on the worker thread, post onto the main handler
244            mHandler.post(r);
245        } else {
246            r.run();
247        }
248    }
249
250    /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
251     * posted on the worker thread handler. */
252    private static void runOnWorkerThread(Runnable r) {
253        if (sWorkerThread.getThreadId() == Process.myTid()) {
254            r.run();
255        } else {
256            // If we are not on the worker thread, then post to the worker handler
257            sWorker.post(r);
258        }
259    }
260
261    public void setPackageState(final PackageInstallInfo installInfo) {
262        Runnable updateRunnable = new Runnable() {
263
264            @Override
265            public void run() {
266                synchronized (sBgLock) {
267                    final HashSet<ItemInfo> updates = new HashSet<>();
268
269                    if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
270                        // Ignore install success events as they are handled by Package add events.
271                        return;
272                    }
273
274                    for (ItemInfo info : sBgItemsIdMap) {
275                        if (info instanceof ShortcutInfo) {
276                            ShortcutInfo si = (ShortcutInfo) info;
277                            ComponentName cn = si.getTargetComponent();
278                            if (si.isPromise() && (cn != null)
279                                    && installInfo.packageName.equals(cn.getPackageName())) {
280                                si.setInstallProgress(installInfo.progress);
281
282                                if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
283                                    // Mark this info as broken.
284                                    si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
285                                }
286                                updates.add(si);
287                            }
288                        }
289                    }
290
291                    for (LauncherAppWidgetInfo widget : sBgAppWidgets) {
292                        if (widget.providerName.getPackageName().equals(installInfo.packageName)) {
293                            widget.installProgress = installInfo.progress;
294                            updates.add(widget);
295                        }
296                    }
297
298                    if (!updates.isEmpty()) {
299                        // Push changes to the callback.
300                        Runnable r = new Runnable() {
301                            public void run() {
302                                Callbacks callbacks = getCallback();
303                                if (callbacks != null) {
304                                    callbacks.bindRestoreItemsChange(updates);
305                                }
306                            }
307                        };
308                        mHandler.post(r);
309                    }
310                }
311            }
312        };
313        runOnWorkerThread(updateRunnable);
314    }
315
316    /**
317     * Updates the icons and label of all pending icons for the provided package name.
318     */
319    public void updateSessionDisplayInfo(final String packageName) {
320        Runnable updateRunnable = new Runnable() {
321
322            @Override
323            public void run() {
324                synchronized (sBgLock) {
325                    ArrayList<ShortcutInfo> updates = new ArrayList<>();
326                    UserHandleCompat user = UserHandleCompat.myUserHandle();
327
328                    for (ItemInfo info : sBgItemsIdMap) {
329                        if (info instanceof ShortcutInfo) {
330                            ShortcutInfo si = (ShortcutInfo) info;
331                            ComponentName cn = si.getTargetComponent();
332                            if (si.isPromise() && (cn != null)
333                                    && packageName.equals(cn.getPackageName())) {
334                                if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
335                                    // For auto install apps update the icon as well as label.
336                                    mIconCache.getTitleAndIcon(si,
337                                            si.promisedIntent, user,
338                                            si.shouldUseLowResIcon());
339                                } else {
340                                    // Only update the icon for restored apps.
341                                    si.updateIcon(mIconCache);
342                                }
343                                updates.add(si);
344                            }
345                        }
346                    }
347
348                    bindUpdatedShortcuts(updates, user);
349                }
350            }
351        };
352        runOnWorkerThread(updateRunnable);
353    }
354
355    public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
356        final Callbacks callbacks = getCallback();
357
358        if (allAppsApps == null) {
359            throw new RuntimeException("allAppsApps must not be null");
360        }
361        if (allAppsApps.isEmpty()) {
362            return;
363        }
364
365        // Process the newly added applications and add them to the database first
366        Runnable r = new Runnable() {
367            public void run() {
368                runOnMainThread(new Runnable() {
369                    public void run() {
370                        Callbacks cb = getCallback();
371                        if (callbacks == cb && cb != null) {
372                            callbacks.bindAppsAdded(null, null, null, allAppsApps);
373                        }
374                    }
375                });
376            }
377        };
378        runOnWorkerThread(r);
379    }
380
381    private static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> occupiedPos,
382            int[] xy, int spanX, int spanY) {
383        LauncherAppState app = LauncherAppState.getInstance();
384        InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
385
386        GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows);
387        if (occupiedPos != null) {
388            for (ItemInfo r : occupiedPos) {
389                occupied.markCells(r, true);
390            }
391        }
392        return occupied.findVacantCell(xy, spanX, spanY);
393    }
394
395    /**
396     * Find a position on the screen for the given size or adds a new screen.
397     * @return screenId and the coordinates for the item.
398     */
399    @Thunk Pair<Long, int[]> findSpaceForItem(
400            Context context,
401            ArrayList<Long> workspaceScreens,
402            ArrayList<Long> addedWorkspaceScreensFinal,
403            int spanX, int spanY) {
404        LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
405
406        // Use sBgItemsIdMap as all the items are already loaded.
407        assertWorkspaceLoaded();
408        synchronized (sBgLock) {
409            for (ItemInfo info : sBgItemsIdMap) {
410                if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
411                    ArrayList<ItemInfo> items = screenItems.get(info.screenId);
412                    if (items == null) {
413                        items = new ArrayList<>();
414                        screenItems.put(info.screenId, items);
415                    }
416                    items.add(info);
417                }
418            }
419        }
420
421        // Find appropriate space for the item.
422        long screenId = 0;
423        int[] cordinates = new int[2];
424        boolean found = false;
425
426        int screenCount = workspaceScreens.size();
427        // First check the preferred screen.
428        int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
429        if (preferredScreenIndex < screenCount) {
430            screenId = workspaceScreens.get(preferredScreenIndex);
431            found = findNextAvailableIconSpaceInScreen(
432                    screenItems.get(screenId), cordinates, spanX, spanY);
433        }
434
435        if (!found) {
436            // Search on any of the screens starting from the first screen.
437            for (int screen = 1; screen < screenCount; screen++) {
438                screenId = workspaceScreens.get(screen);
439                if (findNextAvailableIconSpaceInScreen(
440                        screenItems.get(screenId), cordinates, spanX, spanY)) {
441                    // We found a space for it
442                    found = true;
443                    break;
444                }
445            }
446        }
447
448        if (!found) {
449            // Still no position found. Add a new screen to the end.
450            screenId = LauncherSettings.Settings.call(context.getContentResolver(),
451                    LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
452                    .getLong(LauncherSettings.Settings.EXTRA_VALUE);
453
454            // Save the screen id for binding in the workspace
455            workspaceScreens.add(screenId);
456            addedWorkspaceScreensFinal.add(screenId);
457
458            // If we still can't find an empty space, then God help us all!!!
459            if (!findNextAvailableIconSpaceInScreen(
460                    screenItems.get(screenId), cordinates, spanX, spanY)) {
461                throw new RuntimeException("Can't find space to add the item");
462            }
463        }
464        return Pair.create(screenId, cordinates);
465    }
466
467    /**
468     * Adds the provided items to the workspace.
469     */
470    public void addAndBindAddedWorkspaceItems(final Context context,
471            final ArrayList<? extends ItemInfo> workspaceApps) {
472        final Callbacks callbacks = getCallback();
473        if (workspaceApps.isEmpty()) {
474            return;
475        }
476        // Process the newly added applications and add them to the database first
477        Runnable r = new Runnable() {
478            public void run() {
479                final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
480                final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
481
482                // Get the list of workspace screens.  We need to append to this list and
483                // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
484                // called.
485                ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
486                synchronized(sBgLock) {
487                    for (ItemInfo item : workspaceApps) {
488                        if (item instanceof ShortcutInfo) {
489                            // Short-circuit this logic if the icon exists somewhere on the workspace
490                            if (shortcutExists(context, item.getIntent(), item.user)) {
491                                continue;
492                            }
493                        }
494
495                        // Find appropriate space for the item.
496                        Pair<Long, int[]> coords = findSpaceForItem(context,
497                                workspaceScreens, addedWorkspaceScreensFinal, 1, 1);
498                        long screenId = coords.first;
499                        int[] cordinates = coords.second;
500
501                        ItemInfo itemInfo;
502                        if (item instanceof ShortcutInfo || item instanceof FolderInfo) {
503                            itemInfo = item;
504                        } else if (item instanceof AppInfo) {
505                            itemInfo = ((AppInfo) item).makeShortcut();
506                        } else {
507                            throw new RuntimeException("Unexpected info type");
508                        }
509
510                        // Add the shortcut to the db
511                        addItemToDatabase(context, itemInfo,
512                                LauncherSettings.Favorites.CONTAINER_DESKTOP,
513                                screenId, cordinates[0], cordinates[1]);
514                        // Save the ShortcutInfo for binding in the workspace
515                        addedShortcutsFinal.add(itemInfo);
516                    }
517                }
518
519                // Update the workspace screens
520                updateWorkspaceScreenOrder(context, workspaceScreens);
521
522                if (!addedShortcutsFinal.isEmpty()) {
523                    runOnMainThread(new Runnable() {
524                        public void run() {
525                            Callbacks cb = getCallback();
526                            if (callbacks == cb && cb != null) {
527                                final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
528                                final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
529                                if (!addedShortcutsFinal.isEmpty()) {
530                                    ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 1);
531                                    long lastScreenId = info.screenId;
532                                    for (ItemInfo i : addedShortcutsFinal) {
533                                        if (i.screenId == lastScreenId) {
534                                            addAnimated.add(i);
535                                        } else {
536                                            addNotAnimated.add(i);
537                                        }
538                                    }
539                                }
540                                callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
541                                        addNotAnimated, addAnimated, null);
542                            }
543                        }
544                    });
545                }
546            }
547        };
548        runOnWorkerThread(r);
549    }
550
551    /**
552     * Adds an item to the DB if it was not created previously, or move it to a new
553     * <container, screen, cellX, cellY>
554     */
555    public static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
556            long screenId, int cellX, int cellY) {
557        if (item.container == ItemInfo.NO_ID) {
558            // From all apps
559            addItemToDatabase(context, item, container, screenId, cellX, cellY);
560        } else {
561            // From somewhere else
562            moveItemInDatabase(context, item, container, screenId, cellX, cellY);
563        }
564    }
565
566    static void checkItemInfoLocked(
567            final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
568        ItemInfo modelItem = sBgItemsIdMap.get(itemId);
569        if (modelItem != null && item != modelItem) {
570            // check all the data is consistent
571            if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
572                ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
573                ShortcutInfo shortcut = (ShortcutInfo) item;
574                if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
575                        modelShortcut.intent.filterEquals(shortcut.intent) &&
576                        modelShortcut.id == shortcut.id &&
577                        modelShortcut.itemType == shortcut.itemType &&
578                        modelShortcut.container == shortcut.container &&
579                        modelShortcut.screenId == shortcut.screenId &&
580                        modelShortcut.cellX == shortcut.cellX &&
581                        modelShortcut.cellY == shortcut.cellY &&
582                        modelShortcut.spanX == shortcut.spanX &&
583                        modelShortcut.spanY == shortcut.spanY) {
584                    // For all intents and purposes, this is the same object
585                    return;
586                }
587            }
588
589            // the modelItem needs to match up perfectly with item if our model is
590            // to be consistent with the database-- for now, just require
591            // modelItem == item or the equality check above
592            String msg = "item: " + ((item != null) ? item.toString() : "null") +
593                    "modelItem: " +
594                    ((modelItem != null) ? modelItem.toString() : "null") +
595                    "Error: ItemInfo passed to checkItemInfo doesn't match original";
596            RuntimeException e = new RuntimeException(msg);
597            if (stackTrace != null) {
598                e.setStackTrace(stackTrace);
599            }
600            throw e;
601        }
602    }
603
604    static void checkItemInfo(final ItemInfo item) {
605        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
606        final long itemId = item.id;
607        Runnable r = new Runnable() {
608            public void run() {
609                synchronized (sBgLock) {
610                    checkItemInfoLocked(itemId, item, stackTrace);
611                }
612            }
613        };
614        runOnWorkerThread(r);
615    }
616
617    static void updateItemInDatabaseHelper(Context context, final ContentValues values,
618            final ItemInfo item, final String callingFunction) {
619        final long itemId = item.id;
620        final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
621        final ContentResolver cr = context.getContentResolver();
622
623        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
624        Runnable r = new Runnable() {
625            public void run() {
626                cr.update(uri, values, null, null);
627                updateItemArrays(item, itemId, stackTrace);
628            }
629        };
630        runOnWorkerThread(r);
631    }
632
633    static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList,
634            final ArrayList<ItemInfo> items, final String callingFunction) {
635        final ContentResolver cr = context.getContentResolver();
636
637        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
638        Runnable r = new Runnable() {
639            public void run() {
640                ArrayList<ContentProviderOperation> ops =
641                        new ArrayList<ContentProviderOperation>();
642                int count = items.size();
643                for (int i = 0; i < count; i++) {
644                    ItemInfo item = items.get(i);
645                    final long itemId = item.id;
646                    final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
647                    ContentValues values = valuesList.get(i);
648
649                    ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
650                    updateItemArrays(item, itemId, stackTrace);
651
652                }
653                try {
654                    cr.applyBatch(LauncherProvider.AUTHORITY, ops);
655                } catch (Exception e) {
656                    e.printStackTrace();
657                }
658            }
659        };
660        runOnWorkerThread(r);
661    }
662
663    static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
664        // Lock on mBgLock *after* the db operation
665        synchronized (sBgLock) {
666            checkItemInfoLocked(itemId, item, stackTrace);
667
668            if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
669                    item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
670                // Item is in a folder, make sure this folder exists
671                if (!sBgFolders.containsKey(item.container)) {
672                    // An items container is being set to a that of an item which is not in
673                    // the list of Folders.
674                    String msg = "item: " + item + " container being set to: " +
675                            item.container + ", not in the list of folders";
676                    Log.e(TAG, msg);
677                }
678            }
679
680            // Items are added/removed from the corresponding FolderInfo elsewhere, such
681            // as in Workspace.onDrop. Here, we just add/remove them from the list of items
682            // that are on the desktop, as appropriate
683            ItemInfo modelItem = sBgItemsIdMap.get(itemId);
684            if (modelItem != null &&
685                    (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
686                     modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
687                switch (modelItem.itemType) {
688                    case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
689                    case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
690                    case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
691                    case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
692                        if (!sBgWorkspaceItems.contains(modelItem)) {
693                            sBgWorkspaceItems.add(modelItem);
694                        }
695                        break;
696                    default:
697                        break;
698                }
699            } else {
700                sBgWorkspaceItems.remove(modelItem);
701            }
702        }
703    }
704
705    /**
706     * Move an item in the DB to a new <container, screen, cellX, cellY>
707     */
708    public static void moveItemInDatabase(Context context, final ItemInfo item, final long container,
709            final long screenId, final int cellX, final int cellY) {
710        item.container = container;
711        item.cellX = cellX;
712        item.cellY = cellY;
713
714        // We store hotseat items in canonical form which is this orientation invariant position
715        // in the hotseat
716        if (context instanceof Launcher && screenId < 0 &&
717                container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
718            item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
719        } else {
720            item.screenId = screenId;
721        }
722
723        final ContentValues values = new ContentValues();
724        values.put(LauncherSettings.Favorites.CONTAINER, item.container);
725        values.put(LauncherSettings.Favorites.CELLX, item.cellX);
726        values.put(LauncherSettings.Favorites.CELLY, item.cellY);
727        values.put(LauncherSettings.Favorites.RANK, item.rank);
728        values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
729
730        updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
731    }
732
733    /**
734     * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
735     * cellX, cellY have already been updated on the ItemInfos.
736     */
737    public static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items,
738            final long container, final int screen) {
739
740        ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>();
741        int count = items.size();
742
743        for (int i = 0; i < count; i++) {
744            ItemInfo item = items.get(i);
745            item.container = container;
746
747            // We store hotseat items in canonical form which is this orientation invariant position
748            // in the hotseat
749            if (context instanceof Launcher && screen < 0 &&
750                    container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
751                item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(item.cellX,
752                        item.cellY);
753            } else {
754                item.screenId = screen;
755            }
756
757            final ContentValues values = new ContentValues();
758            values.put(LauncherSettings.Favorites.CONTAINER, item.container);
759            values.put(LauncherSettings.Favorites.CELLX, item.cellX);
760            values.put(LauncherSettings.Favorites.CELLY, item.cellY);
761            values.put(LauncherSettings.Favorites.RANK, item.rank);
762            values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
763
764            contentValues.add(values);
765        }
766        updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase");
767    }
768
769    /**
770     * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
771     */
772    static void modifyItemInDatabase(Context context, final ItemInfo item, final long container,
773            final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) {
774        item.container = container;
775        item.cellX = cellX;
776        item.cellY = cellY;
777        item.spanX = spanX;
778        item.spanY = spanY;
779
780        // We store hotseat items in canonical form which is this orientation invariant position
781        // in the hotseat
782        if (context instanceof Launcher && screenId < 0 &&
783                container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
784            item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
785        } else {
786            item.screenId = screenId;
787        }
788
789        final ContentValues values = new ContentValues();
790        values.put(LauncherSettings.Favorites.CONTAINER, item.container);
791        values.put(LauncherSettings.Favorites.CELLX, item.cellX);
792        values.put(LauncherSettings.Favorites.CELLY, item.cellY);
793        values.put(LauncherSettings.Favorites.RANK, item.rank);
794        values.put(LauncherSettings.Favorites.SPANX, item.spanX);
795        values.put(LauncherSettings.Favorites.SPANY, item.spanY);
796        values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
797
798        updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
799    }
800
801    /**
802     * Update an item to the database in a specified container.
803     */
804    public static void updateItemInDatabase(Context context, final ItemInfo item) {
805        final ContentValues values = new ContentValues();
806        item.onAddToDatabase(context, values);
807        updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
808    }
809
810    private void assertWorkspaceLoaded() {
811        if (ProviderConfig.IS_DOGFOOD_BUILD) {
812            synchronized (mLock) {
813                if (!mHasLoaderCompletedOnce ||
814                        (mLoaderTask != null && mLoaderTask.mIsLoadingAndBindingWorkspace)) {
815                    throw new RuntimeException("Trying to add shortcut while loader is running");
816                }
817            }
818        }
819    }
820
821    /**
822     * Returns true if the shortcuts already exists on the workspace. This must be called after
823     * the workspace has been loaded. We identify a shortcut by its intent.
824     */
825    @Thunk boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) {
826        assertWorkspaceLoaded();
827        final String intentWithPkg, intentWithoutPkg;
828        if (intent.getComponent() != null) {
829            // If component is not null, an intent with null package will produce
830            // the same result and should also be a match.
831            String packageName = intent.getComponent().getPackageName();
832            if (intent.getPackage() != null) {
833                intentWithPkg = intent.toUri(0);
834                intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0);
835            } else {
836                intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0);
837                intentWithoutPkg = intent.toUri(0);
838            }
839        } else {
840            intentWithPkg = intent.toUri(0);
841            intentWithoutPkg = intent.toUri(0);
842        }
843
844        synchronized (sBgLock) {
845            for (ItemInfo item : sBgItemsIdMap) {
846                if (item instanceof ShortcutInfo) {
847                    ShortcutInfo info = (ShortcutInfo) item;
848                    Intent targetIntent = info.promisedIntent == null
849                            ? info.intent : info.promisedIntent;
850                    if (targetIntent != null && info.user.equals(user)) {
851                        String s = targetIntent.toUri(0);
852                        if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
853                            return true;
854                        }
855                    }
856                }
857            }
858        }
859        return false;
860    }
861
862    /**
863     * Add an item to the database in a specified container. Sets the container, screen, cellX and
864     * cellY fields of the item. Also assigns an ID to the item.
865     */
866    public static void addItemToDatabase(Context context, final ItemInfo item, final long container,
867            final long screenId, final int cellX, final int cellY) {
868        item.container = container;
869        item.cellX = cellX;
870        item.cellY = cellY;
871        // We store hotseat items in canonical form which is this orientation invariant position
872        // in the hotseat
873        if (context instanceof Launcher && screenId < 0 &&
874                container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
875            item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
876        } else {
877            item.screenId = screenId;
878        }
879
880        final ContentValues values = new ContentValues();
881        final ContentResolver cr = context.getContentResolver();
882        item.onAddToDatabase(context, values);
883
884        item.id = LauncherSettings.Settings.call(cr, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
885                .getLong(LauncherSettings.Settings.EXTRA_VALUE);
886
887        values.put(LauncherSettings.Favorites._ID, item.id);
888
889        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
890        Runnable r = new Runnable() {
891            public void run() {
892                cr.insert(LauncherSettings.Favorites.CONTENT_URI, values);
893
894                // Lock on mBgLock *after* the db operation
895                synchronized (sBgLock) {
896                    checkItemInfoLocked(item.id, item, stackTrace);
897                    sBgItemsIdMap.put(item.id, item);
898                    switch (item.itemType) {
899                        case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
900                            sBgFolders.put(item.id, (FolderInfo) item);
901                            // Fall through
902                        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
903                        case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
904                        case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
905                            if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
906                                    item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
907                                sBgWorkspaceItems.add(item);
908                            } else {
909                                if (!sBgFolders.containsKey(item.container)) {
910                                    // Adding an item to a folder that doesn't exist.
911                                    String msg = "adding item: " + item + " to a folder that " +
912                                            " doesn't exist";
913                                    Log.e(TAG, msg);
914                                }
915                            }
916                            if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
917                                incrementPinnedShortcutCount(
918                                        ShortcutKey.fromItemInfo(item), true /* shouldPin */);
919                            }
920                            break;
921                        case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
922                            sBgAppWidgets.add((LauncherAppWidgetInfo) item);
923                            break;
924                    }
925                }
926            }
927        };
928        runOnWorkerThread(r);
929    }
930
931    private static ArrayList<ItemInfo> getItemsByPackageName(
932            final String pn, final UserHandleCompat user) {
933        ItemInfoFilter filter  = new ItemInfoFilter() {
934            @Override
935            public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
936                return cn.getPackageName().equals(pn) && info.user.equals(user);
937            }
938        };
939        return filterItemInfos(sBgItemsIdMap, filter);
940    }
941
942    /**
943     * Removes all the items from the database corresponding to the specified package.
944     */
945    static void deletePackageFromDatabase(Context context, final String pn,
946            final UserHandleCompat user) {
947        deleteItemsFromDatabase(context, getItemsByPackageName(pn, user));
948    }
949
950    /**
951     * Removes the specified item from the database
952     */
953    public static void deleteItemFromDatabase(Context context, final ItemInfo item) {
954        ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
955        items.add(item);
956        deleteItemsFromDatabase(context, items);
957    }
958
959    /**
960     * Removes the specified items from the database
961     */
962    static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
963        final ContentResolver cr = context.getContentResolver();
964        Runnable r = new Runnable() {
965            public void run() {
966                for (ItemInfo item : items) {
967                    final Uri uri = LauncherSettings.Favorites.getContentUri(item.id);
968                    cr.delete(uri, null, null);
969
970                    // Lock on mBgLock *after* the db operation
971                    synchronized (sBgLock) {
972                        switch (item.itemType) {
973                            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
974                                sBgFolders.remove(item.id);
975                                for (ItemInfo info: sBgItemsIdMap) {
976                                    if (info.container == item.id) {
977                                        // We are deleting a folder which still contains items that
978                                        // think they are contained by that folder.
979                                        String msg = "deleting a folder (" + item + ") which still " +
980                                                "contains items (" + info + ")";
981                                        Log.e(TAG, msg);
982                                    }
983                                }
984                                sBgWorkspaceItems.remove(item);
985                                break;
986                            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
987                                decrementPinnedShortcutCount(ShortcutKey.fromItemInfo(item));
988                                // Fall through.
989                            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
990                            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
991                                sBgWorkspaceItems.remove(item);
992                                break;
993                            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
994                                sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
995                                break;
996                        }
997                        sBgItemsIdMap.remove(item.id);
998                    }
999                }
1000            }
1001        };
1002        runOnWorkerThread(r);
1003    }
1004
1005    /**
1006     * Decrement the count for the given pinned shortcut, unpinning it if the count becomes 0.
1007     */
1008    private static void decrementPinnedShortcutCount(final ShortcutKey pinnedShortcut) {
1009        synchronized (sBgLock) {
1010            MutableInt count = sBgPinnedShortcutCounts.get(pinnedShortcut);
1011            if (count == null || --count.value == 0) {
1012                LauncherAppState.getInstance().getShortcutManager().unpinShortcut(pinnedShortcut);
1013            }
1014        }
1015    }
1016
1017    /**
1018     * Increment the count for the given shortcut, pinning it if the count becomes 1.
1019     *
1020     * As an optimization, the caller can pass shouldPin == false to avoid
1021     * unnecessary RPC's if the shortcut is already pinned.
1022     */
1023    private static void incrementPinnedShortcutCount(ShortcutKey pinnedShortcut, boolean shouldPin) {
1024        synchronized (sBgLock) {
1025            MutableInt count = sBgPinnedShortcutCounts.get(pinnedShortcut);
1026            if (count == null) {
1027                count = new MutableInt(1);
1028                sBgPinnedShortcutCounts.put(pinnedShortcut, count);
1029            } else {
1030                count.value++;
1031            }
1032            if (shouldPin && count.value == 1) {
1033                LauncherAppState.getInstance().getShortcutManager().pinShortcut(pinnedShortcut);
1034            }
1035        }
1036    }
1037
1038    /**
1039     * Update the order of the workspace screens in the database. The array list contains
1040     * a list of screen ids in the order that they should appear.
1041     */
1042    public void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
1043        final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
1044        final ContentResolver cr = context.getContentResolver();
1045        final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1046
1047        // Remove any negative screen ids -- these aren't persisted
1048        Iterator<Long> iter = screensCopy.iterator();
1049        while (iter.hasNext()) {
1050            long id = iter.next();
1051            if (id < 0) {
1052                iter.remove();
1053            }
1054        }
1055
1056        Runnable r = new Runnable() {
1057            @Override
1058            public void run() {
1059                ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1060                // Clear the table
1061                ops.add(ContentProviderOperation.newDelete(uri).build());
1062                int count = screensCopy.size();
1063                for (int i = 0; i < count; i++) {
1064                    ContentValues v = new ContentValues();
1065                    long screenId = screensCopy.get(i);
1066                    v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
1067                    v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
1068                    ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
1069                }
1070
1071                try {
1072                    cr.applyBatch(LauncherProvider.AUTHORITY, ops);
1073                } catch (Exception ex) {
1074                    throw new RuntimeException(ex);
1075                }
1076
1077                synchronized (sBgLock) {
1078                    sBgWorkspaceScreens.clear();
1079                    sBgWorkspaceScreens.addAll(screensCopy);
1080                }
1081            }
1082        };
1083        runOnWorkerThread(r);
1084    }
1085
1086    /**
1087     * Remove the specified folder and all its contents from the database.
1088     */
1089    public static void deleteFolderAndContentsFromDatabase(Context context, final FolderInfo info) {
1090        final ContentResolver cr = context.getContentResolver();
1091
1092        Runnable r = new Runnable() {
1093            public void run() {
1094                cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
1095                // Lock on mBgLock *after* the db operation
1096                synchronized (sBgLock) {
1097                    sBgItemsIdMap.remove(info.id);
1098                    sBgFolders.remove(info.id);
1099                    sBgWorkspaceItems.remove(info);
1100                }
1101
1102                cr.delete(LauncherSettings.Favorites.CONTENT_URI,
1103                        LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
1104                // Lock on mBgLock *after* the db operation
1105                synchronized (sBgLock) {
1106                    for (ItemInfo childInfo : info.contents) {
1107                        sBgItemsIdMap.remove(childInfo.id);
1108                    }
1109                }
1110            }
1111        };
1112        runOnWorkerThread(r);
1113    }
1114
1115    /**
1116     * Set this as the current Launcher activity object for the loader.
1117     */
1118    public void initialize(Callbacks callbacks) {
1119        synchronized (mLock) {
1120            Preconditions.assertUIThread();
1121            // Remove any queued UI runnables
1122            mHandler.cancelAll();
1123            mCallbacks = new WeakReference<>(callbacks);
1124        }
1125    }
1126
1127    @Override
1128    public void onPackageChanged(String packageName, UserHandleCompat user) {
1129        int op = PackageUpdatedTask.OP_UPDATE;
1130        enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
1131                user));
1132    }
1133
1134    @Override
1135    public void onPackageRemoved(String packageName, UserHandleCompat user) {
1136        int op = PackageUpdatedTask.OP_REMOVE;
1137        enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
1138                user));
1139    }
1140
1141    @Override
1142    public void onPackageAdded(String packageName, UserHandleCompat user) {
1143        int op = PackageUpdatedTask.OP_ADD;
1144        enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
1145                user));
1146    }
1147
1148    @Override
1149    public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
1150            boolean replacing) {
1151        enqueueItemUpdatedTask(
1152                new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, packageNames, user));
1153    }
1154
1155    @Override
1156    public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user,
1157            boolean replacing) {
1158        if (!replacing) {
1159            enqueueItemUpdatedTask(new PackageUpdatedTask(
1160                    PackageUpdatedTask.OP_UNAVAILABLE, packageNames,
1161                    user));
1162        }
1163    }
1164
1165    @Override
1166    public void onPackagesSuspended(String[] packageNames, UserHandleCompat user) {
1167        enqueueItemUpdatedTask(new PackageUpdatedTask(
1168                PackageUpdatedTask.OP_SUSPEND, packageNames,
1169                user));
1170    }
1171
1172    @Override
1173    public void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user) {
1174        enqueueItemUpdatedTask(new PackageUpdatedTask(
1175                PackageUpdatedTask.OP_UNSUSPEND, packageNames,
1176                user));
1177    }
1178
1179    @Override
1180    public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
1181            UserHandleCompat user) {
1182        enqueueItemUpdatedTask(new ShortcutsChangedTask(packageName, shortcuts, user));
1183    }
1184
1185    /**
1186     * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
1187     * ACTION_PACKAGE_CHANGED.
1188     */
1189    @Override
1190    public void onReceive(Context context, Intent intent) {
1191        if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
1192
1193        final String action = intent.getAction();
1194        if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
1195            // If we have changed locale we need to clear out the labels in all apps/workspace.
1196            forceReload();
1197        } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)
1198                || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
1199            UserManagerCompat.getInstance(context).enableAndResetCache();
1200            forceReload();
1201        } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
1202                Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
1203                Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
1204            UserHandleCompat user = UserHandleCompat.fromIntent(intent);
1205            if (user != null) {
1206                if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
1207                        Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
1208                    enqueueItemUpdatedTask(new PackageUpdatedTask(
1209                            PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE,
1210                            new String[0], user));
1211                }
1212
1213                // ACTION_MANAGED_PROFILE_UNAVAILABLE sends the profile back to locked mode, so
1214                // we need to run the state change task again.
1215                if (Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
1216                        Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
1217                    enqueueItemUpdatedTask(new UserLockStateChangedTask(user));
1218                }
1219            }
1220        } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(action)) {
1221            ExtractionUtils.startColorExtractionServiceIfNecessary(context);
1222        }
1223    }
1224
1225    void forceReload() {
1226        resetLoadedState(true, true);
1227
1228        // Do this here because if the launcher activity is running it will be restarted.
1229        // If it's not running startLoaderFromBackground will merely tell it that it needs
1230        // to reload.
1231        startLoaderFromBackground();
1232    }
1233
1234    public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
1235        synchronized (mLock) {
1236            // Stop any existing loaders first, so they don't set mAllAppsLoaded or
1237            // mWorkspaceLoaded to true later
1238            stopLoaderLocked();
1239            if (resetAllAppsLoaded) mAllAppsLoaded = false;
1240            if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
1241            // Always reset deep shortcuts loaded.
1242            mDeepShortcutsLoaded = false;
1243        }
1244    }
1245
1246    /**
1247     * When the launcher is in the background, it's possible for it to miss paired
1248     * configuration changes.  So whenever we trigger the loader from the background
1249     * tell the launcher that it needs to re-run the loader when it comes back instead
1250     * of doing it now.
1251     */
1252    public void startLoaderFromBackground() {
1253        Callbacks callbacks = getCallback();
1254        if (callbacks != null) {
1255            // Only actually run the loader if they're not paused.
1256            if (!callbacks.setLoadOnResume()) {
1257                startLoader(callbacks.getCurrentWorkspaceScreen());
1258            }
1259        }
1260    }
1261
1262    /**
1263     * If there is already a loader task running, tell it to stop.
1264     */
1265    private void stopLoaderLocked() {
1266        LoaderTask oldTask = mLoaderTask;
1267        if (oldTask != null) {
1268            oldTask.stopLocked();
1269        }
1270    }
1271
1272    public boolean isCurrentCallbacks(Callbacks callbacks) {
1273        return (mCallbacks != null && mCallbacks.get() == callbacks);
1274    }
1275
1276    /**
1277     * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible.
1278     * @return true if the page could be bound synchronously.
1279     */
1280    public boolean startLoader(int synchronousBindPage) {
1281        // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
1282        InstallShortcutReceiver.enableInstallQueue();
1283        synchronized (mLock) {
1284            // Don't bother to start the thread if we know it's not going to do anything
1285            if (mCallbacks != null && mCallbacks.get() != null) {
1286                final Callbacks oldCallbacks = mCallbacks.get();
1287                // Clear any pending bind-runnables from the synchronized load process.
1288                runOnMainThread(new Runnable() {
1289                    public void run() {
1290                        oldCallbacks.clearPendingBinds();
1291                    }
1292                });
1293
1294                // If there is already one running, tell it to stop.
1295                stopLoaderLocked();
1296                mLoaderTask = new LoaderTask(mApp.getContext(), synchronousBindPage);
1297                if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE && mAllAppsLoaded
1298                        && mWorkspaceLoaded && mDeepShortcutsLoaded && !mIsLoaderTaskRunning) {
1299                    mLoaderTask.runBindSynchronousPage(synchronousBindPage);
1300                    return true;
1301                } else {
1302                    sWorkerThread.setPriority(Thread.NORM_PRIORITY);
1303                    sWorker.post(mLoaderTask);
1304                }
1305            }
1306        }
1307        return false;
1308    }
1309
1310    public void stopLoader() {
1311        synchronized (mLock) {
1312            if (mLoaderTask != null) {
1313                mLoaderTask.stopLocked();
1314            }
1315        }
1316    }
1317
1318    /**
1319     * Loads the workspace screen ids in an ordered list.
1320     */
1321    public static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
1322        final ContentResolver contentResolver = context.getContentResolver();
1323        final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1324
1325        // Get screens ordered by rank.
1326        return LauncherDbUtils.getScreenIdsFromCursor(contentResolver.query(
1327                screensUri, null, null, null, LauncherSettings.WorkspaceScreens.SCREEN_RANK));
1328    }
1329
1330    /**
1331     * Runnable for the thread that loads the contents of the launcher:
1332     *   - workspace icons
1333     *   - widgets
1334     *   - all apps icons
1335     *   - deep shortcuts within apps
1336     */
1337    private class LoaderTask implements Runnable {
1338        private Context mContext;
1339        private int mPageToBindFirst;
1340
1341        @Thunk boolean mIsLoadingAndBindingWorkspace;
1342        private boolean mStopped;
1343        @Thunk boolean mLoadAndBindStepFinished;
1344
1345        LoaderTask(Context context, int pageToBindFirst) {
1346            mContext = context;
1347            mPageToBindFirst = pageToBindFirst;
1348        }
1349
1350        private void loadAndBindWorkspace() {
1351            mIsLoadingAndBindingWorkspace = true;
1352
1353            // Load the workspace
1354            if (DEBUG_LOADERS) {
1355                Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
1356            }
1357
1358            if (!mWorkspaceLoaded) {
1359                loadWorkspace();
1360                synchronized (LoaderTask.this) {
1361                    if (mStopped) {
1362                        return;
1363                    }
1364                    mWorkspaceLoaded = true;
1365                }
1366            }
1367
1368            // Bind the workspace
1369            bindWorkspace(mPageToBindFirst);
1370        }
1371
1372        private void waitForIdle() {
1373            // Wait until the either we're stopped or the other threads are done.
1374            // This way we don't start loading all apps until the workspace has settled
1375            // down.
1376            synchronized (LoaderTask.this) {
1377                final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1378
1379                mHandler.postIdle(new Runnable() {
1380                        public void run() {
1381                            synchronized (LoaderTask.this) {
1382                                mLoadAndBindStepFinished = true;
1383                                if (DEBUG_LOADERS) {
1384                                    Log.d(TAG, "done with previous binding step");
1385                                }
1386                                LoaderTask.this.notify();
1387                            }
1388                        }
1389                    });
1390
1391                while (!mStopped && !mLoadAndBindStepFinished) {
1392                    try {
1393                        // Just in case mFlushingWorkerThread changes but we aren't woken up,
1394                        // wait no longer than 1sec at a time
1395                        this.wait(1000);
1396                    } catch (InterruptedException ex) {
1397                        // Ignore
1398                    }
1399                }
1400                if (DEBUG_LOADERS) {
1401                    Log.d(TAG, "waited "
1402                            + (SystemClock.uptimeMillis()-workspaceWaitTime)
1403                            + "ms for previous step to finish binding");
1404                }
1405            }
1406        }
1407
1408        void runBindSynchronousPage(int synchronousBindPage) {
1409            if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) {
1410                // Ensure that we have a valid page index to load synchronously
1411                throw new RuntimeException("Should not call runBindSynchronousPage() without " +
1412                        "valid page index");
1413            }
1414            if (!mAllAppsLoaded || !mWorkspaceLoaded) {
1415                // Ensure that we don't try and bind a specified page when the pages have not been
1416                // loaded already (we should load everything asynchronously in that case)
1417                throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
1418            }
1419            synchronized (mLock) {
1420                if (mIsLoaderTaskRunning) {
1421                    // Ensure that we are never running the background loading at this point since
1422                    // we also touch the background collections
1423                    throw new RuntimeException("Error! Background loading is already running");
1424                }
1425            }
1426
1427            // XXX: Throw an exception if we are already loading (since we touch the worker thread
1428            //      data structures, we can't allow any other thread to touch that data, but because
1429            //      this call is synchronous, we can get away with not locking).
1430
1431            // The LauncherModel is static in the LauncherAppState and mHandler may have queued
1432            // operations from the previous activity.  We need to ensure that all queued operations
1433            // are executed before any synchronous binding work is done.
1434            mHandler.flush();
1435
1436            // Divide the set of loaded items into those that we are binding synchronously, and
1437            // everything else that is to be bound normally (asynchronously).
1438            bindWorkspace(synchronousBindPage);
1439            // XXX: For now, continue posting the binding of AllApps as there are other issues that
1440            //      arise from that.
1441            onlyBindAllApps();
1442
1443            bindDeepShortcuts();
1444        }
1445
1446        public void run() {
1447            synchronized (mLock) {
1448                if (mStopped) {
1449                    return;
1450                }
1451                mIsLoaderTaskRunning = true;
1452            }
1453            // Optimize for end-user experience: if the Launcher is up and // running with the
1454            // All Apps interface in the foreground, load All Apps first. Otherwise, load the
1455            // workspace first (default).
1456            keep_running: {
1457                if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
1458                loadAndBindWorkspace();
1459
1460                if (mStopped) {
1461                    break keep_running;
1462                }
1463
1464                waitForIdle();
1465
1466                // second step
1467                if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
1468                loadAndBindAllApps();
1469
1470                waitForIdle();
1471
1472                // third step
1473                if (DEBUG_LOADERS) Log.d(TAG, "step 3: loading deep shortcuts");
1474                loadAndBindDeepShortcuts();
1475            }
1476
1477            // Clear out this reference, otherwise we end up holding it until all of the
1478            // callback runnables are done.
1479            mContext = null;
1480
1481            synchronized (mLock) {
1482                // If we are still the last one to be scheduled, remove ourselves.
1483                if (mLoaderTask == this) {
1484                    mLoaderTask = null;
1485                }
1486                mIsLoaderTaskRunning = false;
1487                mHasLoaderCompletedOnce = true;
1488            }
1489        }
1490
1491        public void stopLocked() {
1492            synchronized (LoaderTask.this) {
1493                mStopped = true;
1494                this.notify();
1495            }
1496        }
1497
1498        /**
1499         * Gets the callbacks object.  If we've been stopped, or if the launcher object
1500         * has somehow been garbage collected, return null instead.  Pass in the Callbacks
1501         * object that was around when the deferred message was scheduled, and if there's
1502         * a new Callbacks object around then also return null.  This will save us from
1503         * calling onto it with data that will be ignored.
1504         */
1505        Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
1506            synchronized (mLock) {
1507                if (mStopped) {
1508                    return null;
1509                }
1510
1511                if (mCallbacks == null) {
1512                    return null;
1513                }
1514
1515                final Callbacks callbacks = mCallbacks.get();
1516                if (callbacks != oldCallbacks) {
1517                    return null;
1518                }
1519                if (callbacks == null) {
1520                    Log.w(TAG, "no mCallbacks");
1521                    return null;
1522                }
1523
1524                return callbacks;
1525            }
1526        }
1527
1528        // check & update map of what's occupied; used to discard overlapping/invalid items
1529        private boolean checkItemPlacement(LongArrayMap<GridOccupancy> occupied, ItemInfo item,
1530                   ArrayList<Long> workspaceScreens) {
1531            LauncherAppState app = LauncherAppState.getInstance();
1532            InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
1533
1534            long containerIndex = item.screenId;
1535            if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1536                // Return early if we detect that an item is under the hotseat button
1537                if (!FeatureFlags.NO_ALL_APPS_ICON &&
1538                        profile.isAllAppsButtonRank((int) item.screenId)) {
1539                    Log.e(TAG, "Error loading shortcut into hotseat " + item
1540                            + " into position (" + item.screenId + ":" + item.cellX + ","
1541                            + item.cellY + ") occupied by all apps");
1542                    return false;
1543                }
1544
1545                final GridOccupancy hotseatOccupancy =
1546                        occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
1547
1548                if (item.screenId >= profile.numHotseatIcons) {
1549                    Log.e(TAG, "Error loading shortcut " + item
1550                            + " into hotseat position " + item.screenId
1551                            + ", position out of bounds: (0 to " + (profile.numHotseatIcons - 1)
1552                            + ")");
1553                    return false;
1554                }
1555
1556                if (hotseatOccupancy != null) {
1557                    if (hotseatOccupancy.cells[(int) item.screenId][0]) {
1558                        Log.e(TAG, "Error loading shortcut into hotseat " + item
1559                                + " into position (" + item.screenId + ":" + item.cellX + ","
1560                                + item.cellY + ") already occupied");
1561                            return false;
1562                    } else {
1563                        hotseatOccupancy.cells[(int) item.screenId][0] = true;
1564                        return true;
1565                    }
1566                } else {
1567                    final GridOccupancy occupancy = new GridOccupancy(profile.numHotseatIcons, 1);
1568                    occupancy.cells[(int) item.screenId][0] = true;
1569                    occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, occupancy);
1570                    return true;
1571                }
1572            } else if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1573                if (!workspaceScreens.contains((Long) item.screenId)) {
1574                    // The item has an invalid screen id.
1575                    return false;
1576                }
1577            } else {
1578                // Skip further checking if it is not the hotseat or workspace container
1579                return true;
1580            }
1581
1582            final int countX = profile.numColumns;
1583            final int countY = profile.numRows;
1584            if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1585                    item.cellX < 0 || item.cellY < 0 ||
1586                    item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) {
1587                Log.e(TAG, "Error loading shortcut " + item
1588                        + " into cell (" + containerIndex + "-" + item.screenId + ":"
1589                        + item.cellX + "," + item.cellY
1590                        + ") out of screen bounds ( " + countX + "x" + countY + ")");
1591                return false;
1592            }
1593
1594            if (!occupied.containsKey(item.screenId)) {
1595                GridOccupancy screen = new GridOccupancy(countX + 1, countY + 1);
1596                if (item.screenId == Workspace.FIRST_SCREEN_ID) {
1597                    // Mark the first row as occupied (if the feature is enabled)
1598                    // in order to account for the QSB.
1599                    screen.markCells(0, 0, countX + 1, 1, FeatureFlags.QSB_ON_FIRST_SCREEN);
1600                }
1601                occupied.put(item.screenId, screen);
1602            }
1603            final GridOccupancy occupancy = occupied.get(item.screenId);
1604
1605            // Check if any workspace icons overlap with each other
1606            if (occupancy.isRegionVacant(item.cellX, item.cellY, item.spanX, item.spanY)) {
1607                occupancy.markCells(item, true);
1608                return true;
1609            } else {
1610                Log.e(TAG, "Error loading shortcut " + item
1611                        + " into cell (" + containerIndex + "-" + item.screenId + ":"
1612                        + item.cellX + "," + item.cellX + "," + item.spanX + "," + item.spanY
1613                        + ") already occupied");
1614                return false;
1615            }
1616        }
1617
1618        /** Clears all the sBg data structures */
1619        private void clearSBgDataStructures() {
1620            synchronized (sBgLock) {
1621                sBgWorkspaceItems.clear();
1622                sBgAppWidgets.clear();
1623                sBgFolders.clear();
1624                sBgItemsIdMap.clear();
1625                sBgWorkspaceScreens.clear();
1626                sBgPinnedShortcutCounts.clear();
1627            }
1628        }
1629
1630        private void loadWorkspace() {
1631            if (LauncherAppState.PROFILE_STARTUP) {
1632                Trace.beginSection("Loading Workspace");
1633            }
1634            final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1635
1636            final Context context = mContext;
1637            final ContentResolver contentResolver = context.getContentResolver();
1638            final PackageManager manager = context.getPackageManager();
1639            final boolean isSafeMode = manager.isSafeMode();
1640            final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
1641            final boolean isSdCardReady = Utilities.isBootCompleted();
1642
1643            LauncherAppState app = LauncherAppState.getInstance();
1644            InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
1645            int countX = profile.numColumns;
1646            int countY = profile.numRows;
1647
1648            boolean clearDb = false;
1649            try {
1650                ImportDataTask.performImportIfPossible(context);
1651            } catch (Exception e) {
1652                // Migration failed. Clear workspace.
1653                clearDb = true;
1654            }
1655
1656            if (!clearDb && GridSizeMigrationTask.ENABLED &&
1657                    !GridSizeMigrationTask.migrateGridIfNeeded(mContext)) {
1658                // Migration failed. Clear workspace.
1659                clearDb = true;
1660            }
1661
1662            if (clearDb) {
1663                Log.d(TAG, "loadWorkspace: resetting launcher database");
1664                LauncherSettings.Settings.call(contentResolver,
1665                        LauncherSettings.Settings.METHOD_DELETE_DB);
1666            }
1667
1668            Log.d(TAG, "loadWorkspace: loading default favorites");
1669            LauncherSettings.Settings.call(contentResolver,
1670                    LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES);
1671
1672            synchronized (sBgLock) {
1673                clearSBgDataStructures();
1674                final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
1675                        .getInstance(mContext).updateAndGetActiveSessionCache();
1676                sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
1677
1678                final ArrayList<Long> itemsToRemove = new ArrayList<>();
1679                final ArrayList<Long> restoredRows = new ArrayList<>();
1680                Map<ShortcutKey, ShortcutInfoCompat> shortcutKeyToPinnedShortcuts = new HashMap<>();
1681                final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
1682                if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
1683                final Cursor c = contentResolver.query(contentUri, null, null, null, null);
1684
1685                // +1 for the hotseat (it can be larger than the workspace)
1686                // Load workspace in reverse order to ensure that latest items are loaded first (and
1687                // before any earlier duplicates)
1688                final LongArrayMap<GridOccupancy> occupied = new LongArrayMap<>();
1689                HashMap<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null;
1690
1691                try {
1692                    final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1693                    final int intentIndex = c.getColumnIndexOrThrow
1694                            (LauncherSettings.Favorites.INTENT);
1695                    final int containerIndex = c.getColumnIndexOrThrow(
1696                            LauncherSettings.Favorites.CONTAINER);
1697                    final int itemTypeIndex = c.getColumnIndexOrThrow(
1698                            LauncherSettings.Favorites.ITEM_TYPE);
1699                    final int appWidgetIdIndex = c.getColumnIndexOrThrow(
1700                            LauncherSettings.Favorites.APPWIDGET_ID);
1701                    final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
1702                            LauncherSettings.Favorites.APPWIDGET_PROVIDER);
1703                    final int screenIndex = c.getColumnIndexOrThrow(
1704                            LauncherSettings.Favorites.SCREEN);
1705                    final int cellXIndex = c.getColumnIndexOrThrow
1706                            (LauncherSettings.Favorites.CELLX);
1707                    final int cellYIndex = c.getColumnIndexOrThrow
1708                            (LauncherSettings.Favorites.CELLY);
1709                    final int spanXIndex = c.getColumnIndexOrThrow
1710                            (LauncherSettings.Favorites.SPANX);
1711                    final int spanYIndex = c.getColumnIndexOrThrow(
1712                            LauncherSettings.Favorites.SPANY);
1713                    final int rankIndex = c.getColumnIndexOrThrow(
1714                            LauncherSettings.Favorites.RANK);
1715                    final int restoredIndex = c.getColumnIndexOrThrow(
1716                            LauncherSettings.Favorites.RESTORED);
1717                    final int profileIdIndex = c.getColumnIndexOrThrow(
1718                            LauncherSettings.Favorites.PROFILE_ID);
1719                    final int optionsIndex = c.getColumnIndexOrThrow(
1720                            LauncherSettings.Favorites.OPTIONS);
1721                    final CursorIconInfo cursorIconInfo = new CursorIconInfo(mContext, c);
1722
1723                    final LongSparseArray<UserHandleCompat> allUsers = new LongSparseArray<>();
1724                    final LongSparseArray<Boolean> quietMode = new LongSparseArray<>();
1725                    final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
1726                    for (UserHandleCompat user : mUserManager.getUserProfiles()) {
1727                        long serialNo = mUserManager.getSerialNumberForUser(user);
1728                        allUsers.put(serialNo, user);
1729                        quietMode.put(serialNo, mUserManager.isQuietModeEnabled(user));
1730
1731                        boolean userUnlocked = mUserManager.isUserUnlocked(user);
1732                        unlockedUsers.put(serialNo, userUnlocked);
1733
1734                        // We can only query for shortcuts when the user is unlocked.
1735                        if (userUnlocked) {
1736                            List<ShortcutInfoCompat> pinnedShortcuts = mDeepShortcutManager
1737                                    .queryForPinnedShortcuts(null, user);
1738                            for (ShortcutInfoCompat shortcut : pinnedShortcuts) {
1739                                shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
1740                                        shortcut);
1741                            }
1742                        }
1743                    }
1744
1745                    ShortcutInfo info;
1746                    String intentDescription;
1747                    LauncherAppWidgetInfo appWidgetInfo;
1748                    int container;
1749                    long id;
1750                    long serialNumber;
1751                    Intent intent;
1752                    UserHandleCompat user;
1753                    String targetPackage;
1754
1755                    while (!mStopped && c.moveToNext()) {
1756                        try {
1757                            int itemType = c.getInt(itemTypeIndex);
1758                            boolean restored = 0 != c.getInt(restoredIndex);
1759                            boolean allowMissingTarget = false;
1760                            container = c.getInt(containerIndex);
1761
1762                            switch (itemType) {
1763                            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1764                            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1765                            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
1766                                id = c.getLong(idIndex);
1767                                intentDescription = c.getString(intentIndex);
1768                                serialNumber = c.getInt(profileIdIndex);
1769                                user = allUsers.get(serialNumber);
1770                                int promiseType = c.getInt(restoredIndex);
1771                                int disabledState = 0;
1772                                boolean itemReplaced = false;
1773                                targetPackage = null;
1774                                if (user == null) {
1775                                    // User has been deleted remove the item.
1776                                    itemsToRemove.add(id);
1777                                    continue;
1778                                }
1779                                try {
1780                                    intent = Intent.parseUri(intentDescription, 0);
1781                                    ComponentName cn = intent.getComponent();
1782                                    if (cn != null && cn.getPackageName() != null) {
1783                                        boolean validPkg = launcherApps.isPackageEnabledForProfile(
1784                                                cn.getPackageName(), user);
1785                                        boolean validComponent = validPkg &&
1786                                                launcherApps.isActivityEnabledForProfile(cn, user);
1787                                        if (validPkg) {
1788                                            targetPackage = cn.getPackageName();
1789                                        }
1790
1791                                        if (validComponent) {
1792                                            if (restored) {
1793                                                // no special handling necessary for this item
1794                                                restoredRows.add(id);
1795                                                restored = false;
1796                                            }
1797                                            if (quietMode.get(serialNumber)) {
1798                                                disabledState = ShortcutInfo.FLAG_DISABLED_QUIET_USER;
1799                                            }
1800                                        } else if (validPkg) {
1801                                            intent = null;
1802                                            if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
1803                                                // We allow auto install apps to have their intent
1804                                                // updated after an install.
1805                                                intent = manager.getLaunchIntentForPackage(
1806                                                        cn.getPackageName());
1807                                                if (intent != null) {
1808                                                    ContentValues values = new ContentValues();
1809                                                    values.put(LauncherSettings.Favorites.INTENT,
1810                                                            intent.toUri(0));
1811                                                    updateItem(id, values);
1812                                                }
1813                                            }
1814
1815                                            if (intent == null) {
1816                                                // The app is installed but the component is no
1817                                                // longer available.
1818                                                FileLog.d(TAG, "Invalid component removed: " + cn);
1819                                                itemsToRemove.add(id);
1820                                                continue;
1821                                            } else {
1822                                                // no special handling necessary for this item
1823                                                restoredRows.add(id);
1824                                                restored = false;
1825                                            }
1826                                        } else if (restored) {
1827                                            // Package is not yet available but might be
1828                                            // installed later.
1829                                            FileLog.d(TAG, "package not yet restored: " + cn);
1830
1831                                            if ((promiseType & ShortcutInfo.FLAG_RESTORE_STARTED) != 0) {
1832                                                // Restore has started once.
1833                                            } else if (installingPkgs.containsKey(cn.getPackageName())) {
1834                                                // App restore has started. Update the flag
1835                                                promiseType |= ShortcutInfo.FLAG_RESTORE_STARTED;
1836                                                ContentValues values = new ContentValues();
1837                                                values.put(LauncherSettings.Favorites.RESTORED,
1838                                                        promiseType);
1839                                                updateItem(id, values);
1840                                            } else if ((promiseType & ShortcutInfo.FLAG_RESTORED_APP_TYPE) != 0) {
1841                                                // This is a common app. Try to replace this.
1842                                                int appType = CommonAppTypeParser.decodeItemTypeFromFlag(promiseType);
1843                                                CommonAppTypeParser parser = new CommonAppTypeParser(id, appType, context);
1844                                                if (parser.findDefaultApp()) {
1845                                                    // Default app found. Replace it.
1846                                                    intent = parser.parsedIntent;
1847                                                    cn = intent.getComponent();
1848                                                    ContentValues values = parser.parsedValues;
1849                                                    values.put(LauncherSettings.Favorites.RESTORED, 0);
1850                                                    updateItem(id, values);
1851                                                    restored = false;
1852                                                    itemReplaced = true;
1853
1854                                                } else if (REMOVE_UNRESTORED_ICONS) {
1855                                                    FileLog.d(TAG, "Unrestored package removed: " + cn);
1856                                                    itemsToRemove.add(id);
1857                                                    continue;
1858                                                }
1859                                            } else if (REMOVE_UNRESTORED_ICONS) {
1860                                                FileLog.d(TAG, "Unrestored package removed: " + cn);
1861                                                itemsToRemove.add(id);
1862                                                continue;
1863                                            }
1864                                        } else if (PackageManagerHelper.isAppOnSdcard(
1865                                                manager, cn.getPackageName())) {
1866                                            // Package is present but not available.
1867                                            allowMissingTarget = true;
1868                                            disabledState = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
1869                                        } else if (!isSdCardReady) {
1870                                            // SdCard is not ready yet. Package might get available,
1871                                            // once it is ready.
1872                                            Log.d(TAG, "Invalid package: " + cn + " (check again later)");
1873                                            HashSet<String> pkgs = sPendingPackages.get(user);
1874                                            if (pkgs == null) {
1875                                                pkgs = new HashSet<String>();
1876                                                sPendingPackages.put(user, pkgs);
1877                                            }
1878                                            pkgs.add(cn.getPackageName());
1879                                            allowMissingTarget = true;
1880                                            // Add the icon on the workspace anyway.
1881
1882                                        } else {
1883                                            // Do not wait for external media load anymore.
1884                                            // Log the invalid package, and remove it
1885                                            FileLog.d(TAG, "Invalid package removed: " + cn);
1886                                            itemsToRemove.add(id);
1887                                            continue;
1888                                        }
1889                                    } else if (cn == null) {
1890                                        // For shortcuts with no component, keep them as they are
1891                                        restoredRows.add(id);
1892                                        restored = false;
1893                                    }
1894                                } catch (URISyntaxException e) {
1895                                    FileLog.d(TAG, "Invalid uri: " + intentDescription);
1896                                    itemsToRemove.add(id);
1897                                    continue;
1898                                }
1899
1900                                boolean useLowResIcon = container >= 0 &&
1901                                        c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
1902
1903                                if (itemReplaced) {
1904                                    if (user.equals(UserHandleCompat.myUserHandle())) {
1905                                        info = getAppShortcutInfo(intent, user, null,
1906                                                cursorIconInfo, false, useLowResIcon);
1907                                    } else {
1908                                        // Don't replace items for other profiles.
1909                                        itemsToRemove.add(id);
1910                                        continue;
1911                                    }
1912                                } else if (restored) {
1913                                    if (user.equals(UserHandleCompat.myUserHandle())) {
1914                                        info = getRestoredItemInfo(c, intent,
1915                                                promiseType, itemType, cursorIconInfo);
1916                                        intent = getRestoredItemIntent(c, context, intent);
1917                                    } else {
1918                                        // Don't restore items for other profiles.
1919                                        itemsToRemove.add(id);
1920                                        continue;
1921                                    }
1922                                } else if (itemType ==
1923                                        LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
1924                                    info = getAppShortcutInfo(intent, user, c,
1925                                            cursorIconInfo, allowMissingTarget, useLowResIcon);
1926                                } else if (itemType ==
1927                                        LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
1928
1929                                    ShortcutKey key = ShortcutKey.fromIntent(intent, user);
1930                                    if (unlockedUsers.get(serialNumber)) {
1931                                        ShortcutInfoCompat pinnedShortcut =
1932                                                shortcutKeyToPinnedShortcuts.get(key);
1933                                        if (pinnedShortcut == null) {
1934                                            // The shortcut is no longer valid.
1935                                            itemsToRemove.add(id);
1936                                            continue;
1937                                        }
1938                                        info = new ShortcutInfo(pinnedShortcut, context);
1939                                        intent = info.intent;
1940                                    } else {
1941                                        // Create a shortcut info in disabled mode for now.
1942                                        info = new ShortcutInfo();
1943                                        info.user = user;
1944                                        info.itemType = itemType;
1945                                        loadInfoFromCursor(info, c, cursorIconInfo);
1946
1947                                        info.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
1948                                    }
1949                                    incrementPinnedShortcutCount(key, false /* shouldPin */);
1950                                } else { // item type == ITEM_TYPE_SHORTCUT
1951                                    info = getShortcutInfo(c, cursorIconInfo);
1952
1953                                    // Shortcuts are only available on the primary profile
1954                                    if (PackageManagerHelper.isAppSuspended(manager, targetPackage)) {
1955                                        disabledState |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
1956                                    }
1957
1958                                    // App shortcuts that used to be automatically added to Launcher
1959                                    // didn't always have the correct intent flags set, so do that
1960                                    // here
1961                                    if (intent.getAction() != null &&
1962                                        intent.getCategories() != null &&
1963                                        intent.getAction().equals(Intent.ACTION_MAIN) &&
1964                                        intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
1965                                        intent.addFlags(
1966                                            Intent.FLAG_ACTIVITY_NEW_TASK |
1967                                            Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
1968                                    }
1969                                }
1970
1971                                if (info != null) {
1972                                    info.id = id;
1973                                    info.intent = intent;
1974                                    info.container = container;
1975                                    info.screenId = c.getInt(screenIndex);
1976                                    info.cellX = c.getInt(cellXIndex);
1977                                    info.cellY = c.getInt(cellYIndex);
1978                                    info.rank = c.getInt(rankIndex);
1979                                    info.spanX = 1;
1980                                    info.spanY = 1;
1981                                    info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
1982                                    if (info.promisedIntent != null) {
1983                                        info.promisedIntent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
1984                                    }
1985                                    info.isDisabled |= disabledState;
1986                                    if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
1987                                        info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
1988                                    }
1989
1990                                    // check & update map of what's occupied
1991                                    if (!checkItemPlacement(occupied, info, sBgWorkspaceScreens)) {
1992                                        itemsToRemove.add(id);
1993                                        break;
1994                                    }
1995
1996                                    if (restored) {
1997                                        ComponentName cn = info.getTargetComponent();
1998                                        if (cn != null) {
1999                                            Integer progress = installingPkgs.get(cn.getPackageName());
2000                                            if (progress != null) {
2001                                                info.setInstallProgress(progress);
2002                                            } else {
2003                                                info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
2004                                            }
2005                                        }
2006                                    }
2007
2008                                    switch (container) {
2009                                    case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2010                                    case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2011                                        sBgWorkspaceItems.add(info);
2012                                        break;
2013                                    default:
2014                                        // Item is in a user folder
2015                                        FolderInfo folderInfo =
2016                                                findOrMakeFolder(sBgFolders, container);
2017                                        folderInfo.add(info, false);
2018                                        break;
2019                                    }
2020                                    sBgItemsIdMap.put(info.id, info);
2021                                } else {
2022                                    throw new RuntimeException("Unexpected null ShortcutInfo");
2023                                }
2024                                break;
2025
2026                            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
2027                                id = c.getLong(idIndex);
2028                                FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
2029
2030                                // Do not trim the folder label, as is was set by the user.
2031                                folderInfo.title = c.getString(cursorIconInfo.titleIndex);
2032                                folderInfo.id = id;
2033                                folderInfo.container = container;
2034                                folderInfo.screenId = c.getInt(screenIndex);
2035                                folderInfo.cellX = c.getInt(cellXIndex);
2036                                folderInfo.cellY = c.getInt(cellYIndex);
2037                                folderInfo.spanX = 1;
2038                                folderInfo.spanY = 1;
2039                                folderInfo.options = c.getInt(optionsIndex);
2040
2041                                // check & update map of what's occupied
2042                                if (!checkItemPlacement(occupied, folderInfo, sBgWorkspaceScreens)) {
2043                                    itemsToRemove.add(id);
2044                                    break;
2045                                }
2046
2047                                switch (container) {
2048                                    case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2049                                    case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2050                                        sBgWorkspaceItems.add(folderInfo);
2051                                        break;
2052                                }
2053
2054                                if (restored) {
2055                                    // no special handling required for restored folders
2056                                    restoredRows.add(id);
2057                                }
2058
2059                                sBgItemsIdMap.put(folderInfo.id, folderInfo);
2060                                sBgFolders.put(folderInfo.id, folderInfo);
2061                                break;
2062
2063                            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2064                            case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
2065                                // Read all Launcher-specific widget details
2066                                boolean customWidget = itemType ==
2067                                    LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
2068
2069                                int appWidgetId = c.getInt(appWidgetIdIndex);
2070                                serialNumber = c.getLong(profileIdIndex);
2071                                String savedProvider = c.getString(appWidgetProviderIndex);
2072                                id = c.getLong(idIndex);
2073                                user = allUsers.get(serialNumber);
2074                                if (user == null) {
2075                                    itemsToRemove.add(id);
2076                                    continue;
2077                                }
2078
2079                                final ComponentName component =
2080                                        ComponentName.unflattenFromString(savedProvider);
2081
2082                                final int restoreStatus = c.getInt(restoredIndex);
2083                                final boolean isIdValid = (restoreStatus &
2084                                        LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) == 0;
2085                                final boolean wasProviderReady = (restoreStatus &
2086                                        LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0;
2087
2088                                if (widgetProvidersMap == null) {
2089                                    widgetProvidersMap = AppWidgetManagerCompat
2090                                            .getInstance(mContext).getAllProvidersMap();
2091                                }
2092                                final AppWidgetProviderInfo provider = widgetProvidersMap.get(
2093                                        new ComponentKey(
2094                                                ComponentName.unflattenFromString(savedProvider),
2095                                                user));
2096
2097                                final boolean isProviderReady = isValidProvider(provider);
2098                                if (!isSafeMode && !customWidget &&
2099                                        wasProviderReady && !isProviderReady) {
2100                                    FileLog.d(TAG, "Deleting widget that isn't installed anymore: "
2101                                            + provider);
2102                                    itemsToRemove.add(id);
2103                                } else {
2104                                    if (isProviderReady) {
2105                                        appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
2106                                                provider.provider);
2107
2108                                        // The provider is available. So the widget is either
2109                                        // available or not available. We do not need to track
2110                                        // any future restore updates.
2111                                        int status = restoreStatus &
2112                                                ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
2113                                        if (!wasProviderReady) {
2114                                            // If provider was not previously ready, update the
2115                                            // status and UI flag.
2116
2117                                            // Id would be valid only if the widget restore broadcast was received.
2118                                            if (isIdValid) {
2119                                                status = LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
2120                                            } else {
2121                                                status &= ~LauncherAppWidgetInfo
2122                                                        .FLAG_PROVIDER_NOT_READY;
2123                                            }
2124                                        }
2125                                        appWidgetInfo.restoreStatus = status;
2126                                    } else {
2127                                        Log.v(TAG, "Widget restore pending id=" + id
2128                                                + " appWidgetId=" + appWidgetId
2129                                                + " status =" + restoreStatus);
2130                                        appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
2131                                                component);
2132                                        appWidgetInfo.restoreStatus = restoreStatus;
2133                                        Integer installProgress = installingPkgs.get(component.getPackageName());
2134
2135                                        if ((restoreStatus & LauncherAppWidgetInfo.FLAG_RESTORE_STARTED) != 0) {
2136                                            // Restore has started once.
2137                                        } else if (installProgress != null) {
2138                                            // App restore has started. Update the flag
2139                                            appWidgetInfo.restoreStatus |=
2140                                                    LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
2141                                        } else if (REMOVE_UNRESTORED_ICONS && !isSafeMode) {
2142                                            FileLog.d(TAG, "Unrestored widget removed: " + component);
2143                                            itemsToRemove.add(id);
2144                                            continue;
2145                                        }
2146
2147                                        appWidgetInfo.installProgress =
2148                                                installProgress == null ? 0 : installProgress;
2149                                    }
2150
2151                                    appWidgetInfo.id = id;
2152                                    appWidgetInfo.screenId = c.getInt(screenIndex);
2153                                    appWidgetInfo.cellX = c.getInt(cellXIndex);
2154                                    appWidgetInfo.cellY = c.getInt(cellYIndex);
2155                                    appWidgetInfo.spanX = c.getInt(spanXIndex);
2156                                    appWidgetInfo.spanY = c.getInt(spanYIndex);
2157                                    appWidgetInfo.user = user;
2158
2159                                    if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2160                                        container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2161                                        Log.e(TAG, "Widget found where container != " +
2162                                                "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
2163                                        itemsToRemove.add(id);
2164                                        continue;
2165                                    }
2166
2167                                    appWidgetInfo.container = container;
2168                                    // check & update map of what's occupied
2169                                    if (!checkItemPlacement(occupied, appWidgetInfo, sBgWorkspaceScreens)) {
2170                                        itemsToRemove.add(id);
2171                                        break;
2172                                    }
2173
2174                                    if (!customWidget) {
2175                                        String providerName =
2176                                                appWidgetInfo.providerName.flattenToString();
2177                                        if (!providerName.equals(savedProvider) ||
2178                                                (appWidgetInfo.restoreStatus != restoreStatus)) {
2179                                            ContentValues values = new ContentValues();
2180                                            values.put(
2181                                                    LauncherSettings.Favorites.APPWIDGET_PROVIDER,
2182                                                    providerName);
2183                                            values.put(LauncherSettings.Favorites.RESTORED,
2184                                                    appWidgetInfo.restoreStatus);
2185                                            updateItem(id, values);
2186                                        }
2187                                    }
2188                                    sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
2189                                    sBgAppWidgets.add(appWidgetInfo);
2190                                }
2191                                break;
2192                            }
2193                        } catch (Exception e) {
2194                            Log.e(TAG, "Desktop items loading interrupted", e);
2195                        }
2196                    }
2197                } finally {
2198                    Utilities.closeSilently(c);
2199                }
2200
2201                // Break early if we've stopped loading
2202                if (mStopped) {
2203                    clearSBgDataStructures();
2204                    return;
2205                }
2206
2207                if (itemsToRemove.size() > 0) {
2208                    // Remove dead items
2209                    contentResolver.delete(LauncherSettings.Favorites.CONTENT_URI,
2210                            Utilities.createDbSelectionQuery(
2211                                    LauncherSettings.Favorites._ID, itemsToRemove), null);
2212                    if (DEBUG_LOADERS) {
2213                        Log.d(TAG, "Removed = " + Utilities.createDbSelectionQuery(
2214                                LauncherSettings.Favorites._ID, itemsToRemove));
2215                    }
2216
2217                    // Remove any empty folder
2218                    ArrayList<Long> deletedFolderIds = (ArrayList<Long>) LauncherSettings.Settings
2219                            .call(contentResolver,
2220                                    LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS)
2221                            .getSerializable(LauncherSettings.Settings.EXTRA_VALUE);
2222                    for (long folderId : deletedFolderIds) {
2223                        sBgWorkspaceItems.remove(sBgFolders.get(folderId));
2224                        sBgFolders.remove(folderId);
2225                        sBgItemsIdMap.remove(folderId);
2226                    }
2227                }
2228
2229                // Unpin shortcuts that don't exist on the workspace.
2230                for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) {
2231                    MutableInt numTimesPinned = sBgPinnedShortcutCounts.get(key);
2232                    if (numTimesPinned == null || numTimesPinned.value == 0) {
2233                        // Shortcut is pinned but doesn't exist on the workspace; unpin it.
2234                        mDeepShortcutManager.unpinShortcut(key);
2235                    }
2236                }
2237
2238                // Sort all the folder items and make sure the first 3 items are high resolution.
2239                for (FolderInfo folder : sBgFolders) {
2240                    Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
2241                    int pos = 0;
2242                    for (ShortcutInfo info : folder.contents) {
2243                        if (info.usingLowResIcon) {
2244                            info.updateIcon(mIconCache, false);
2245                        }
2246                        pos ++;
2247                        if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
2248                            break;
2249                        }
2250                    }
2251                }
2252
2253                if (restoredRows.size() > 0) {
2254                    // Update restored items that no longer require special handling
2255                    ContentValues values = new ContentValues();
2256                    values.put(LauncherSettings.Favorites.RESTORED, 0);
2257                    contentResolver.update(LauncherSettings.Favorites.CONTENT_URI, values,
2258                            Utilities.createDbSelectionQuery(
2259                                    LauncherSettings.Favorites._ID, restoredRows), null);
2260                }
2261
2262                if (!isSdCardReady && !sPendingPackages.isEmpty()) {
2263                    context.registerReceiver(new AppsAvailabilityCheck(),
2264                            new IntentFilter(Intent.ACTION_BOOT_COMPLETED),
2265                            null, sWorker);
2266                }
2267
2268                // Remove any empty screens
2269                ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
2270                for (ItemInfo item: sBgItemsIdMap) {
2271                    long screenId = item.screenId;
2272                    if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2273                            unusedScreens.contains(screenId)) {
2274                        unusedScreens.remove(screenId);
2275                    }
2276                }
2277
2278                // If there are any empty screens remove them, and update.
2279                if (unusedScreens.size() != 0) {
2280                    sBgWorkspaceScreens.removeAll(unusedScreens);
2281                    updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
2282                }
2283
2284                if (DEBUG_LOADERS) {
2285                    Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
2286                    Log.d(TAG, "workspace layout: ");
2287                    int nScreens = occupied.size();
2288                    for (int y = 0; y < countY; y++) {
2289                        String line = "";
2290
2291                        for (int i = 0; i < nScreens; i++) {
2292                            long screenId = occupied.keyAt(i);
2293                            if (screenId > 0) {
2294                                line += " | ";
2295                            }
2296                        }
2297                        Log.d(TAG, "[ " + line + " ]");
2298                    }
2299                }
2300            }
2301            if (LauncherAppState.PROFILE_STARTUP) {
2302                Trace.endSection();
2303            }
2304        }
2305
2306        /**
2307         * Partially updates the item without any notification. Must be called on the worker thread.
2308         */
2309        private void updateItem(long itemId, ContentValues update) {
2310            mContext.getContentResolver().update(
2311                    LauncherSettings.Favorites.CONTENT_URI,
2312                    update,
2313                    BaseColumns._ID + "= ?",
2314                    new String[]{Long.toString(itemId)});
2315        }
2316
2317        /** Filters the set of items who are directly or indirectly (via another container) on the
2318         * specified screen. */
2319        private void filterCurrentWorkspaceItems(long currentScreenId,
2320                ArrayList<ItemInfo> allWorkspaceItems,
2321                ArrayList<ItemInfo> currentScreenItems,
2322                ArrayList<ItemInfo> otherScreenItems) {
2323            // Purge any null ItemInfos
2324            Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
2325            while (iter.hasNext()) {
2326                ItemInfo i = iter.next();
2327                if (i == null) {
2328                    iter.remove();
2329                }
2330            }
2331
2332            // Order the set of items by their containers first, this allows use to walk through the
2333            // list sequentially, build up a list of containers that are in the specified screen,
2334            // as well as all items in those containers.
2335            Set<Long> itemsOnScreen = new HashSet<Long>();
2336            Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
2337                @Override
2338                public int compare(ItemInfo lhs, ItemInfo rhs) {
2339                    return Utilities.longCompare(lhs.container, rhs.container);
2340                }
2341            });
2342            for (ItemInfo info : allWorkspaceItems) {
2343                if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2344                    if (info.screenId == currentScreenId) {
2345                        currentScreenItems.add(info);
2346                        itemsOnScreen.add(info.id);
2347                    } else {
2348                        otherScreenItems.add(info);
2349                    }
2350                } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2351                    currentScreenItems.add(info);
2352                    itemsOnScreen.add(info.id);
2353                } else {
2354                    if (itemsOnScreen.contains(info.container)) {
2355                        currentScreenItems.add(info);
2356                        itemsOnScreen.add(info.id);
2357                    } else {
2358                        otherScreenItems.add(info);
2359                    }
2360                }
2361            }
2362        }
2363
2364        /** Filters the set of widgets which are on the specified screen. */
2365        private void filterCurrentAppWidgets(long currentScreenId,
2366                ArrayList<LauncherAppWidgetInfo> appWidgets,
2367                ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
2368                ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
2369
2370            for (LauncherAppWidgetInfo widget : appWidgets) {
2371                if (widget == null) continue;
2372                if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2373                        widget.screenId == currentScreenId) {
2374                    currentScreenWidgets.add(widget);
2375                } else {
2376                    otherScreenWidgets.add(widget);
2377                }
2378            }
2379        }
2380
2381        /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
2382         * right) */
2383        private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
2384            final LauncherAppState app = LauncherAppState.getInstance();
2385            final InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
2386            final int screenCols = profile.numColumns;
2387            final int screenCellCount = profile.numColumns * profile.numRows;
2388            Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
2389                @Override
2390                public int compare(ItemInfo lhs, ItemInfo rhs) {
2391                    if (lhs.container == rhs.container) {
2392                        // Within containers, order by their spatial position in that container
2393                        switch ((int) lhs.container) {
2394                            case LauncherSettings.Favorites.CONTAINER_DESKTOP: {
2395                                long lr = (lhs.screenId * screenCellCount +
2396                                        lhs.cellY * screenCols + lhs.cellX);
2397                                long rr = (rhs.screenId * screenCellCount +
2398                                        rhs.cellY * screenCols + rhs.cellX);
2399                                return Utilities.longCompare(lr, rr);
2400                            }
2401                            case LauncherSettings.Favorites.CONTAINER_HOTSEAT: {
2402                                // We currently use the screen id as the rank
2403                                return Utilities.longCompare(lhs.screenId, rhs.screenId);
2404                            }
2405                            default:
2406                                if (ProviderConfig.IS_DOGFOOD_BUILD) {
2407                                    throw new RuntimeException("Unexpected container type when " +
2408                                            "sorting workspace items.");
2409                                }
2410                                return 0;
2411                        }
2412                    } else {
2413                        // Between containers, order by hotseat, desktop
2414                        return Utilities.longCompare(lhs.container, rhs.container);
2415                    }
2416                }
2417            });
2418        }
2419
2420        private void bindWorkspaceScreens(final Callbacks oldCallbacks,
2421                final ArrayList<Long> orderedScreens) {
2422            final Runnable r = new Runnable() {
2423                @Override
2424                public void run() {
2425                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2426                    if (callbacks != null) {
2427                        callbacks.bindScreens(orderedScreens);
2428                    }
2429                }
2430            };
2431            runOnMainThread(r);
2432        }
2433
2434        private void bindWorkspaceItems(final Callbacks oldCallbacks,
2435                final ArrayList<ItemInfo> workspaceItems,
2436                final ArrayList<LauncherAppWidgetInfo> appWidgets,
2437                final Executor executor) {
2438
2439            // Bind the workspace items
2440            int N = workspaceItems.size();
2441            for (int i = 0; i < N; i += ITEMS_CHUNK) {
2442                final int start = i;
2443                final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
2444                final Runnable r = new Runnable() {
2445                    @Override
2446                    public void run() {
2447                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2448                        if (callbacks != null) {
2449                            callbacks.bindItems(workspaceItems, start, start+chunkSize,
2450                                    false);
2451                        }
2452                    }
2453                };
2454                executor.execute(r);
2455            }
2456
2457            // Bind the widgets, one at a time
2458            N = appWidgets.size();
2459            for (int i = 0; i < N; i++) {
2460                final LauncherAppWidgetInfo widget = appWidgets.get(i);
2461                final Runnable r = new Runnable() {
2462                    public void run() {
2463                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2464                        if (callbacks != null) {
2465                            callbacks.bindAppWidget(widget);
2466                        }
2467                    }
2468                };
2469                executor.execute(r);
2470            }
2471        }
2472
2473        /**
2474         * Binds all loaded data to actual views on the main thread.
2475         */
2476        private void bindWorkspace(int synchronizeBindPage) {
2477            final long t = SystemClock.uptimeMillis();
2478            Runnable r;
2479
2480            // Don't use these two variables in any of the callback runnables.
2481            // Otherwise we hold a reference to them.
2482            final Callbacks oldCallbacks = mCallbacks.get();
2483            if (oldCallbacks == null) {
2484                // This launcher has exited and nobody bothered to tell us.  Just bail.
2485                Log.w(TAG, "LoaderTask running with no launcher");
2486                return;
2487            }
2488
2489            // Save a copy of all the bg-thread collections
2490            ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
2491            ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
2492            ArrayList<Long> orderedScreenIds = new ArrayList<>();
2493
2494            synchronized (sBgLock) {
2495                workspaceItems.addAll(sBgWorkspaceItems);
2496                appWidgets.addAll(sBgAppWidgets);
2497                orderedScreenIds.addAll(sBgWorkspaceScreens);
2498            }
2499
2500            final int currentScreen;
2501            {
2502                int currScreen = synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE
2503                        ? synchronizeBindPage : oldCallbacks.getCurrentWorkspaceScreen();
2504                if (currScreen >= orderedScreenIds.size()) {
2505                    // There may be no workspace screens (just hotseat items and an empty page).
2506                    currScreen = PagedView.INVALID_RESTORE_PAGE;
2507                }
2508                currentScreen = currScreen;
2509            }
2510            final boolean validFirstPage = currentScreen >= 0;
2511            final long currentScreenId =
2512                    validFirstPage ? orderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID;
2513
2514            // Separate the items that are on the current screen, and all the other remaining items
2515            ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
2516            ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
2517            ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
2518            ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
2519
2520            filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
2521                    otherWorkspaceItems);
2522            filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
2523                    otherAppWidgets);
2524            sortWorkspaceItemsSpatially(currentWorkspaceItems);
2525            sortWorkspaceItemsSpatially(otherWorkspaceItems);
2526
2527            // Tell the workspace that we're about to start binding items
2528            r = new Runnable() {
2529                public void run() {
2530                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2531                    if (callbacks != null) {
2532                        callbacks.clearPendingBinds();
2533                        callbacks.startBinding();
2534                    }
2535                }
2536            };
2537            runOnMainThread(r);
2538
2539            bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
2540
2541            Executor mainExecutor = new DeferredMainThreadExecutor();
2542            // Load items on the current page.
2543            bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, mainExecutor);
2544
2545            // In case of validFirstPage, only bind the first screen, and defer binding the
2546            // remaining screens after first onDraw (and an optional the fade animation whichever
2547            // happens later).
2548            // This ensures that the first screen is immediately visible (eg. during rotation)
2549            // In case of !validFirstPage, bind all pages one after other.
2550            final Executor deferredExecutor =
2551                    validFirstPage ? new ViewOnDrawExecutor(mHandler) : mainExecutor;
2552
2553            mainExecutor.execute(new Runnable() {
2554                @Override
2555                public void run() {
2556                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2557                    if (callbacks != null) {
2558                        callbacks.finishFirstPageBind(
2559                                validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null);
2560                    }
2561                }
2562            });
2563
2564            bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, deferredExecutor);
2565
2566            // Tell the workspace that we're done binding items
2567            r = new Runnable() {
2568                public void run() {
2569                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2570                    if (callbacks != null) {
2571                        callbacks.finishBindingItems();
2572                    }
2573
2574                    mIsLoadingAndBindingWorkspace = false;
2575
2576                    // Run all the bind complete runnables after workspace is bound.
2577                    if (!mBindCompleteRunnables.isEmpty()) {
2578                        synchronized (mBindCompleteRunnables) {
2579                            for (final Runnable r : mBindCompleteRunnables) {
2580                                runOnWorkerThread(r);
2581                            }
2582                            mBindCompleteRunnables.clear();
2583                        }
2584                    }
2585
2586                    // If we're profiling, ensure this is the last thing in the queue.
2587                    if (DEBUG_LOADERS) {
2588                        Log.d(TAG, "bound workspace in "
2589                            + (SystemClock.uptimeMillis()-t) + "ms");
2590                    }
2591
2592                }
2593            };
2594            deferredExecutor.execute(r);
2595
2596            if (validFirstPage) {
2597                r = new Runnable() {
2598                    public void run() {
2599                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2600                        if (callbacks != null) {
2601                            // We are loading synchronously, which means, some of the pages will be
2602                            // bound after first draw. Inform the callbacks that page binding is
2603                            // not complete, and schedule the remaining pages.
2604                            if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
2605                                callbacks.onPageBoundSynchronously(currentScreen);
2606                            }
2607                            callbacks.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor);
2608                        }
2609                    }
2610                };
2611                runOnMainThread(r);
2612            }
2613        }
2614
2615        private void loadAndBindAllApps() {
2616            if (DEBUG_LOADERS) {
2617                Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
2618            }
2619            if (!mAllAppsLoaded) {
2620                loadAllApps();
2621                synchronized (LoaderTask.this) {
2622                    if (mStopped) {
2623                        return;
2624                    }
2625                }
2626                updateIconCache();
2627                synchronized (LoaderTask.this) {
2628                    if (mStopped) {
2629                        return;
2630                    }
2631                    mAllAppsLoaded = true;
2632                }
2633            } else {
2634                onlyBindAllApps();
2635            }
2636        }
2637
2638        private void updateIconCache() {
2639            // Ignore packages which have a promise icon.
2640            HashSet<String> packagesToIgnore = new HashSet<>();
2641            synchronized (sBgLock) {
2642                for (ItemInfo info : sBgItemsIdMap) {
2643                    if (info instanceof ShortcutInfo) {
2644                        ShortcutInfo si = (ShortcutInfo) info;
2645                        if (si.isPromise() && si.getTargetComponent() != null) {
2646                            packagesToIgnore.add(si.getTargetComponent().getPackageName());
2647                        }
2648                    } else if (info instanceof LauncherAppWidgetInfo) {
2649                        LauncherAppWidgetInfo lawi = (LauncherAppWidgetInfo) info;
2650                        if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
2651                            packagesToIgnore.add(lawi.providerName.getPackageName());
2652                        }
2653                    }
2654                }
2655            }
2656            mIconCache.updateDbIcons(packagesToIgnore);
2657        }
2658
2659        private void onlyBindAllApps() {
2660            final Callbacks oldCallbacks = mCallbacks.get();
2661            if (oldCallbacks == null) {
2662                // This launcher has exited and nobody bothered to tell us.  Just bail.
2663                Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
2664                return;
2665            }
2666
2667            // shallow copy
2668            @SuppressWarnings("unchecked")
2669            final ArrayList<AppInfo> list
2670                    = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
2671            Runnable r = new Runnable() {
2672                public void run() {
2673                    final long t = SystemClock.uptimeMillis();
2674                    final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2675                    if (callbacks != null) {
2676                        callbacks.bindAllApplications(list);
2677                    }
2678                    if (DEBUG_LOADERS) {
2679                        Log.d(TAG, "bound all " + list.size() + " apps from cache in "
2680                                + (SystemClock.uptimeMillis() - t) + "ms");
2681                    }
2682                }
2683            };
2684            runOnMainThread(r);
2685        }
2686
2687        private void loadAllApps() {
2688            final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2689
2690            final Callbacks oldCallbacks = mCallbacks.get();
2691            if (oldCallbacks == null) {
2692                // This launcher has exited and nobody bothered to tell us.  Just bail.
2693                Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
2694                return;
2695            }
2696
2697            final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
2698
2699            // Clear the list of apps
2700            mBgAllAppsList.clear();
2701            for (UserHandleCompat user : profiles) {
2702                // Query for the set of apps
2703                final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2704                final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
2705                if (DEBUG_LOADERS) {
2706                    Log.d(TAG, "getActivityList took "
2707                            + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
2708                    Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
2709                }
2710                // Fail if we don't have any apps
2711                // TODO: Fix this. Only fail for the current user.
2712                if (apps == null || apps.isEmpty()) {
2713                    return;
2714                }
2715                boolean quietMode = mUserManager.isQuietModeEnabled(user);
2716                // Create the ApplicationInfos
2717                for (int i = 0; i < apps.size(); i++) {
2718                    LauncherActivityInfoCompat app = apps.get(i);
2719                    // This builds the icon bitmaps.
2720                    mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache, quietMode));
2721                }
2722
2723                final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
2724                if (heuristic != null) {
2725                    final Runnable r = new Runnable() {
2726
2727                        @Override
2728                        public void run() {
2729                            heuristic.processUserApps(apps);
2730                        }
2731                    };
2732                    runOnMainThread(new Runnable() {
2733
2734                        @Override
2735                        public void run() {
2736                            // Check isLoadingWorkspace on the UI thread, as it is updated on
2737                            // the UI thread.
2738                            if (mIsLoadingAndBindingWorkspace) {
2739                                synchronized (mBindCompleteRunnables) {
2740                                    mBindCompleteRunnables.add(r);
2741                                }
2742                            } else {
2743                                runOnWorkerThread(r);
2744                            }
2745                        }
2746                    });
2747                }
2748            }
2749            // Huh? Shouldn't this be inside the Runnable below?
2750            final ArrayList<AppInfo> added = mBgAllAppsList.added;
2751            mBgAllAppsList.added = new ArrayList<AppInfo>();
2752
2753            // Post callback on main thread
2754            mHandler.post(new Runnable() {
2755                public void run() {
2756
2757                    final long bindTime = SystemClock.uptimeMillis();
2758                    final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2759                    if (callbacks != null) {
2760                        callbacks.bindAllApplications(added);
2761                        if (DEBUG_LOADERS) {
2762                            Log.d(TAG, "bound " + added.size() + " apps in "
2763                                    + (SystemClock.uptimeMillis() - bindTime) + "ms");
2764                        }
2765                    } else {
2766                        Log.i(TAG, "not binding apps: no Launcher activity");
2767                    }
2768                }
2769            });
2770            // Cleanup any data stored for a deleted user.
2771            ManagedProfileHeuristic.processAllUsers(profiles, mContext);
2772            if (DEBUG_LOADERS) {
2773                Log.d(TAG, "Icons processed in "
2774                        + (SystemClock.uptimeMillis() - loadTime) + "ms");
2775            }
2776        }
2777
2778        private void loadAndBindDeepShortcuts() {
2779            if (DEBUG_LOADERS) {
2780                Log.d(TAG, "loadAndBindDeepShortcuts mDeepShortcutsLoaded=" + mDeepShortcutsLoaded);
2781            }
2782            if (!mDeepShortcutsLoaded) {
2783                mBgDeepShortcutMap.clear();
2784                for (UserHandleCompat user : mUserManager.getUserProfiles()) {
2785                    if (mUserManager.isUserUnlocked(user)) {
2786                        List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager
2787                                .queryForAllShortcuts(user);
2788                        updateDeepShortcutMap(null, user, shortcuts);
2789                    }
2790                }
2791                synchronized (LoaderTask.this) {
2792                    if (mStopped) {
2793                        return;
2794                    }
2795                    mDeepShortcutsLoaded = true;
2796                }
2797            }
2798            bindDeepShortcuts();
2799        }
2800
2801        public void dumpState() {
2802            synchronized (sBgLock) {
2803                Log.d(TAG, "mLoaderTask.mContext=" + mContext);
2804                Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
2805                Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
2806                Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
2807            }
2808        }
2809    }
2810
2811    /**
2812     * Clear all the shortcuts for the given package, and re-add the new shortcuts.
2813     */
2814    private void updateDeepShortcutMap(
2815            String packageName, UserHandleCompat user, List<ShortcutInfoCompat> shortcuts) {
2816        if (packageName != null) {
2817            Iterator<ComponentKey> keysIter = mBgDeepShortcutMap.keySet().iterator();
2818            while (keysIter.hasNext()) {
2819                ComponentKey next = keysIter.next();
2820                if (next.componentName.getPackageName().equals(packageName)
2821                        && next.user.equals(user)) {
2822                    keysIter.remove();
2823                }
2824            }
2825        }
2826
2827        // Now add the new shortcuts to the map.
2828        for (ShortcutInfoCompat shortcut : shortcuts) {
2829            boolean shouldShowInContainer = shortcut.isEnabled()
2830                    && (shortcut.isDeclaredInManifest() || shortcut.isDynamic());
2831            if (shouldShowInContainer) {
2832                ComponentKey targetComponent
2833                        = new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
2834                mBgDeepShortcutMap.addToList(targetComponent, shortcut.getId());
2835            }
2836        }
2837    }
2838
2839    public void bindDeepShortcuts() {
2840        final MultiHashMap<ComponentKey, String> shortcutMapCopy = new MultiHashMap<>();
2841        shortcutMapCopy.putAll(mBgDeepShortcutMap);
2842        Runnable r = new Runnable() {
2843            @Override
2844            public void run() {
2845                Callbacks callbacks = getCallback();
2846                if (callbacks != null) {
2847                    callbacks.bindDeepShortcutMap(shortcutMapCopy);
2848                }
2849            }
2850        };
2851        runOnMainThread(r);
2852    }
2853
2854    /**
2855     * Called when the icons for packages have been updated in the icon cache.
2856     */
2857    public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandleCompat user) {
2858        final Callbacks callbacks = getCallback();
2859        final ArrayList<AppInfo> updatedApps = new ArrayList<>();
2860        final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
2861
2862        // If any package icon has changed (app was updated while launcher was dead),
2863        // update the corresponding shortcuts.
2864        synchronized (sBgLock) {
2865            for (ItemInfo info : sBgItemsIdMap) {
2866                if (info instanceof ShortcutInfo && user.equals(info.user)
2867                        && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
2868                    ShortcutInfo si = (ShortcutInfo) info;
2869                    ComponentName cn = si.getTargetComponent();
2870                    if (cn != null && updatedPackages.contains(cn.getPackageName())) {
2871                        si.updateIcon(mIconCache);
2872                        updatedShortcuts.add(si);
2873                    }
2874                }
2875            }
2876            mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps);
2877        }
2878
2879        bindUpdatedShortcuts(updatedShortcuts, user);
2880
2881        if (!updatedApps.isEmpty()) {
2882            mHandler.post(new Runnable() {
2883
2884                public void run() {
2885                    Callbacks cb = getCallback();
2886                    if (cb != null && callbacks == cb) {
2887                        cb.bindAppsUpdated(updatedApps);
2888                    }
2889                }
2890            });
2891        }
2892    }
2893
2894    private void bindUpdatedShortcuts(
2895            ArrayList<ShortcutInfo> updatedShortcuts, UserHandleCompat user) {
2896        bindUpdatedShortcuts(updatedShortcuts, new ArrayList<ShortcutInfo>(), user);
2897    }
2898
2899    private void bindUpdatedShortcuts(
2900            final ArrayList<ShortcutInfo> updatedShortcuts,
2901            final ArrayList<ShortcutInfo> removedShortcuts,
2902            final UserHandleCompat user) {
2903        if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) {
2904            final Callbacks callbacks = getCallback();
2905            mHandler.post(new Runnable() {
2906
2907                public void run() {
2908                    Callbacks cb = getCallback();
2909                    if (cb != null && callbacks == cb) {
2910                        cb.bindShortcutsChanged(updatedShortcuts, removedShortcuts, user);
2911                    }
2912                }
2913            });
2914        }
2915    }
2916
2917    void enqueueItemUpdatedTask(Runnable task) {
2918        sWorker.post(task);
2919    }
2920
2921    @Thunk class AppsAvailabilityCheck extends BroadcastReceiver {
2922
2923        @Override
2924        public void onReceive(Context context, Intent intent) {
2925            synchronized (sBgLock) {
2926                final LauncherAppsCompat launcherApps = LauncherAppsCompat
2927                        .getInstance(mApp.getContext());
2928                final PackageManager manager = context.getPackageManager();
2929                final ArrayList<String> packagesRemoved = new ArrayList<String>();
2930                final ArrayList<String> packagesUnavailable = new ArrayList<String>();
2931                for (Entry<UserHandleCompat, HashSet<String>> entry : sPendingPackages.entrySet()) {
2932                    UserHandleCompat user = entry.getKey();
2933                    packagesRemoved.clear();
2934                    packagesUnavailable.clear();
2935                    for (String pkg : entry.getValue()) {
2936                        if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
2937                            if (PackageManagerHelper.isAppOnSdcard(manager, pkg)) {
2938                                packagesUnavailable.add(pkg);
2939                            } else {
2940                                packagesRemoved.add(pkg);
2941                            }
2942                        }
2943                    }
2944                    if (!packagesRemoved.isEmpty()) {
2945                        enqueueItemUpdatedTask(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE,
2946                                packagesRemoved.toArray(new String[packagesRemoved.size()]), user));
2947                    }
2948                    if (!packagesUnavailable.isEmpty()) {
2949                        enqueueItemUpdatedTask(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE,
2950                                packagesUnavailable.toArray(new String[packagesUnavailable.size()]), user));
2951                    }
2952                }
2953                sPendingPackages.clear();
2954            }
2955        }
2956    }
2957
2958    private class PackageUpdatedTask implements Runnable {
2959        int mOp;
2960        String[] mPackages;
2961        UserHandleCompat mUser;
2962
2963        public static final int OP_NONE = 0;
2964        public static final int OP_ADD = 1;
2965        public static final int OP_UPDATE = 2;
2966        public static final int OP_REMOVE = 3; // uninstlled
2967        public static final int OP_UNAVAILABLE = 4; // external media unmounted
2968        public static final int OP_SUSPEND = 5; // package suspended
2969        public static final int OP_UNSUSPEND = 6; // package unsuspended
2970        public static final int OP_USER_AVAILABILITY_CHANGE = 7; // user available/unavailable
2971
2972        public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) {
2973            mOp = op;
2974            mPackages = packages;
2975            mUser = user;
2976        }
2977
2978        public void run() {
2979            if (!mHasLoaderCompletedOnce) {
2980                // Loader has not yet run.
2981                return;
2982            }
2983            final Context context = mApp.getContext();
2984
2985            final String[] packages = mPackages;
2986            final int N = packages.length;
2987            FlagOp flagOp = FlagOp.NO_OP;
2988            StringFilter pkgFilter = StringFilter.of(new HashSet<>(Arrays.asList(packages)));
2989            switch (mOp) {
2990                case OP_ADD: {
2991                    for (int i=0; i<N; i++) {
2992                        if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
2993                        mIconCache.updateIconsForPkg(packages[i], mUser);
2994                        mBgAllAppsList.addPackage(context, packages[i], mUser);
2995                    }
2996
2997                    ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
2998                    if (heuristic != null) {
2999                        heuristic.processPackageAdd(mPackages);
3000                    }
3001                    break;
3002                }
3003                case OP_UPDATE:
3004                    for (int i=0; i<N; i++) {
3005                        if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
3006                        mIconCache.updateIconsForPkg(packages[i], mUser);
3007                        mBgAllAppsList.updatePackage(context, packages[i], mUser);
3008                        mApp.getWidgetCache().removePackage(packages[i], mUser);
3009                    }
3010                    // Since package was just updated, the target must be available now.
3011                    flagOp = FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
3012                    break;
3013                case OP_REMOVE: {
3014                    ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
3015                    if (heuristic != null) {
3016                        heuristic.processPackageRemoved(mPackages);
3017                    }
3018                    for (int i=0; i<N; i++) {
3019                        if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
3020                        mIconCache.removeIconsForPkg(packages[i], mUser);
3021                    }
3022                    // Fall through
3023                }
3024                case OP_UNAVAILABLE:
3025                    for (int i=0; i<N; i++) {
3026                        if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
3027                        mBgAllAppsList.removePackage(packages[i], mUser);
3028                        mApp.getWidgetCache().removePackage(packages[i], mUser);
3029                    }
3030                    flagOp = FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
3031                    break;
3032                case OP_SUSPEND:
3033                case OP_UNSUSPEND:
3034                    flagOp = mOp == OP_SUSPEND ?
3035                            FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED) :
3036                                    FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED);
3037                    if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.(un)suspend " + N);
3038                    mBgAllAppsList.updatePackageFlags(pkgFilter, mUser, flagOp);
3039                    break;
3040                case OP_USER_AVAILABILITY_CHANGE:
3041                    flagOp = UserManagerCompat.getInstance(context).isQuietModeEnabled(mUser)
3042                            ? FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER)
3043                            : FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER);
3044                    // We want to update all packages for this user.
3045                    pkgFilter = StringFilter.matchesAll();
3046                    mBgAllAppsList.updatePackageFlags(pkgFilter, mUser, flagOp);
3047                    break;
3048            }
3049
3050            ArrayList<AppInfo> added = null;
3051            ArrayList<AppInfo> modified = null;
3052            final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
3053
3054            if (mBgAllAppsList.added.size() > 0) {
3055                added = new ArrayList<>(mBgAllAppsList.added);
3056                mBgAllAppsList.added.clear();
3057            }
3058            if (mBgAllAppsList.modified.size() > 0) {
3059                modified = new ArrayList<>(mBgAllAppsList.modified);
3060                mBgAllAppsList.modified.clear();
3061            }
3062            if (mBgAllAppsList.removed.size() > 0) {
3063                removedApps.addAll(mBgAllAppsList.removed);
3064                mBgAllAppsList.removed.clear();
3065            }
3066
3067            final HashMap<ComponentName, AppInfo> addedOrUpdatedApps = new HashMap<>();
3068
3069            if (added != null) {
3070                addAppsToAllApps(context, added);
3071                for (AppInfo ai : added) {
3072                    addedOrUpdatedApps.put(ai.componentName, ai);
3073                }
3074            }
3075
3076            if (modified != null) {
3077                final Callbacks callbacks = getCallback();
3078                final ArrayList<AppInfo> modifiedFinal = modified;
3079                for (AppInfo ai : modified) {
3080                    addedOrUpdatedApps.put(ai.componentName, ai);
3081                }
3082
3083                mHandler.post(new Runnable() {
3084                    public void run() {
3085                        Callbacks cb = getCallback();
3086                        if (callbacks == cb && cb != null) {
3087                            callbacks.bindAppsUpdated(modifiedFinal);
3088                        }
3089                    }
3090                });
3091            }
3092
3093            // Update shortcut infos
3094            if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
3095                final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<ShortcutInfo>();
3096                final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<ShortcutInfo>();
3097                final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<LauncherAppWidgetInfo>();
3098
3099                synchronized (sBgLock) {
3100                    for (ItemInfo info : sBgItemsIdMap) {
3101                        if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
3102                            ShortcutInfo si = (ShortcutInfo) info;
3103                            boolean infoUpdated = false;
3104                            boolean shortcutUpdated = false;
3105
3106                            // Update shortcuts which use iconResource.
3107                            if ((si.iconResource != null)
3108                                    && pkgFilter.matches(si.iconResource.packageName)) {
3109                                Bitmap icon = Utilities.createIconBitmap(
3110                                        si.iconResource.packageName,
3111                                        si.iconResource.resourceName, context);
3112                                if (icon != null) {
3113                                    si.setIcon(icon);
3114                                    si.usingFallbackIcon = false;
3115                                    infoUpdated = true;
3116                                }
3117                            }
3118
3119                            ComponentName cn = si.getTargetComponent();
3120                            if (cn != null && pkgFilter.matches(cn.getPackageName())) {
3121                                AppInfo appInfo = addedOrUpdatedApps.get(cn);
3122
3123                                if (si.isPromise()) {
3124                                    if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
3125                                        // Auto install icon
3126                                        PackageManager pm = context.getPackageManager();
3127                                        ResolveInfo matched = pm.resolveActivity(
3128                                                new Intent(Intent.ACTION_MAIN)
3129                                                .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
3130                                                PackageManager.MATCH_DEFAULT_ONLY);
3131                                        if (matched == null) {
3132                                            // Try to find the best match activity.
3133                                            Intent intent = pm.getLaunchIntentForPackage(
3134                                                    cn.getPackageName());
3135                                            if (intent != null) {
3136                                                cn = intent.getComponent();
3137                                                appInfo = addedOrUpdatedApps.get(cn);
3138                                            }
3139
3140                                            if ((intent == null) || (appInfo == null)) {
3141                                                removedShortcuts.add(si);
3142                                                continue;
3143                                            }
3144                                            si.promisedIntent = intent;
3145                                        }
3146                                    }
3147
3148                                    // Restore the shortcut.
3149                                    if (appInfo != null) {
3150                                        si.flags = appInfo.flags;
3151                                    }
3152
3153                                    si.intent = si.promisedIntent;
3154                                    si.promisedIntent = null;
3155                                    si.status = ShortcutInfo.DEFAULT;
3156                                    infoUpdated = true;
3157                                    si.updateIcon(mIconCache);
3158                                }
3159
3160                                if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
3161                                        && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
3162                                    si.updateIcon(mIconCache);
3163                                    si.title = Utilities.trim(appInfo.title);
3164                                    si.contentDescription = appInfo.contentDescription;
3165                                    infoUpdated = true;
3166                                }
3167
3168                                int oldDisabledFlags = si.isDisabled;
3169                                si.isDisabled = flagOp.apply(si.isDisabled);
3170                                if (si.isDisabled != oldDisabledFlags) {
3171                                    shortcutUpdated = true;
3172                                }
3173                            }
3174
3175                            if (infoUpdated || shortcutUpdated) {
3176                                updatedShortcuts.add(si);
3177                            }
3178                            if (infoUpdated) {
3179                                updateItemInDatabase(context, si);
3180                            }
3181                        } else if (info instanceof LauncherAppWidgetInfo && mOp == OP_ADD) {
3182                            LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
3183                            if (mUser.equals(widgetInfo.user)
3184                                    && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
3185                                    && pkgFilter.matches(widgetInfo.providerName.getPackageName())) {
3186                                widgetInfo.restoreStatus &=
3187                                        ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY &
3188                                        ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
3189
3190                                // adding this flag ensures that launcher shows 'click to setup'
3191                                // if the widget has a config activity. In case there is no config
3192                                // activity, it will be marked as 'restored' during bind.
3193                                widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
3194
3195                                widgets.add(widgetInfo);
3196                                updateItemInDatabase(context, widgetInfo);
3197                            }
3198                        }
3199                    }
3200                }
3201
3202                bindUpdatedShortcuts(updatedShortcuts, removedShortcuts, mUser);
3203                if (!removedShortcuts.isEmpty()) {
3204                    deleteItemsFromDatabase(context, removedShortcuts);
3205                }
3206
3207                if (!widgets.isEmpty()) {
3208                    final Callbacks callbacks = getCallback();
3209                    mHandler.post(new Runnable() {
3210                        public void run() {
3211                            Callbacks cb = getCallback();
3212                            if (callbacks == cb && cb != null) {
3213                                callbacks.bindWidgetsRestored(widgets);
3214                            }
3215                        }
3216                    });
3217                }
3218            }
3219
3220            final HashSet<String> removedPackages = new HashSet<>();
3221            final HashSet<ComponentName> removedComponents = new HashSet<>();
3222            if (mOp == OP_REMOVE) {
3223                // Mark all packages in the broadcast to be removed
3224                Collections.addAll(removedPackages, packages);
3225
3226                // No need to update the removedComponents as
3227                // removedPackages is a super-set of removedComponents
3228            } else if (mOp == OP_UPDATE) {
3229                // Mark disabled packages in the broadcast to be removed
3230                for (int i=0; i<N; i++) {
3231                    if (isPackageDisabled(context, packages[i], mUser)) {
3232                        removedPackages.add(packages[i]);
3233                    }
3234                }
3235
3236                // Update removedComponents as some components can get removed during package update
3237                for (AppInfo info : removedApps) {
3238                    removedComponents.add(info.componentName);
3239                }
3240            }
3241
3242            if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
3243                for (String pn : removedPackages) {
3244                    deletePackageFromDatabase(context, pn, mUser);
3245                }
3246                for (ComponentName cn : removedComponents) {
3247                    deleteItemsFromDatabase(context, getItemInfoForComponentName(cn, mUser));
3248                }
3249
3250                // Remove any queued items from the install queue
3251                InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser);
3252
3253                // Call the components-removed callback
3254                final Callbacks callbacks = getCallback();
3255                mHandler.post(new Runnable() {
3256                    public void run() {
3257                        Callbacks cb = getCallback();
3258                        if (callbacks == cb && cb != null) {
3259                            callbacks.bindWorkspaceComponentsRemoved(
3260                                    removedPackages, removedComponents, mUser);
3261                        }
3262                    }
3263                });
3264            }
3265
3266            if (!removedApps.isEmpty()) {
3267                // Remove corresponding apps from All-Apps
3268                final Callbacks callbacks = getCallback();
3269                mHandler.post(new Runnable() {
3270                    public void run() {
3271                        Callbacks cb = getCallback();
3272                        if (callbacks == cb && cb != null) {
3273                            callbacks.bindAppInfosRemoved(removedApps);
3274                        }
3275                    }
3276                });
3277            }
3278
3279            // Notify launcher of widget update. From marshmallow onwards we use AppWidgetHost to
3280            // get widget update signals.
3281            if (!Utilities.ATLEAST_MARSHMALLOW &&
3282                    (mOp == OP_ADD || mOp == OP_REMOVE || mOp == OP_UPDATE)) {
3283                final Callbacks callbacks = getCallback();
3284                mHandler.post(new Runnable() {
3285                    public void run() {
3286                        Callbacks cb = getCallback();
3287                        if (callbacks == cb && cb != null) {
3288                            callbacks.notifyWidgetProvidersChanged();
3289                        }
3290                    }
3291                });
3292            }
3293        }
3294    }
3295
3296    /**
3297     * Repopulates the shortcut info, possibly updating any icon already on the workspace.
3298     */
3299    public void updateShortcutInfo(final ShortcutInfoCompat fullDetail, final ShortcutInfo info) {
3300        enqueueItemUpdatedTask(new Runnable() {
3301            @Override
3302            public void run() {
3303                info.updateFromDeepShortcutInfo(
3304                        fullDetail, LauncherAppState.getInstance().getContext());
3305                ArrayList<ShortcutInfo> update = new ArrayList<ShortcutInfo>();
3306                update.add(info);
3307                bindUpdatedShortcuts(update, fullDetail.getUserHandle());
3308            }
3309        });
3310    }
3311
3312    private class ShortcutsChangedTask implements Runnable {
3313        private String mPackageName;
3314        private List<ShortcutInfoCompat> mShortcuts;
3315        private UserHandleCompat mUser;
3316
3317        public ShortcutsChangedTask(String packageName, List<ShortcutInfoCompat> shortcuts,
3318                UserHandleCompat user) {
3319            mPackageName = packageName;
3320            mShortcuts = shortcuts;
3321            mUser = user;
3322        }
3323
3324        @Override
3325        public void run() {
3326            mDeepShortcutManager.onShortcutsChanged(mShortcuts);
3327
3328            Map<String, ShortcutInfoCompat> idsToShortcuts = new HashMap<>();
3329            for (ShortcutInfoCompat shortcut : mShortcuts) {
3330                idsToShortcuts.put(shortcut.getId(), shortcut);
3331            }
3332
3333            // Find ShortcutInfo's that have changed on the workspace.
3334            MultiHashMap<String, ShortcutInfo> idsToWorkspaceShortcutInfos = new MultiHashMap<>();
3335            for (ItemInfo itemInfo : sBgItemsIdMap) {
3336                if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
3337                    ShortcutInfo si = (ShortcutInfo) itemInfo;
3338                    if (si.getIntent().getPackage().equals(mPackageName) && si.user.equals(mUser)) {
3339                        String shortcutId = si.getDeepShortcutId();
3340                        if (idsToShortcuts.containsKey(shortcutId)) {
3341                            idsToWorkspaceShortcutInfos.addToList(shortcutId, si);
3342                        }
3343                    }
3344                }
3345            }
3346
3347            // Update the workspace to reflect the changes to updated shortcuts residing on it.
3348            List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager.queryForFullDetails(
3349                    mPackageName, new ArrayList<>(idsToWorkspaceShortcutInfos.keySet()), mUser);
3350            ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
3351            Context context = LauncherAppState.getInstance().getContext();
3352            for (ShortcutInfoCompat fullDetails : shortcuts) {
3353                List<ShortcutInfo> shortcutInfos = idsToWorkspaceShortcutInfos
3354                        .get(fullDetails.getId());
3355                for (ShortcutInfo shortcutInfo : shortcutInfos) {
3356                    shortcutInfo.updateFromDeepShortcutInfo(fullDetails, context);
3357                    updatedShortcutInfos.add(shortcutInfo);
3358                }
3359            }
3360            bindUpdatedShortcuts(updatedShortcutInfos, mUser);
3361
3362            // Update the deep shortcut map, in case the list of ids has changed for an activity.
3363            updateDeepShortcutMap(mPackageName, mUser, mShortcuts);
3364            bindDeepShortcuts();
3365        }
3366    }
3367
3368    /**
3369     * Task to handle changing of lock state of the user
3370     */
3371    private class UserLockStateChangedTask implements Runnable {
3372
3373        private final UserHandleCompat mUser;
3374
3375        public UserLockStateChangedTask(UserHandleCompat user) {
3376            mUser = user;
3377        }
3378
3379        @Override
3380        public void run() {
3381            boolean isUserUnlocked = mUserManager.isUserUnlocked(mUser);
3382            Context context = mApp.getContext();
3383
3384            HashMap<ShortcutKey, ShortcutInfoCompat> pinnedShortcuts = new HashMap<>();
3385            if (isUserUnlocked) {
3386                for (ShortcutInfoCompat shortcut :
3387                        mDeepShortcutManager.queryForPinnedShortcuts(null, mUser)) {
3388                    pinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut);
3389                }
3390            }
3391
3392            // Update the workspace to reflect the changes to updated shortcuts residing on it.
3393            ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
3394            ArrayList<ShortcutInfo> deletedShortcutInfos = new ArrayList<>();
3395            for (ItemInfo itemInfo : sBgItemsIdMap) {
3396                if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
3397                        && mUser.equals(itemInfo.user)) {
3398                    ShortcutInfo si = (ShortcutInfo) itemInfo;
3399                    if (isUserUnlocked) {
3400                        ShortcutInfoCompat shortcut =
3401                                pinnedShortcuts.get(ShortcutKey.fromItemInfo(si));
3402                        // We couldn't verify the shortcut during loader. If its no longer available
3403                        // (probably due to clear data), delete the workspace item as well
3404                        if (shortcut == null) {
3405                            deletedShortcutInfos.add(si);
3406                            continue;
3407                        }
3408                        si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
3409                        si.updateFromDeepShortcutInfo(shortcut, context);
3410                    } else {
3411                        si.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
3412                    }
3413                    updatedShortcutInfos.add(si);
3414                }
3415            }
3416            bindUpdatedShortcuts(updatedShortcutInfos, deletedShortcutInfos, mUser);
3417            if (!deletedShortcutInfos.isEmpty()) {
3418                deleteItemsFromDatabase(context, deletedShortcutInfos);
3419            }
3420
3421            // Remove shortcut id map for that user
3422            Iterator<ComponentKey> keysIter = mBgDeepShortcutMap.keySet().iterator();
3423            while (keysIter.hasNext()) {
3424                if (keysIter.next().user.equals(mUser)) {
3425                    keysIter.remove();
3426                }
3427            }
3428
3429            if (isUserUnlocked) {
3430                updateDeepShortcutMap(null, mUser, mDeepShortcutManager.queryForAllShortcuts(mUser));
3431            }
3432            bindDeepShortcuts();
3433        }
3434    }
3435
3436    private void bindWidgetsModel(final Callbacks callbacks, final WidgetsModel model) {
3437        mHandler.post(new Runnable() {
3438            @Override
3439            public void run() {
3440                Callbacks cb = getCallback();
3441                if (callbacks == cb && cb != null) {
3442                    callbacks.bindWidgetsModel(model);
3443                }
3444            }
3445        });
3446    }
3447
3448    public void refreshAndBindWidgetsAndShortcuts(
3449            final Callbacks callbacks, final boolean bindFirst) {
3450        runOnWorkerThread(new Runnable() {
3451            @Override
3452            public void run() {
3453                if (bindFirst && !mBgWidgetsModel.isEmpty()) {
3454                    bindWidgetsModel(callbacks, mBgWidgetsModel.clone());
3455                }
3456                final WidgetsModel model = mBgWidgetsModel.updateAndClone(mApp.getContext());
3457                bindWidgetsModel(callbacks, model);
3458                // update the Widget entries inside DB on the worker thread.
3459                LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(
3460                        model.getRawList());
3461            }
3462        });
3463    }
3464
3465    @Thunk static boolean isPackageDisabled(Context context, String packageName,
3466            UserHandleCompat user) {
3467        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3468        return !launcherApps.isPackageEnabledForProfile(packageName, user);
3469    }
3470
3471    public static boolean isValidPackageActivity(Context context, ComponentName cn,
3472            UserHandleCompat user) {
3473        if (cn == null) {
3474            return false;
3475        }
3476        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3477        if (!launcherApps.isPackageEnabledForProfile(cn.getPackageName(), user)) {
3478            return false;
3479        }
3480        return launcherApps.isActivityEnabledForProfile(cn, user);
3481    }
3482
3483    public static boolean isValidPackage(Context context, String packageName,
3484            UserHandleCompat user) {
3485        if (packageName == null) {
3486            return false;
3487        }
3488        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3489        return launcherApps.isPackageEnabledForProfile(packageName, user);
3490    }
3491
3492    /**
3493     * Make an ShortcutInfo object for a restored application or shortcut item that points
3494     * to a package that is not yet installed on the system.
3495     */
3496    public ShortcutInfo getRestoredItemInfo(Cursor c, Intent intent,
3497            int promiseType, int itemType, CursorIconInfo iconInfo) {
3498        final ShortcutInfo info = new ShortcutInfo();
3499        info.user = UserHandleCompat.myUserHandle();
3500
3501        Bitmap icon = iconInfo.loadIcon(c, info);
3502        // the fallback icon
3503        if (icon == null) {
3504            mIconCache.getTitleAndIcon(info, intent, info.user, false /* useLowResIcon */);
3505        } else {
3506            info.setIcon(icon);
3507        }
3508
3509        if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
3510            String title = iconInfo.getTitle(c);
3511            if (!TextUtils.isEmpty(title)) {
3512                info.title = Utilities.trim(title);
3513            }
3514        } else if  ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
3515            if (TextUtils.isEmpty(info.title)) {
3516                info.title = iconInfo.getTitle(c);
3517            }
3518        } else {
3519            throw new InvalidParameterException("Invalid restoreType " + promiseType);
3520        }
3521
3522        info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3523        info.itemType = itemType;
3524        info.promisedIntent = intent;
3525        info.status = promiseType;
3526        return info;
3527    }
3528
3529    /**
3530     * Make an Intent object for a restored application or shortcut item that points
3531     * to the market page for the item.
3532     */
3533    @Thunk Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) {
3534        ComponentName componentName = intent.getComponent();
3535        return getMarketIntent(componentName.getPackageName());
3536    }
3537
3538    static Intent getMarketIntent(String packageName) {
3539        return new Intent(Intent.ACTION_VIEW)
3540            .setData(new Uri.Builder()
3541                .scheme("market")
3542                .authority("details")
3543                .appendQueryParameter("id", packageName)
3544                .build());
3545    }
3546
3547    /**
3548     * Make an ShortcutInfo object for a shortcut that is an application.
3549     *
3550     * If c is not null, then it will be used to fill in missing data like the title and icon.
3551     */
3552    public ShortcutInfo getAppShortcutInfo(Intent intent,
3553            UserHandleCompat user, Cursor c, CursorIconInfo iconInfo,
3554            boolean allowMissingTarget, boolean useLowResIcon) {
3555        if (user == null) {
3556            Log.d(TAG, "Null user found in getShortcutInfo");
3557            return null;
3558        }
3559
3560        ComponentName componentName = intent.getComponent();
3561        if (componentName == null) {
3562            Log.d(TAG, "Missing component found in getShortcutInfo");
3563            return null;
3564        }
3565
3566        Intent newIntent = new Intent(intent.getAction(), null);
3567        newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
3568        newIntent.setComponent(componentName);
3569        LauncherActivityInfoCompat lai = mLauncherApps.resolveActivity(newIntent, user);
3570        if ((lai == null) && !allowMissingTarget) {
3571            Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName);
3572            return null;
3573        }
3574
3575        final ShortcutInfo info = new ShortcutInfo();
3576        mIconCache.getTitleAndIcon(info, componentName, lai, user, false, useLowResIcon);
3577        if (mIconCache.isDefaultIcon(info.getIcon(mIconCache), user) && c != null) {
3578            Bitmap icon = iconInfo.loadIcon(c);
3579            info.setIcon(icon == null ? mIconCache.getDefaultIcon(user) : icon);
3580        }
3581
3582        if (lai != null && PackageManagerHelper.isAppSuspended(lai.getApplicationInfo())) {
3583            info.isDisabled = ShortcutInfo.FLAG_DISABLED_SUSPENDED;
3584        }
3585
3586        // from the db
3587        if (TextUtils.isEmpty(info.title) && c != null) {
3588            info.title = iconInfo.getTitle(c);
3589        }
3590
3591        // fall back to the class name of the activity
3592        if (info.title == null) {
3593            info.title = componentName.getClassName();
3594        }
3595
3596        info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
3597        info.user = user;
3598        info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3599        if (lai != null) {
3600            info.flags = AppInfo.initFlags(lai);
3601        }
3602        return info;
3603    }
3604
3605    static ArrayList<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos,
3606            ItemInfoFilter f) {
3607        HashSet<ItemInfo> filtered = new HashSet<ItemInfo>();
3608        for (ItemInfo i : infos) {
3609            if (i instanceof ShortcutInfo) {
3610                ShortcutInfo info = (ShortcutInfo) i;
3611                ComponentName cn = info.getTargetComponent();
3612                if (cn != null && f.filterItem(null, info, cn)) {
3613                    filtered.add(info);
3614                }
3615            } else if (i instanceof FolderInfo) {
3616                FolderInfo info = (FolderInfo) i;
3617                for (ShortcutInfo s : info.contents) {
3618                    ComponentName cn = s.getTargetComponent();
3619                    if (cn != null && f.filterItem(info, s, cn)) {
3620                        filtered.add(s);
3621                    }
3622                }
3623            } else if (i instanceof LauncherAppWidgetInfo) {
3624                LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
3625                ComponentName cn = info.providerName;
3626                if (cn != null && f.filterItem(null, info, cn)) {
3627                    filtered.add(info);
3628                }
3629            }
3630        }
3631        return new ArrayList<ItemInfo>(filtered);
3632    }
3633
3634    @Thunk ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname,
3635            final UserHandleCompat user) {
3636        ItemInfoFilter filter  = new ItemInfoFilter() {
3637            @Override
3638            public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
3639                if (info.user == null) {
3640                    return cn.equals(cname);
3641                } else {
3642                    return cn.equals(cname) && info.user.equals(user);
3643                }
3644            }
3645        };
3646        return filterItemInfos(sBgItemsIdMap, filter);
3647    }
3648
3649    /**
3650     * Make an ShortcutInfo object for a shortcut that isn't an application.
3651     */
3652    @Thunk ShortcutInfo getShortcutInfo(Cursor c, CursorIconInfo iconInfo) {
3653        final ShortcutInfo info = new ShortcutInfo();
3654        // Non-app shortcuts are only supported for current user.
3655        info.user = UserHandleCompat.myUserHandle();
3656        info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
3657
3658        // TODO: If there's an explicit component and we can't install that, delete it.
3659
3660        loadInfoFromCursor(info, c, iconInfo);
3661        return info;
3662    }
3663
3664    /**
3665     * Make an ShortcutInfo object for a shortcut that isn't an application.
3666     */
3667    public void loadInfoFromCursor(ShortcutInfo info, Cursor c, CursorIconInfo iconInfo) {
3668        info.title = iconInfo.getTitle(c);
3669        Bitmap icon = iconInfo.loadIcon(c, info);
3670        // the fallback icon
3671        if (icon == null) {
3672            icon = mIconCache.getDefaultIcon(info.user);
3673            info.usingFallbackIcon = true;
3674        }
3675        info.setIcon(icon);
3676    }
3677
3678    ShortcutInfo infoFromShortcutIntent(Context context, Intent data) {
3679        Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
3680        String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
3681        Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
3682
3683        if (intent == null) {
3684            // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
3685            Log.e(TAG, "Can't construct ShorcutInfo with null intent");
3686            return null;
3687        }
3688
3689        Bitmap icon = null;
3690        boolean customIcon = false;
3691        ShortcutIconResource iconResource = null;
3692
3693        if (bitmap instanceof Bitmap) {
3694            icon = Utilities.createIconBitmap((Bitmap) bitmap, context);
3695            customIcon = true;
3696        } else {
3697            Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
3698            if (extra instanceof ShortcutIconResource) {
3699                iconResource = (ShortcutIconResource) extra;
3700                icon = Utilities.createIconBitmap(iconResource.packageName,
3701                        iconResource.resourceName, context);
3702            }
3703        }
3704
3705        final ShortcutInfo info = new ShortcutInfo();
3706
3707        // Only support intents for current user for now. Intents sent from other
3708        // users wouldn't get here without intent forwarding anyway.
3709        info.user = UserHandleCompat.myUserHandle();
3710        if (icon == null) {
3711            icon = mIconCache.getDefaultIcon(info.user);
3712            info.usingFallbackIcon = true;
3713        }
3714        info.setIcon(icon);
3715
3716        info.title = Utilities.trim(name);
3717        info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3718        info.intent = intent;
3719        info.iconResource = iconResource;
3720
3721        return info;
3722    }
3723
3724    /**
3725     * Return an existing FolderInfo object if we have encountered this ID previously,
3726     * or make a new one.
3727     */
3728    @Thunk static FolderInfo findOrMakeFolder(LongArrayMap<FolderInfo> folders, long id) {
3729        // See if a placeholder was created for us already
3730        FolderInfo folderInfo = folders.get(id);
3731        if (folderInfo == null) {
3732            // No placeholder -- create a new instance
3733            folderInfo = new FolderInfo();
3734            folders.put(id, folderInfo);
3735        }
3736        return folderInfo;
3737    }
3738
3739
3740    static boolean isValidProvider(AppWidgetProviderInfo provider) {
3741        return (provider != null) && (provider.provider != null)
3742                && (provider.provider.getPackageName() != null);
3743    }
3744
3745    public void dumpState() {
3746        Log.d(TAG, "mCallbacks=" + mCallbacks);
3747        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
3748        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
3749        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
3750        AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
3751        if (mLoaderTask != null) {
3752            mLoaderTask.dumpState();
3753        } else {
3754            Log.d(TAG, "mLoaderTask=null");
3755        }
3756    }
3757
3758    public Callbacks getCallback() {
3759        return mCallbacks != null ? mCallbacks.get() : null;
3760    }
3761
3762    /**
3763     * @return {@link FolderInfo} if its already loaded.
3764     */
3765    public FolderInfo findFolderById(Long folderId) {
3766        synchronized (sBgLock) {
3767            return sBgFolders.get(folderId);
3768        }
3769    }
3770
3771    @Thunk class DeferredMainThreadExecutor implements Executor {
3772
3773        @Override
3774        public void execute(Runnable command) {
3775            runOnMainThread(command);
3776        }
3777    }
3778
3779    /**
3780     * @return the looper for the worker thread which can be used to start background tasks.
3781     */
3782    public static Looper getWorkerLooper() {
3783        return sWorkerThread.getLooper();
3784    }
3785}
3786