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