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