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