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