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