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