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