OSDN Git Service

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