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