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