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