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