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