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