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