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