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