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