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