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.IntentFilter;
28 import android.content.pm.LauncherActivityInfo;
29 import android.net.Uri;
30 import android.os.Handler;
31 import android.os.HandlerThread;
32 import android.os.Looper;
33 import android.os.Process;
34 import android.os.SystemClock;
35 import android.os.Trace;
36 import android.os.UserHandle;
37 import android.support.annotation.Nullable;
38 import android.text.TextUtils;
39 import android.util.Log;
40 import android.util.LongSparseArray;
41 import android.util.MutableInt;
42 
43 import com.android.launcher3.compat.AppWidgetManagerCompat;
44 import com.android.launcher3.compat.LauncherAppsCompat;
45 import com.android.launcher3.compat.PackageInstallerCompat;
46 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
47 import com.android.launcher3.compat.UserManagerCompat;
48 import com.android.launcher3.config.ProviderConfig;
49 import com.android.launcher3.dynamicui.ExtractionUtils;
50 import com.android.launcher3.folder.Folder;
51 import com.android.launcher3.folder.FolderIcon;
52 import com.android.launcher3.graphics.LauncherIcons;
53 import com.android.launcher3.logging.FileLog;
54 import com.android.launcher3.model.AddWorkspaceItemsTask;
55 import com.android.launcher3.model.BgDataModel;
56 import com.android.launcher3.model.CacheDataUpdatedTask;
57 import com.android.launcher3.model.ExtendedModelTask;
58 import com.android.launcher3.model.GridSizeMigrationTask;
59 import com.android.launcher3.model.LoaderCursor;
60 import com.android.launcher3.model.ModelWriter;
61 import com.android.launcher3.model.PackageInstallStateChangedTask;
62 import com.android.launcher3.model.PackageItemInfo;
63 import com.android.launcher3.model.PackageUpdatedTask;
64 import com.android.launcher3.model.SdCardAvailableReceiver;
65 import com.android.launcher3.model.ShortcutsChangedTask;
66 import com.android.launcher3.model.UserLockStateChangedTask;
67 import com.android.launcher3.model.WidgetItem;
68 import com.android.launcher3.model.WidgetsModel;
69 import com.android.launcher3.provider.ImportDataTask;
70 import com.android.launcher3.provider.LauncherDbUtils;
71 import com.android.launcher3.shortcuts.DeepShortcutManager;
72 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
73 import com.android.launcher3.shortcuts.ShortcutKey;
74 import com.android.launcher3.util.ComponentKey;
75 import com.android.launcher3.util.ManagedProfileHeuristic;
76 import com.android.launcher3.util.MultiHashMap;
77 import com.android.launcher3.util.PackageManagerHelper;
78 import com.android.launcher3.util.PackageUserKey;
79 import com.android.launcher3.util.Preconditions;
80 import com.android.launcher3.util.Provider;
81 import com.android.launcher3.util.Thunk;
82 import com.android.launcher3.util.ViewOnDrawExecutor;
83 
84 import java.io.FileDescriptor;
85 import java.io.PrintWriter;
86 import java.lang.ref.WeakReference;
87 import java.util.ArrayList;
88 import java.util.Collections;
89 import java.util.Comparator;
90 import java.util.HashMap;
91 import java.util.HashSet;
92 import java.util.Iterator;
93 import java.util.List;
94 import java.util.Map;
95 import java.util.Set;
96 import java.util.concurrent.CancellationException;
97 import java.util.concurrent.Executor;
98 
99 /**
100  * Maintains in-memory state of the Launcher. It is expected that there should be only one
101  * LauncherModel object held in a static. Also provide APIs for updating the database state
102  * for the Launcher.
103  */
104 public class LauncherModel extends BroadcastReceiver
105         implements LauncherAppsCompat.OnAppsChangedCallbackCompat {
106     static final boolean DEBUG_LOADERS = false;
107     private static final boolean DEBUG_RECEIVER = false;
108 
109     static final String TAG = "Launcher.Model";
110 
111     private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
112     private static final long INVALID_SCREEN_ID = -1L;
113 
114     @Thunk final LauncherAppState mApp;
115     @Thunk final Object mLock = new Object();
116     @Thunk DeferredHandler mHandler = new DeferredHandler();
117     @Thunk LoaderTask mLoaderTask;
118     @Thunk boolean mIsLoaderTaskRunning;
119     @Thunk boolean mHasLoaderCompletedOnce;
120     @Thunk boolean mIsManagedHeuristicAppsUpdated;
121 
122     @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
123     static {
sWorkerThread.start()124         sWorkerThread.start();
125     }
126     @Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());
127 
128     // Indicates whether the current model data is valid or not.
129     // We start off with everything not loaded. After that, we assume that
130     // our monitoring of the package manager provides all updates and we never
131     // need to do a requery. This is only ever touched from the loader thread.
132     private boolean mModelLoaded;
isModelLoaded()133     public boolean isModelLoaded() {
134         synchronized (mLock) {
135             return mModelLoaded && mLoaderTask == null;
136         }
137     }
138 
139     /**
140      * Set of runnables to be called on the background thread after the workspace binding
141      * is complete.
142      */
143     static final ArrayList<Runnable> mBindCompleteRunnables = new ArrayList<Runnable>();
144 
145     @Thunk WeakReference<Callbacks> mCallbacks;
146 
147     // < only access in worker thread >
148     private final AllAppsList mBgAllAppsList;
149     // Entire list of widgets.
150     private final WidgetsModel mBgWidgetsModel;
151 
152     private boolean mHasShortcutHostPermission;
153     // Runnable to check if the shortcuts permission has changed.
154     private final Runnable mShortcutPermissionCheckRunnable = new Runnable() {
155         @Override
156         public void run() {
157             if (mModelLoaded) {
158                 boolean hasShortcutHostPermission =
159                         DeepShortcutManager.getInstance(mApp.getContext()).hasHostPermission();
160                 if (hasShortcutHostPermission != mHasShortcutHostPermission) {
161                     forceReload();
162                 }
163             }
164         }
165     };
166 
167     /**
168      * All the static data should be accessed on the background thread, A lock should be acquired
169      * on this object when accessing any data from this model.
170      */
171     static final BgDataModel sBgDataModel = new BgDataModel();
172 
173     // </ only access in worker thread >
174 
175     private final IconCache mIconCache;
176 
177     private final LauncherAppsCompat mLauncherApps;
178     private final UserManagerCompat mUserManager;
179 
180     public interface Callbacks {
setLoadOnResume()181         public boolean setLoadOnResume();
getCurrentWorkspaceScreen()182         public int getCurrentWorkspaceScreen();
clearPendingBinds()183         public void clearPendingBinds();
startBinding()184         public void startBinding();
bindItems(ArrayList<ItemInfo> shortcuts, int start, int end, boolean forceAnimateIcons)185         public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
186                               boolean forceAnimateIcons);
bindScreens(ArrayList<Long> orderedScreenIds)187         public void bindScreens(ArrayList<Long> orderedScreenIds);
finishFirstPageBind(ViewOnDrawExecutor executor)188         public void finishFirstPageBind(ViewOnDrawExecutor executor);
finishBindingItems()189         public void finishBindingItems();
bindAppWidget(LauncherAppWidgetInfo info)190         public void bindAppWidget(LauncherAppWidgetInfo info);
bindAllApplications(ArrayList<AppInfo> apps)191         public void bindAllApplications(ArrayList<AppInfo> apps);
bindAppsAdded(ArrayList<Long> newScreens, ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated, ArrayList<AppInfo> addedApps)192         public void bindAppsAdded(ArrayList<Long> newScreens,
193                                   ArrayList<ItemInfo> addNotAnimated,
194                                   ArrayList<ItemInfo> addAnimated,
195                                   ArrayList<AppInfo> addedApps);
bindAppsUpdated(ArrayList<AppInfo> apps)196         public void bindAppsUpdated(ArrayList<AppInfo> apps);
bindShortcutsChanged(ArrayList<ShortcutInfo> updated, ArrayList<ShortcutInfo> removed, UserHandle user)197         public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,
198                 ArrayList<ShortcutInfo> removed, UserHandle user);
bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets)199         public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
bindRestoreItemsChange(HashSet<ItemInfo> updates)200         public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
bindWorkspaceComponentsRemoved( HashSet<String> packageNames, HashSet<ComponentName> components, UserHandle user)201         public void bindWorkspaceComponentsRemoved(
202                 HashSet<String> packageNames, HashSet<ComponentName> components,
203                 UserHandle user);
bindAppInfosRemoved(ArrayList<AppInfo> appInfos)204         public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos);
notifyWidgetProvidersChanged()205         public void notifyWidgetProvidersChanged();
bindAllWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets)206         public void bindAllWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets);
onPageBoundSynchronously(int page)207         public void onPageBoundSynchronously(int page);
executeOnNextDraw(ViewOnDrawExecutor executor)208         public void executeOnNextDraw(ViewOnDrawExecutor executor);
bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap)209         public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap);
210     }
211 
LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter)212     LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
213         Context context = app.getContext();
214         mApp = app;
215         mBgAllAppsList = new AllAppsList(iconCache, appFilter);
216         mBgWidgetsModel = new WidgetsModel(iconCache, appFilter);
217         mIconCache = iconCache;
218 
219         mLauncherApps = LauncherAppsCompat.getInstance(context);
220         mUserManager = UserManagerCompat.getInstance(context);
221     }
222 
223     /** Runs the specified runnable immediately if called from the main thread, otherwise it is
224      * posted on the main thread handler. */
runOnMainThread(Runnable r)225     private void runOnMainThread(Runnable r) {
226         if (sWorkerThread.getThreadId() == Process.myTid()) {
227             // If we are on the worker thread, post onto the main handler
228             mHandler.post(r);
229         } else {
230             r.run();
231         }
232     }
233 
234     /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
235      * posted on the worker thread handler. */
runOnWorkerThread(Runnable r)236     private static void runOnWorkerThread(Runnable r) {
237         if (sWorkerThread.getThreadId() == Process.myTid()) {
238             r.run();
239         } else {
240             // If we are not on the worker thread, then post to the worker handler
241             sWorker.post(r);
242         }
243     }
244 
setPackageState(PackageInstallInfo installInfo)245     public void setPackageState(PackageInstallInfo installInfo) {
246         enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo));
247     }
248 
249     /**
250      * Updates the icons and label of all pending icons for the provided package name.
251      */
updateSessionDisplayInfo(final String packageName)252     public void updateSessionDisplayInfo(final String packageName) {
253         HashSet<String> packages = new HashSet<>();
254         packages.add(packageName);
255         enqueueModelUpdateTask(new CacheDataUpdatedTask(
256                 CacheDataUpdatedTask.OP_SESSION_UPDATE, Process.myUserHandle(), packages));
257     }
258 
259     /**
260      * Adds the provided items to the workspace.
261      */
addAndBindAddedWorkspaceItems(List<ItemInfo> workspaceApps)262     public void addAndBindAddedWorkspaceItems(List<ItemInfo> workspaceApps) {
263         addAndBindAddedWorkspaceItems(Provider.of(workspaceApps));
264     }
265 
266     /**
267      * Adds the provided items to the workspace.
268      */
addAndBindAddedWorkspaceItems( Provider<List<ItemInfo>> appsProvider)269     public void addAndBindAddedWorkspaceItems(
270             Provider<List<ItemInfo>> appsProvider) {
271         enqueueModelUpdateTask(new AddWorkspaceItemsTask(appsProvider));
272     }
273 
getWriter(boolean hasVerticalHotseat)274     public ModelWriter getWriter(boolean hasVerticalHotseat) {
275         return new ModelWriter(mApp.getContext(), sBgDataModel, hasVerticalHotseat);
276     }
277 
checkItemInfoLocked( final long itemId, final ItemInfo item, StackTraceElement[] stackTrace)278     static void checkItemInfoLocked(
279             final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
280         ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId);
281         if (modelItem != null && item != modelItem) {
282             // check all the data is consistent
283             if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
284                 ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
285                 ShortcutInfo shortcut = (ShortcutInfo) item;
286                 if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
287                         modelShortcut.intent.filterEquals(shortcut.intent) &&
288                         modelShortcut.id == shortcut.id &&
289                         modelShortcut.itemType == shortcut.itemType &&
290                         modelShortcut.container == shortcut.container &&
291                         modelShortcut.screenId == shortcut.screenId &&
292                         modelShortcut.cellX == shortcut.cellX &&
293                         modelShortcut.cellY == shortcut.cellY &&
294                         modelShortcut.spanX == shortcut.spanX &&
295                         modelShortcut.spanY == shortcut.spanY) {
296                     // For all intents and purposes, this is the same object
297                     return;
298                 }
299             }
300 
301             // the modelItem needs to match up perfectly with item if our model is
302             // to be consistent with the database-- for now, just require
303             // modelItem == item or the equality check above
304             String msg = "item: " + ((item != null) ? item.toString() : "null") +
305                     "modelItem: " +
306                     ((modelItem != null) ? modelItem.toString() : "null") +
307                     "Error: ItemInfo passed to checkItemInfo doesn't match original";
308             RuntimeException e = new RuntimeException(msg);
309             if (stackTrace != null) {
310                 e.setStackTrace(stackTrace);
311             }
312             throw e;
313         }
314     }
315 
checkItemInfo(final ItemInfo item)316     static void checkItemInfo(final ItemInfo item) {
317         final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
318         final long itemId = item.id;
319         Runnable r = new Runnable() {
320             public void run() {
321                 synchronized (sBgDataModel) {
322                     checkItemInfoLocked(itemId, item, stackTrace);
323                 }
324             }
325         };
326         runOnWorkerThread(r);
327     }
328 
329     /**
330      * Update the order of the workspace screens in the database. The array list contains
331      * a list of screen ids in the order that they should appear.
332      */
updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens)333     public static void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
334         final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
335         final ContentResolver cr = context.getContentResolver();
336         final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
337 
338         // Remove any negative screen ids -- these aren't persisted
339         Iterator<Long> iter = screensCopy.iterator();
340         while (iter.hasNext()) {
341             long id = iter.next();
342             if (id < 0) {
343                 iter.remove();
344             }
345         }
346 
347         Runnable r = new Runnable() {
348             @Override
349             public void run() {
350                 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
351                 // Clear the table
352                 ops.add(ContentProviderOperation.newDelete(uri).build());
353                 int count = screensCopy.size();
354                 for (int i = 0; i < count; i++) {
355                     ContentValues v = new ContentValues();
356                     long screenId = screensCopy.get(i);
357                     v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
358                     v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
359                     ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
360                 }
361 
362                 try {
363                     cr.applyBatch(LauncherProvider.AUTHORITY, ops);
364                 } catch (Exception ex) {
365                     throw new RuntimeException(ex);
366                 }
367 
368                 synchronized (sBgDataModel) {
369                     sBgDataModel.workspaceScreens.clear();
370                     sBgDataModel.workspaceScreens.addAll(screensCopy);
371                 }
372             }
373         };
374         runOnWorkerThread(r);
375     }
376 
377     /**
378      * Set this as the current Launcher activity object for the loader.
379      */
initialize(Callbacks callbacks)380     public void initialize(Callbacks callbacks) {
381         synchronized (mLock) {
382             Preconditions.assertUIThread();
383             // Remove any queued UI runnables
384             mHandler.cancelAll();
385             mCallbacks = new WeakReference<>(callbacks);
386         }
387     }
388 
389     @Override
onPackageChanged(String packageName, UserHandle user)390     public void onPackageChanged(String packageName, UserHandle user) {
391         int op = PackageUpdatedTask.OP_UPDATE;
392         enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
393     }
394 
395     @Override
onPackageRemoved(String packageName, UserHandle user)396     public void onPackageRemoved(String packageName, UserHandle user) {
397         onPackagesRemoved(user, packageName);
398     }
399 
onPackagesRemoved(UserHandle user, String... packages)400     public void onPackagesRemoved(UserHandle user, String... packages) {
401         int op = PackageUpdatedTask.OP_REMOVE;
402         enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages));
403     }
404 
405     @Override
onPackageAdded(String packageName, UserHandle user)406     public void onPackageAdded(String packageName, UserHandle user) {
407         int op = PackageUpdatedTask.OP_ADD;
408         enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
409     }
410 
411     @Override
onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing)412     public void onPackagesAvailable(String[] packageNames, UserHandle user,
413             boolean replacing) {
414         enqueueModelUpdateTask(
415                 new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, user, packageNames));
416     }
417 
418     @Override
onPackagesUnavailable(String[] packageNames, UserHandle user, boolean replacing)419     public void onPackagesUnavailable(String[] packageNames, UserHandle user,
420             boolean replacing) {
421         if (!replacing) {
422             enqueueModelUpdateTask(new PackageUpdatedTask(
423                     PackageUpdatedTask.OP_UNAVAILABLE, user, packageNames));
424         }
425     }
426 
427     @Override
onPackagesSuspended(String[] packageNames, UserHandle user)428     public void onPackagesSuspended(String[] packageNames, UserHandle user) {
429         enqueueModelUpdateTask(new PackageUpdatedTask(
430                 PackageUpdatedTask.OP_SUSPEND, user, packageNames));
431     }
432 
433     @Override
onPackagesUnsuspended(String[] packageNames, UserHandle user)434     public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
435         enqueueModelUpdateTask(new PackageUpdatedTask(
436                 PackageUpdatedTask.OP_UNSUSPEND, user, packageNames));
437     }
438 
439     @Override
onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts, UserHandle user)440     public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
441             UserHandle user) {
442         enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true));
443     }
444 
updatePinnedShortcuts(String packageName, List<ShortcutInfoCompat> shortcuts, UserHandle user)445     public void updatePinnedShortcuts(String packageName, List<ShortcutInfoCompat> shortcuts,
446             UserHandle user) {
447         enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, false));
448     }
449 
450     /**
451      * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
452      * ACTION_PACKAGE_CHANGED.
453      */
454     @Override
onReceive(Context context, Intent intent)455     public void onReceive(Context context, Intent intent) {
456         if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
457 
458         final String action = intent.getAction();
459         if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
460             // If we have changed locale we need to clear out the labels in all apps/workspace.
461             forceReload();
462         } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)
463                 || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
464             UserManagerCompat.getInstance(context).enableAndResetCache();
465             forceReload();
466         } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
467                 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
468                 Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
469             UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
470             if (user != null) {
471                 if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
472                         Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
473                     enqueueModelUpdateTask(new PackageUpdatedTask(
474                             PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
475                 }
476 
477                 // ACTION_MANAGED_PROFILE_UNAVAILABLE sends the profile back to locked mode, so
478                 // we need to run the state change task again.
479                 if (Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
480                         Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
481                     enqueueModelUpdateTask(new UserLockStateChangedTask(user));
482                 }
483             }
484         } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(action)) {
485             ExtractionUtils.startColorExtractionServiceIfNecessary(context);
486         }
487     }
488 
489     /**
490      * Reloads the workspace items from the DB and re-binds the workspace. This should generally
491      * not be called as DB updates are automatically followed by UI update
492      */
forceReload()493     public void forceReload() {
494         synchronized (mLock) {
495             // Stop any existing loaders first, so they don't set mModelLoaded to true later
496             stopLoaderLocked();
497             mModelLoaded = false;
498         }
499 
500         // Do this here because if the launcher activity is running it will be restarted.
501         // If it's not running startLoaderFromBackground will merely tell it that it needs
502         // to reload.
503         startLoaderFromBackground();
504     }
505 
506     /**
507      * When the launcher is in the background, it's possible for it to miss paired
508      * configuration changes.  So whenever we trigger the loader from the background
509      * tell the launcher that it needs to re-run the loader when it comes back instead
510      * of doing it now.
511      */
startLoaderFromBackground()512     public void startLoaderFromBackground() {
513         Callbacks callbacks = getCallback();
514         if (callbacks != null) {
515             // Only actually run the loader if they're not paused.
516             if (!callbacks.setLoadOnResume()) {
517                 startLoader(callbacks.getCurrentWorkspaceScreen());
518             }
519         }
520     }
521 
522     /**
523      * If there is already a loader task running, tell it to stop.
524      */
stopLoaderLocked()525     private void stopLoaderLocked() {
526         LoaderTask oldTask = mLoaderTask;
527         if (oldTask != null) {
528             oldTask.stopLocked();
529         }
530     }
531 
isCurrentCallbacks(Callbacks callbacks)532     public boolean isCurrentCallbacks(Callbacks callbacks) {
533         return (mCallbacks != null && mCallbacks.get() == callbacks);
534     }
535 
536     /**
537      * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible.
538      * @return true if the page could be bound synchronously.
539      */
startLoader(int synchronousBindPage)540     public boolean startLoader(int synchronousBindPage) {
541         // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
542         InstallShortcutReceiver.enableInstallQueue();
543         synchronized (mLock) {
544             // Don't bother to start the thread if we know it's not going to do anything
545             if (mCallbacks != null && mCallbacks.get() != null) {
546                 final Callbacks oldCallbacks = mCallbacks.get();
547                 // Clear any pending bind-runnables from the synchronized load process.
548                 runOnMainThread(new Runnable() {
549                     public void run() {
550                         oldCallbacks.clearPendingBinds();
551                     }
552                 });
553 
554                 // If there is already one running, tell it to stop.
555                 stopLoaderLocked();
556                 mLoaderTask = new LoaderTask(mApp.getContext(), synchronousBindPage);
557                 if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
558                         && mModelLoaded && !mIsLoaderTaskRunning) {
559                     mLoaderTask.runBindSynchronousPage(synchronousBindPage);
560                     return true;
561                 } else {
562                     sWorkerThread.setPriority(Thread.NORM_PRIORITY);
563                     sWorker.post(mLoaderTask);
564                 }
565             }
566         }
567         return false;
568     }
569 
stopLoader()570     public void stopLoader() {
571         synchronized (mLock) {
572             if (mLoaderTask != null) {
573                 mLoaderTask.stopLocked();
574             }
575         }
576     }
577 
578     /**
579      * Loads the workspace screen ids in an ordered list.
580      */
loadWorkspaceScreensDb(Context context)581     public static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
582         final ContentResolver contentResolver = context.getContentResolver();
583         final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
584 
585         // Get screens ordered by rank.
586         return LauncherDbUtils.getScreenIdsFromCursor(contentResolver.query(
587                 screensUri, null, null, null, LauncherSettings.WorkspaceScreens.SCREEN_RANK));
588     }
589 
590     /**
591      * Runnable for the thread that loads the contents of the launcher:
592      *   - workspace icons
593      *   - widgets
594      *   - all apps icons
595      *   - deep shortcuts within apps
596      */
597     private class LoaderTask implements Runnable {
598         private Context mContext;
599         private int mPageToBindFirst;
600 
601         @Thunk boolean mIsLoadingAndBindingWorkspace;
602         private boolean mStopped;
603         @Thunk boolean mLoadAndBindStepFinished;
604 
LoaderTask(Context context, int pageToBindFirst)605         LoaderTask(Context context, int pageToBindFirst) {
606             mContext = context;
607             mPageToBindFirst = pageToBindFirst;
608         }
609 
waitForIdle()610         private void waitForIdle() {
611             // Wait until the either we're stopped or the other threads are done.
612             // This way we don't start loading all apps until the workspace has settled
613             // down.
614             synchronized (LoaderTask.this) {
615                 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
616 
617                 mHandler.postIdle(new Runnable() {
618                         public void run() {
619                             synchronized (LoaderTask.this) {
620                                 mLoadAndBindStepFinished = true;
621                                 if (DEBUG_LOADERS) {
622                                     Log.d(TAG, "done with previous binding step");
623                                 }
624                                 LoaderTask.this.notify();
625                             }
626                         }
627                     });
628 
629                 while (!mStopped && !mLoadAndBindStepFinished) {
630                     try {
631                         // Just in case mFlushingWorkerThread changes but we aren't woken up,
632                         // wait no longer than 1sec at a time
633                         this.wait(1000);
634                     } catch (InterruptedException ex) {
635                         // Ignore
636                     }
637                 }
638                 if (DEBUG_LOADERS) {
639                     Log.d(TAG, "waited "
640                             + (SystemClock.uptimeMillis()-workspaceWaitTime)
641                             + "ms for previous step to finish binding");
642                 }
643             }
644         }
645 
runBindSynchronousPage(int synchronousBindPage)646         void runBindSynchronousPage(int synchronousBindPage) {
647             if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) {
648                 // Ensure that we have a valid page index to load synchronously
649                 throw new RuntimeException("Should not call runBindSynchronousPage() without " +
650                         "valid page index");
651             }
652             if (!mModelLoaded) {
653                 // Ensure that we don't try and bind a specified page when the pages have not been
654                 // loaded already (we should load everything asynchronously in that case)
655                 throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
656             }
657             synchronized (mLock) {
658                 if (mIsLoaderTaskRunning) {
659                     // Ensure that we are never running the background loading at this point since
660                     // we also touch the background collections
661                     throw new RuntimeException("Error! Background loading is already running");
662                 }
663             }
664 
665             // XXX: Throw an exception if we are already loading (since we touch the worker thread
666             //      data structures, we can't allow any other thread to touch that data, but because
667             //      this call is synchronous, we can get away with not locking).
668 
669             // The LauncherModel is static in the LauncherAppState and mHandler may have queued
670             // operations from the previous activity.  We need to ensure that all queued operations
671             // are executed before any synchronous binding work is done.
672             mHandler.flush();
673 
674             // Divide the set of loaded items into those that we are binding synchronously, and
675             // everything else that is to be bound normally (asynchronously).
676             bindWorkspace(synchronousBindPage);
677             // XXX: For now, continue posting the binding of AllApps as there are other issues that
678             //      arise from that.
679             onlyBindAllApps();
680 
681             bindDeepShortcuts();
682         }
683 
verifyNotStopped()684         private void verifyNotStopped() throws CancellationException {
685             synchronized (LoaderTask.this) {
686                 if (mStopped) {
687                     throw new CancellationException("Loader stopped");
688                 }
689             }
690         }
691 
run()692         public void run() {
693             synchronized (mLock) {
694                 if (mStopped) {
695                     return;
696                 }
697                 mIsLoaderTaskRunning = true;
698             }
699 
700             try {
701                 if (DEBUG_LOADERS) Log.d(TAG, "step 1.1: loading workspace");
702                 // Set to false in bindWorkspace()
703                 mIsLoadingAndBindingWorkspace = true;
704                 loadWorkspace();
705 
706                 verifyNotStopped();
707                 if (DEBUG_LOADERS) Log.d(TAG, "step 1.2: bind workspace workspace");
708                 bindWorkspace(mPageToBindFirst);
709 
710                 // Take a break
711                 if (DEBUG_LOADERS) Log.d(TAG, "step 1 completed, wait for idle");
712                 waitForIdle();
713                 verifyNotStopped();
714 
715                 // second step
716                 if (DEBUG_LOADERS) Log.d(TAG, "step 2.1: loading all apps");
717                 loadAllApps();
718 
719                 verifyNotStopped();
720                 if (DEBUG_LOADERS) Log.d(TAG, "step 2.2: Update icon cache");
721                 updateIconCache();
722 
723                 // Take a break
724                 if (DEBUG_LOADERS) Log.d(TAG, "step 2 completed, wait for idle");
725                 waitForIdle();
726                 verifyNotStopped();
727 
728                 // third step
729                 if (DEBUG_LOADERS) Log.d(TAG, "step 3.1: loading deep shortcuts");
730                 loadDeepShortcuts();
731 
732                 verifyNotStopped();
733                 if (DEBUG_LOADERS) Log.d(TAG, "step 3.2: bind deep shortcuts");
734                 bindDeepShortcuts();
735 
736                 // Take a break
737                 if (DEBUG_LOADERS) Log.d(TAG, "step 3 completed, wait for idle");
738                 waitForIdle();
739                 verifyNotStopped();
740 
741                 // fourth step
742                 if (DEBUG_LOADERS) Log.d(TAG, "step 4.1: loading widgets");
743                 refreshAndBindWidgetsAndShortcuts(getCallback(), false /* bindFirst */,
744                         null /* packageUser */);
745 
746                 synchronized (mLock) {
747                     // Everything loaded bind the data.
748                     mModelLoaded = true;
749                     mHasLoaderCompletedOnce = true;
750                 }
751             } catch (CancellationException e) {
752               // Loader stopped, ignore
753             } finally {
754                 // Clear out this reference, otherwise we end up holding it until all of the
755                 // callback runnables are done.
756                 mContext = null;
757 
758                 synchronized (mLock) {
759                     // If we are still the last one to be scheduled, remove ourselves.
760                     if (mLoaderTask == this) {
761                         mLoaderTask = null;
762                     }
763                     mIsLoaderTaskRunning = false;
764                 }
765             }
766         }
767 
stopLocked()768         public void stopLocked() {
769             synchronized (LoaderTask.this) {
770                 mStopped = true;
771                 this.notify();
772             }
773         }
774 
775         /**
776          * Gets the callbacks object.  If we've been stopped, or if the launcher object
777          * has somehow been garbage collected, return null instead.  Pass in the Callbacks
778          * object that was around when the deferred message was scheduled, and if there's
779          * a new Callbacks object around then also return null.  This will save us from
780          * calling onto it with data that will be ignored.
781          */
tryGetCallbacks(Callbacks oldCallbacks)782         Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
783             synchronized (mLock) {
784                 if (mStopped) {
785                     return null;
786                 }
787 
788                 if (mCallbacks == null) {
789                     return null;
790                 }
791 
792                 final Callbacks callbacks = mCallbacks.get();
793                 if (callbacks != oldCallbacks) {
794                     return null;
795                 }
796                 if (callbacks == null) {
797                     Log.w(TAG, "no mCallbacks");
798                     return null;
799                 }
800 
801                 return callbacks;
802             }
803         }
804 
loadWorkspace()805         private void loadWorkspace() {
806             if (LauncherAppState.PROFILE_STARTUP) {
807                 Trace.beginSection("Loading Workspace");
808             }
809 
810             final Context context = mContext;
811             final ContentResolver contentResolver = context.getContentResolver();
812             final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
813             final boolean isSafeMode = pmHelper.isSafeMode();
814             final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
815             final DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(context);
816             final boolean isSdCardReady = Utilities.isBootCompleted();
817             final MultiHashMap<UserHandle, String> pendingPackages = new MultiHashMap<>();
818 
819             boolean clearDb = false;
820             try {
821                 ImportDataTask.performImportIfPossible(context);
822             } catch (Exception e) {
823                 // Migration failed. Clear workspace.
824                 clearDb = true;
825             }
826 
827             if (!clearDb && GridSizeMigrationTask.ENABLED &&
828                     !GridSizeMigrationTask.migrateGridIfNeeded(mContext)) {
829                 // Migration failed. Clear workspace.
830                 clearDb = true;
831             }
832 
833             if (clearDb) {
834                 Log.d(TAG, "loadWorkspace: resetting launcher database");
835                 LauncherSettings.Settings.call(contentResolver,
836                         LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
837             }
838 
839             Log.d(TAG, "loadWorkspace: loading default favorites");
840             LauncherSettings.Settings.call(contentResolver,
841                     LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES);
842 
843             synchronized (sBgDataModel) {
844                 sBgDataModel.clear();
845 
846                 final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
847                         .getInstance(mContext).updateAndGetActiveSessionCache();
848                 sBgDataModel.workspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
849 
850                 Map<ShortcutKey, ShortcutInfoCompat> shortcutKeyToPinnedShortcuts = new HashMap<>();
851                 final LoaderCursor c = new LoaderCursor(contentResolver.query(
852                         LauncherSettings.Favorites.CONTENT_URI, null, null, null, null), mApp);
853 
854                 HashMap<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null;
855 
856                 try {
857                     final int appWidgetIdIndex = c.getColumnIndexOrThrow(
858                             LauncherSettings.Favorites.APPWIDGET_ID);
859                     final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
860                             LauncherSettings.Favorites.APPWIDGET_PROVIDER);
861                     final int spanXIndex = c.getColumnIndexOrThrow
862                             (LauncherSettings.Favorites.SPANX);
863                     final int spanYIndex = c.getColumnIndexOrThrow(
864                             LauncherSettings.Favorites.SPANY);
865                     final int rankIndex = c.getColumnIndexOrThrow(
866                             LauncherSettings.Favorites.RANK);
867                     final int optionsIndex = c.getColumnIndexOrThrow(
868                             LauncherSettings.Favorites.OPTIONS);
869 
870                     final LongSparseArray<UserHandle> allUsers = c.allUsers;
871                     final LongSparseArray<Boolean> quietMode = new LongSparseArray<>();
872                     final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
873                     for (UserHandle user : mUserManager.getUserProfiles()) {
874                         long serialNo = mUserManager.getSerialNumberForUser(user);
875                         allUsers.put(serialNo, user);
876                         quietMode.put(serialNo, mUserManager.isQuietModeEnabled(user));
877 
878                         boolean userUnlocked = mUserManager.isUserUnlocked(user);
879 
880                         // We can only query for shortcuts when the user is unlocked.
881                         if (userUnlocked) {
882                             List<ShortcutInfoCompat> pinnedShortcuts =
883                                     shortcutManager.queryForPinnedShortcuts(null, user);
884                             if (shortcutManager.wasLastCallSuccess()) {
885                                 for (ShortcutInfoCompat shortcut : pinnedShortcuts) {
886                                     shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
887                                             shortcut);
888                                 }
889                             } else {
890                                 // Shortcut manager can fail due to some race condition when the
891                                 // lock state changes too frequently. For the purpose of the loading
892                                 // shortcuts, consider the user is still locked.
893                                 userUnlocked = false;
894                             }
895                         }
896                         unlockedUsers.put(serialNo, userUnlocked);
897                     }
898 
899                     ShortcutInfo info;
900                     LauncherAppWidgetInfo appWidgetInfo;
901                     Intent intent;
902                     String targetPkg;
903 
904                     while (!mStopped && c.moveToNext()) {
905                         try {
906                             if (c.user == null) {
907                                 // User has been deleted, remove the item.
908                                 c.markDeleted("User has been deleted");
909                                 continue;
910                             }
911 
912                             boolean allowMissingTarget = false;
913                             switch (c.itemType) {
914                             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
915                             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
916                             case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
917                                 intent = c.parseIntent();
918                                 if (intent == null) {
919                                     c.markDeleted("Invalid or null intent");
920                                     continue;
921                                 }
922 
923                                 int disabledState = quietMode.get(c.serialNumber) ?
924                                         ShortcutInfo.FLAG_DISABLED_QUIET_USER : 0;
925                                 ComponentName cn = intent.getComponent();
926                                 targetPkg = cn == null ? intent.getPackage() : cn.getPackageName();
927 
928                                 if (!Process.myUserHandle().equals(c.user)) {
929                                     if (c.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
930                                         c.markDeleted("Legacy shortcuts are only allowed for default user");
931                                         continue;
932                                     } else if (c.restoreFlag != 0) {
933                                         // Don't restore items for other profiles.
934                                         c.markDeleted("Restore from managed profile not supported");
935                                         continue;
936                                     }
937                                 }
938                                 if (TextUtils.isEmpty(targetPkg) &&
939                                         c.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
940                                     c.markDeleted("Only legacy shortcuts can have null package");
941                                     continue;
942                                 }
943 
944                                 // If there is no target package, its an implicit intent
945                                 // (legacy shortcut) which is always valid
946                                 boolean validTarget = TextUtils.isEmpty(targetPkg) ||
947                                         launcherApps.isPackageEnabledForProfile(targetPkg, c.user);
948 
949                                 if (cn != null && validTarget) {
950                                     // If the apk is present and the shortcut points to a specific
951                                     // component.
952 
953                                     // If the component is already present
954                                     if (launcherApps.isActivityEnabledForProfile(cn, c.user)) {
955                                         // no special handling necessary for this item
956                                         c.markRestored();
957                                     } else {
958                                         if (c.hasRestoreFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
959                                             // We allow auto install apps to have their intent
960                                             // updated after an install.
961                                             intent = pmHelper.getAppLaunchIntent(targetPkg, c.user);
962                                             if (intent != null) {
963                                                 c.restoreFlag = 0;
964                                                 c.updater().put(
965                                                         LauncherSettings.Favorites.INTENT,
966                                                         intent.toUri(0)).commit();
967                                                 cn = intent.getComponent();
968                                             } else {
969                                                 c.markDeleted("Unable to find a launch target");
970                                                 continue;
971                                             }
972                                         } else {
973                                             // The app is installed but the component is no
974                                             // longer available.
975                                             c.markDeleted("Invalid component removed: " + cn);
976                                             continue;
977                                         }
978                                     }
979                                 }
980                                 // else if cn == null => can't infer much, leave it
981                                 // else if !validPkg => could be restored icon or missing sd-card
982 
983                                 if (!TextUtils.isEmpty(targetPkg) && !validTarget) {
984                                     // Points to a valid app (superset of cn != null) but the apk
985                                     // is not available.
986 
987                                     if (c.restoreFlag != 0) {
988                                         // Package is not yet available but might be
989                                         // installed later.
990                                         FileLog.d(TAG, "package not yet restored: " + targetPkg);
991 
992                                         if (c.hasRestoreFlag(ShortcutInfo.FLAG_RESTORE_STARTED)) {
993                                             // Restore has started once.
994                                         } else if (installingPkgs.containsKey(targetPkg)) {
995                                             // App restore has started. Update the flag
996                                             c.restoreFlag |= ShortcutInfo.FLAG_RESTORE_STARTED;
997                                             c.updater().commit();
998                                         } else {
999                                             c.markDeleted("Unrestored app removed: " + targetPkg);
1000                                             continue;
1001                                         }
1002                                     } else if (pmHelper.isAppOnSdcard(targetPkg, c.user)) {
1003                                         // Package is present but not available.
1004                                         disabledState |= ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
1005                                         // Add the icon on the workspace anyway.
1006                                         allowMissingTarget = true;
1007                                     } else if (!isSdCardReady) {
1008                                         // SdCard is not ready yet. Package might get available,
1009                                         // once it is ready.
1010                                         Log.d(TAG, "Missing pkg, will check later: " + targetPkg);
1011                                         pendingPackages.addToList(c.user, targetPkg);
1012                                         // Add the icon on the workspace anyway.
1013                                         allowMissingTarget = true;
1014                                     } else {
1015                                         // Do not wait for external media load anymore.
1016                                         c.markDeleted("Invalid package removed: " + targetPkg);
1017                                         continue;
1018                                     }
1019                                 }
1020 
1021                                 if (validTarget) {
1022                                     // The shortcut points to a valid target (either no target
1023                                     // or something which is ready to be used)
1024                                     c.markRestored();
1025                                 }
1026 
1027                                 boolean useLowResIcon = !c.isOnWorkspaceOrHotseat() &&
1028                                         c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
1029 
1030                                 if (c.restoreFlag != 0) {
1031                                     // Already verified above that user is same as default user
1032                                     info = c.getRestoredItemInfo(intent);
1033                                 } else if (c.itemType ==
1034                                         LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
1035                                     info = c.getAppShortcutInfo(
1036                                             intent, allowMissingTarget, useLowResIcon);
1037                                 } else if (c.itemType ==
1038                                         LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
1039 
1040                                     ShortcutKey key = ShortcutKey.fromIntent(intent, c.user);
1041                                     if (unlockedUsers.get(c.serialNumber)) {
1042                                         ShortcutInfoCompat pinnedShortcut =
1043                                                 shortcutKeyToPinnedShortcuts.get(key);
1044                                         if (pinnedShortcut == null) {
1045                                             // The shortcut is no longer valid.
1046                                             c.markDeleted("Pinned shortcut not found");
1047                                             continue;
1048                                         }
1049                                         info = new ShortcutInfo(pinnedShortcut, context);
1050                                         info.iconBitmap = LauncherIcons
1051                                                 .createShortcutIcon(pinnedShortcut, context);
1052                                         if (pmHelper.isAppSuspended(
1053                                                 pinnedShortcut.getPackage(), info.user)) {
1054                                             info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
1055                                         }
1056                                         intent = info.intent;
1057                                     } else {
1058                                         // Create a shortcut info in disabled mode for now.
1059                                         info = c.loadSimpleShortcut();
1060                                         info.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
1061                                     }
1062                                 } else { // item type == ITEM_TYPE_SHORTCUT
1063                                     info = c.loadSimpleShortcut();
1064 
1065                                     // Shortcuts are only available on the primary profile
1066                                     if (!TextUtils.isEmpty(targetPkg)
1067                                             && pmHelper.isAppSuspended(targetPkg, c.user)) {
1068                                         disabledState |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
1069                                     }
1070 
1071                                     // App shortcuts that used to be automatically added to Launcher
1072                                     // didn't always have the correct intent flags set, so do that
1073                                     // here
1074                                     if (intent.getAction() != null &&
1075                                         intent.getCategories() != null &&
1076                                         intent.getAction().equals(Intent.ACTION_MAIN) &&
1077                                         intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
1078                                         intent.addFlags(
1079                                             Intent.FLAG_ACTIVITY_NEW_TASK |
1080                                             Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
1081                                     }
1082                                 }
1083 
1084                                 if (info != null) {
1085                                     c.applyCommonProperties(info);
1086 
1087                                     info.intent = intent;
1088                                     info.rank = c.getInt(rankIndex);
1089                                     info.spanX = 1;
1090                                     info.spanY = 1;
1091                                     info.isDisabled |= disabledState;
1092                                     if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
1093                                         info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
1094                                     }
1095 
1096                                     if (c.restoreFlag != 0 && !TextUtils.isEmpty(targetPkg)) {
1097                                         Integer progress = installingPkgs.get(targetPkg);
1098                                         if (progress != null) {
1099                                             info.setInstallProgress(progress);
1100                                         } else {
1101                                             info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
1102                                         }
1103                                     }
1104 
1105                                     c.checkAndAddItem(info, sBgDataModel);
1106                                 } else {
1107                                     throw new RuntimeException("Unexpected null ShortcutInfo");
1108                                 }
1109                                 break;
1110 
1111                             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1112                                 FolderInfo folderInfo = sBgDataModel.findOrMakeFolder(c.id);
1113                                 c.applyCommonProperties(folderInfo);
1114 
1115                                 // Do not trim the folder label, as is was set by the user.
1116                                 folderInfo.title = c.getString(c.titleIndex);
1117                                 folderInfo.spanX = 1;
1118                                 folderInfo.spanY = 1;
1119                                 folderInfo.options = c.getInt(optionsIndex);
1120 
1121                                 // no special handling required for restored folders
1122                                 c.markRestored();
1123 
1124                                 c.checkAndAddItem(folderInfo, sBgDataModel);
1125                                 break;
1126 
1127                             case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1128                             case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
1129                                 // Read all Launcher-specific widget details
1130                                 boolean customWidget = c.itemType ==
1131                                     LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
1132 
1133                                 int appWidgetId = c.getInt(appWidgetIdIndex);
1134                                 String savedProvider = c.getString(appWidgetProviderIndex);
1135 
1136                                 final ComponentName component =
1137                                         ComponentName.unflattenFromString(savedProvider);
1138 
1139                                 final boolean isIdValid = !c.hasRestoreFlag(
1140                                         LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
1141                                 final boolean wasProviderReady = !c.hasRestoreFlag(
1142                                         LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY);
1143 
1144                                 if (widgetProvidersMap == null) {
1145                                     widgetProvidersMap = AppWidgetManagerCompat
1146                                             .getInstance(mContext).getAllProvidersMap();
1147                                 }
1148                                 final AppWidgetProviderInfo provider = widgetProvidersMap.get(
1149                                         new ComponentKey(
1150                                                 ComponentName.unflattenFromString(savedProvider),
1151                                                 c.user));
1152 
1153                                 final boolean isProviderReady = isValidProvider(provider);
1154                                 if (!isSafeMode && !customWidget &&
1155                                         wasProviderReady && !isProviderReady) {
1156                                     c.markDeleted(
1157                                             "Deleting widget that isn't installed anymore: "
1158                                             + provider);
1159                                 } else {
1160                                     if (isProviderReady) {
1161                                         appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
1162                                                 provider.provider);
1163 
1164                                         // The provider is available. So the widget is either
1165                                         // available or not available. We do not need to track
1166                                         // any future restore updates.
1167                                         int status = c.restoreFlag &
1168                                                 ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
1169                                         if (!wasProviderReady) {
1170                                             // If provider was not previously ready, update the
1171                                             // status and UI flag.
1172 
1173                                             // Id would be valid only if the widget restore broadcast was received.
1174                                             if (isIdValid) {
1175                                                 status |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
1176                                             } else {
1177                                                 status &= ~LauncherAppWidgetInfo
1178                                                         .FLAG_PROVIDER_NOT_READY;
1179                                             }
1180                                         }
1181                                         appWidgetInfo.restoreStatus = status;
1182                                     } else {
1183                                         Log.v(TAG, "Widget restore pending id=" + c.id
1184                                                 + " appWidgetId=" + appWidgetId
1185                                                 + " status =" + c.restoreFlag);
1186                                         appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
1187                                                 component);
1188                                         appWidgetInfo.restoreStatus = c.restoreFlag;
1189                                         Integer installProgress = installingPkgs.get(component.getPackageName());
1190 
1191                                         if (c.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_RESTORE_STARTED)) {
1192                                             // Restore has started once.
1193                                         } else if (installProgress != null) {
1194                                             // App restore has started. Update the flag
1195                                             appWidgetInfo.restoreStatus |=
1196                                                     LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
1197                                         } else if (!isSafeMode) {
1198                                             c.markDeleted("Unrestored widget removed: " + component);
1199                                             continue;
1200                                         }
1201 
1202                                         appWidgetInfo.installProgress =
1203                                                 installProgress == null ? 0 : installProgress;
1204                                     }
1205                                     if (appWidgetInfo.hasRestoreFlag(
1206                                             LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG)) {
1207                                         appWidgetInfo.bindOptions = c.parseIntent();
1208                                     }
1209 
1210                                     c.applyCommonProperties(appWidgetInfo);
1211                                     appWidgetInfo.spanX = c.getInt(spanXIndex);
1212                                     appWidgetInfo.spanY = c.getInt(spanYIndex);
1213                                     appWidgetInfo.user = c.user;
1214 
1215                                     if (!c.isOnWorkspaceOrHotseat()) {
1216                                         c.markDeleted("Widget found where container != " +
1217                                                 "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
1218                                         continue;
1219                                     }
1220 
1221                                     if (!customWidget) {
1222                                         String providerName =
1223                                                 appWidgetInfo.providerName.flattenToString();
1224                                         if (!providerName.equals(savedProvider) ||
1225                                                 (appWidgetInfo.restoreStatus != c.restoreFlag)) {
1226                                             c.updater()
1227                                                     .put(LauncherSettings.Favorites.APPWIDGET_PROVIDER,
1228                                                             providerName)
1229                                                     .put(LauncherSettings.Favorites.RESTORED,
1230                                                             appWidgetInfo.restoreStatus)
1231                                                     .commit();
1232                                         }
1233                                     }
1234                                     c.checkAndAddItem(appWidgetInfo, sBgDataModel);
1235                                 }
1236                                 break;
1237                             }
1238                         } catch (Exception e) {
1239                             Log.e(TAG, "Desktop items loading interrupted", e);
1240                         }
1241                     }
1242                 } finally {
1243                     Utilities.closeSilently(c);
1244                 }
1245 
1246                 // Break early if we've stopped loading
1247                 if (mStopped) {
1248                     sBgDataModel.clear();
1249                     return;
1250                 }
1251 
1252                 // Remove dead items
1253                 if (c.commitDeleted()) {
1254                     // Remove any empty folder
1255                     ArrayList<Long> deletedFolderIds = (ArrayList<Long>) LauncherSettings.Settings
1256                             .call(contentResolver,
1257                                     LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS)
1258                             .getSerializable(LauncherSettings.Settings.EXTRA_VALUE);
1259                     for (long folderId : deletedFolderIds) {
1260                         sBgDataModel.workspaceItems.remove(sBgDataModel.folders.get(folderId));
1261                         sBgDataModel.folders.remove(folderId);
1262                         sBgDataModel.itemsIdMap.remove(folderId);
1263                     }
1264 
1265                     // Remove any ghost widgets
1266                     LauncherSettings.Settings.call(contentResolver,
1267                             LauncherSettings.Settings.METHOD_REMOVE_GHOST_WIDGETS);
1268                 }
1269 
1270                 // Unpin shortcuts that don't exist on the workspace.
1271                 HashSet<ShortcutKey> pendingShortcuts =
1272                         InstallShortcutReceiver.getPendingShortcuts(context);
1273                 for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) {
1274                     MutableInt numTimesPinned = sBgDataModel.pinnedShortcutCounts.get(key);
1275                     if ((numTimesPinned == null || numTimesPinned.value == 0)
1276                             && !pendingShortcuts.contains(key)) {
1277                         // Shortcut is pinned but doesn't exist on the workspace; unpin it.
1278                         shortcutManager.unpinShortcut(key);
1279                     }
1280                 }
1281 
1282                 // Sort all the folder items and make sure the first 3 items are high resolution.
1283                 for (FolderInfo folder : sBgDataModel.folders) {
1284                     Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
1285                     int pos = 0;
1286                     for (ShortcutInfo info : folder.contents) {
1287                         if (info.usingLowResIcon &&
1288                                 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
1289                             mIconCache.getTitleAndIcon(info, false);
1290                         }
1291                         pos ++;
1292                         if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
1293                             break;
1294                         }
1295                     }
1296                 }
1297 
1298                 c.commitRestoredItems();
1299                 if (!isSdCardReady && !pendingPackages.isEmpty()) {
1300                     context.registerReceiver(
1301                             new SdCardAvailableReceiver(
1302                                     LauncherModel.this, mContext, pendingPackages),
1303                             new IntentFilter(Intent.ACTION_BOOT_COMPLETED),
1304                             null,
1305                             sWorker);
1306                 }
1307 
1308                 // Remove any empty screens
1309                 ArrayList<Long> unusedScreens = new ArrayList<>(sBgDataModel.workspaceScreens);
1310                 for (ItemInfo item: sBgDataModel.itemsIdMap) {
1311                     long screenId = item.screenId;
1312                     if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1313                             unusedScreens.contains(screenId)) {
1314                         unusedScreens.remove(screenId);
1315                     }
1316                 }
1317 
1318                 // If there are any empty screens remove them, and update.
1319                 if (unusedScreens.size() != 0) {
1320                     sBgDataModel.workspaceScreens.removeAll(unusedScreens);
1321                     updateWorkspaceScreenOrder(context, sBgDataModel.workspaceScreens);
1322                 }
1323             }
1324             if (LauncherAppState.PROFILE_STARTUP) {
1325                 Trace.endSection();
1326             }
1327         }
1328 
1329         /** Filters the set of items who are directly or indirectly (via another container) on the
1330          * specified screen. */
filterCurrentWorkspaceItems(long currentScreenId, ArrayList<ItemInfo> allWorkspaceItems, ArrayList<ItemInfo> currentScreenItems, ArrayList<ItemInfo> otherScreenItems)1331         private void filterCurrentWorkspaceItems(long currentScreenId,
1332                 ArrayList<ItemInfo> allWorkspaceItems,
1333                 ArrayList<ItemInfo> currentScreenItems,
1334                 ArrayList<ItemInfo> otherScreenItems) {
1335             // Purge any null ItemInfos
1336             Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
1337             while (iter.hasNext()) {
1338                 ItemInfo i = iter.next();
1339                 if (i == null) {
1340                     iter.remove();
1341                 }
1342             }
1343 
1344             // Order the set of items by their containers first, this allows use to walk through the
1345             // list sequentially, build up a list of containers that are in the specified screen,
1346             // as well as all items in those containers.
1347             Set<Long> itemsOnScreen = new HashSet<Long>();
1348             Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
1349                 @Override
1350                 public int compare(ItemInfo lhs, ItemInfo rhs) {
1351                     return Utilities.longCompare(lhs.container, rhs.container);
1352                 }
1353             });
1354             for (ItemInfo info : allWorkspaceItems) {
1355                 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1356                     if (info.screenId == currentScreenId) {
1357                         currentScreenItems.add(info);
1358                         itemsOnScreen.add(info.id);
1359                     } else {
1360                         otherScreenItems.add(info);
1361                     }
1362                 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1363                     currentScreenItems.add(info);
1364                     itemsOnScreen.add(info.id);
1365                 } else {
1366                     if (itemsOnScreen.contains(info.container)) {
1367                         currentScreenItems.add(info);
1368                         itemsOnScreen.add(info.id);
1369                     } else {
1370                         otherScreenItems.add(info);
1371                     }
1372                 }
1373             }
1374         }
1375 
1376         /** Filters the set of widgets which are on the specified screen. */
filterCurrentAppWidgets(long currentScreenId, ArrayList<LauncherAppWidgetInfo> appWidgets, ArrayList<LauncherAppWidgetInfo> currentScreenWidgets, ArrayList<LauncherAppWidgetInfo> otherScreenWidgets)1377         private void filterCurrentAppWidgets(long currentScreenId,
1378                 ArrayList<LauncherAppWidgetInfo> appWidgets,
1379                 ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
1380                 ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
1381 
1382             for (LauncherAppWidgetInfo widget : appWidgets) {
1383                 if (widget == null) continue;
1384                 if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1385                         widget.screenId == currentScreenId) {
1386                     currentScreenWidgets.add(widget);
1387                 } else {
1388                     otherScreenWidgets.add(widget);
1389                 }
1390             }
1391         }
1392 
1393         /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
1394          * right) */
sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems)1395         private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
1396             final InvariantDeviceProfile profile = mApp.getInvariantDeviceProfile();
1397             final int screenCols = profile.numColumns;
1398             final int screenCellCount = profile.numColumns * profile.numRows;
1399             Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
1400                 @Override
1401                 public int compare(ItemInfo lhs, ItemInfo rhs) {
1402                     if (lhs.container == rhs.container) {
1403                         // Within containers, order by their spatial position in that container
1404                         switch ((int) lhs.container) {
1405                             case LauncherSettings.Favorites.CONTAINER_DESKTOP: {
1406                                 long lr = (lhs.screenId * screenCellCount +
1407                                         lhs.cellY * screenCols + lhs.cellX);
1408                                 long rr = (rhs.screenId * screenCellCount +
1409                                         rhs.cellY * screenCols + rhs.cellX);
1410                                 return Utilities.longCompare(lr, rr);
1411                             }
1412                             case LauncherSettings.Favorites.CONTAINER_HOTSEAT: {
1413                                 // We currently use the screen id as the rank
1414                                 return Utilities.longCompare(lhs.screenId, rhs.screenId);
1415                             }
1416                             default:
1417                                 if (ProviderConfig.IS_DOGFOOD_BUILD) {
1418                                     throw new RuntimeException("Unexpected container type when " +
1419                                             "sorting workspace items.");
1420                                 }
1421                                 return 0;
1422                         }
1423                     } else {
1424                         // Between containers, order by hotseat, desktop
1425                         return Utilities.longCompare(lhs.container, rhs.container);
1426                     }
1427                 }
1428             });
1429         }
1430 
bindWorkspaceScreens(final Callbacks oldCallbacks, final ArrayList<Long> orderedScreens)1431         private void bindWorkspaceScreens(final Callbacks oldCallbacks,
1432                 final ArrayList<Long> orderedScreens) {
1433             final Runnable r = new Runnable() {
1434                 @Override
1435                 public void run() {
1436                     Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1437                     if (callbacks != null) {
1438                         callbacks.bindScreens(orderedScreens);
1439                     }
1440                 }
1441             };
1442             runOnMainThread(r);
1443         }
1444 
bindWorkspaceItems(final Callbacks oldCallbacks, final ArrayList<ItemInfo> workspaceItems, final ArrayList<LauncherAppWidgetInfo> appWidgets, final Executor executor)1445         private void bindWorkspaceItems(final Callbacks oldCallbacks,
1446                 final ArrayList<ItemInfo> workspaceItems,
1447                 final ArrayList<LauncherAppWidgetInfo> appWidgets,
1448                 final Executor executor) {
1449 
1450             // Bind the workspace items
1451             int N = workspaceItems.size();
1452             for (int i = 0; i < N; i += ITEMS_CHUNK) {
1453                 final int start = i;
1454                 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
1455                 final Runnable r = new Runnable() {
1456                     @Override
1457                     public void run() {
1458                         Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1459                         if (callbacks != null) {
1460                             callbacks.bindItems(workspaceItems, start, start+chunkSize,
1461                                     false);
1462                         }
1463                     }
1464                 };
1465                 executor.execute(r);
1466             }
1467 
1468             // Bind the widgets, one at a time
1469             N = appWidgets.size();
1470             for (int i = 0; i < N; i++) {
1471                 final LauncherAppWidgetInfo widget = appWidgets.get(i);
1472                 final Runnable r = new Runnable() {
1473                     public void run() {
1474                         Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1475                         if (callbacks != null) {
1476                             callbacks.bindAppWidget(widget);
1477                         }
1478                     }
1479                 };
1480                 executor.execute(r);
1481             }
1482         }
1483 
1484         /**
1485          * Binds all loaded data to actual views on the main thread.
1486          */
bindWorkspace(int synchronizeBindPage)1487         private void bindWorkspace(int synchronizeBindPage) {
1488             final long t = SystemClock.uptimeMillis();
1489             Runnable r;
1490 
1491             // Don't use these two variables in any of the callback runnables.
1492             // Otherwise we hold a reference to them.
1493             final Callbacks oldCallbacks = mCallbacks.get();
1494             if (oldCallbacks == null) {
1495                 // This launcher has exited and nobody bothered to tell us.  Just bail.
1496                 Log.w(TAG, "LoaderTask running with no launcher");
1497                 return;
1498             }
1499 
1500             // Save a copy of all the bg-thread collections
1501             ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
1502             ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
1503             ArrayList<Long> orderedScreenIds = new ArrayList<>();
1504 
1505             synchronized (sBgDataModel) {
1506                 workspaceItems.addAll(sBgDataModel.workspaceItems);
1507                 appWidgets.addAll(sBgDataModel.appWidgets);
1508                 orderedScreenIds.addAll(sBgDataModel.workspaceScreens);
1509             }
1510 
1511             final int currentScreen;
1512             {
1513                 int currScreen = synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE
1514                         ? synchronizeBindPage : oldCallbacks.getCurrentWorkspaceScreen();
1515                 if (currScreen >= orderedScreenIds.size()) {
1516                     // There may be no workspace screens (just hotseat items and an empty page).
1517                     currScreen = PagedView.INVALID_RESTORE_PAGE;
1518                 }
1519                 currentScreen = currScreen;
1520             }
1521             final boolean validFirstPage = currentScreen >= 0;
1522             final long currentScreenId =
1523                     validFirstPage ? orderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID;
1524 
1525             // Separate the items that are on the current screen, and all the other remaining items
1526             ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
1527             ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
1528             ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
1529             ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
1530 
1531             filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
1532                     otherWorkspaceItems);
1533             filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
1534                     otherAppWidgets);
1535             sortWorkspaceItemsSpatially(currentWorkspaceItems);
1536             sortWorkspaceItemsSpatially(otherWorkspaceItems);
1537 
1538             // Tell the workspace that we're about to start binding items
1539             r = new Runnable() {
1540                 public void run() {
1541                     Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1542                     if (callbacks != null) {
1543                         callbacks.clearPendingBinds();
1544                         callbacks.startBinding();
1545                     }
1546                 }
1547             };
1548             runOnMainThread(r);
1549 
1550             bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
1551 
1552             Executor mainExecutor = new DeferredMainThreadExecutor();
1553             // Load items on the current page.
1554             bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, mainExecutor);
1555 
1556             // In case of validFirstPage, only bind the first screen, and defer binding the
1557             // remaining screens after first onDraw (and an optional the fade animation whichever
1558             // happens later).
1559             // This ensures that the first screen is immediately visible (eg. during rotation)
1560             // In case of !validFirstPage, bind all pages one after other.
1561             final Executor deferredExecutor =
1562                     validFirstPage ? new ViewOnDrawExecutor(mHandler) : mainExecutor;
1563 
1564             mainExecutor.execute(new Runnable() {
1565                 @Override
1566                 public void run() {
1567                     Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1568                     if (callbacks != null) {
1569                         callbacks.finishFirstPageBind(
1570                                 validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null);
1571                     }
1572                 }
1573             });
1574 
1575             bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, deferredExecutor);
1576 
1577             // Tell the workspace that we're done binding items
1578             r = new Runnable() {
1579                 public void run() {
1580                     Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1581                     if (callbacks != null) {
1582                         callbacks.finishBindingItems();
1583                     }
1584 
1585                     mIsLoadingAndBindingWorkspace = false;
1586 
1587                     // Run all the bind complete runnables after workspace is bound.
1588                     if (!mBindCompleteRunnables.isEmpty()) {
1589                         synchronized (mBindCompleteRunnables) {
1590                             for (final Runnable r : mBindCompleteRunnables) {
1591                                 runOnWorkerThread(r);
1592                             }
1593                             mBindCompleteRunnables.clear();
1594                         }
1595                     }
1596 
1597                     // If we're profiling, ensure this is the last thing in the queue.
1598                     if (DEBUG_LOADERS) {
1599                         Log.d(TAG, "bound workspace in "
1600                             + (SystemClock.uptimeMillis()-t) + "ms");
1601                     }
1602 
1603                 }
1604             };
1605             deferredExecutor.execute(r);
1606 
1607             if (validFirstPage) {
1608                 r = new Runnable() {
1609                     public void run() {
1610                         Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1611                         if (callbacks != null) {
1612                             // We are loading synchronously, which means, some of the pages will be
1613                             // bound after first draw. Inform the callbacks that page binding is
1614                             // not complete, and schedule the remaining pages.
1615                             if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
1616                                 callbacks.onPageBoundSynchronously(currentScreen);
1617                             }
1618                             callbacks.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor);
1619                         }
1620                     }
1621                 };
1622                 runOnMainThread(r);
1623             }
1624         }
1625 
updateIconCache()1626         private void updateIconCache() {
1627             // Ignore packages which have a promise icon.
1628             HashSet<String> packagesToIgnore = new HashSet<>();
1629             synchronized (sBgDataModel) {
1630                 for (ItemInfo info : sBgDataModel.itemsIdMap) {
1631                     if (info instanceof ShortcutInfo) {
1632                         ShortcutInfo si = (ShortcutInfo) info;
1633                         if (si.isPromise() && si.getTargetComponent() != null) {
1634                             packagesToIgnore.add(si.getTargetComponent().getPackageName());
1635                         }
1636                     } else if (info instanceof LauncherAppWidgetInfo) {
1637                         LauncherAppWidgetInfo lawi = (LauncherAppWidgetInfo) info;
1638                         if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
1639                             packagesToIgnore.add(lawi.providerName.getPackageName());
1640                         }
1641                     }
1642                 }
1643             }
1644             mIconCache.updateDbIcons(packagesToIgnore);
1645         }
1646 
onlyBindAllApps()1647         private void onlyBindAllApps() {
1648             final Callbacks oldCallbacks = mCallbacks.get();
1649             if (oldCallbacks == null) {
1650                 // This launcher has exited and nobody bothered to tell us.  Just bail.
1651                 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
1652                 return;
1653             }
1654 
1655             // shallow copy
1656             @SuppressWarnings("unchecked")
1657             final ArrayList<AppInfo> list
1658                     = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
1659             Runnable r = new Runnable() {
1660                 public void run() {
1661                     final long t = SystemClock.uptimeMillis();
1662                     final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1663                     if (callbacks != null) {
1664                         callbacks.bindAllApplications(list);
1665                     }
1666                     if (DEBUG_LOADERS) {
1667                         Log.d(TAG, "bound all " + list.size() + " apps from cache in "
1668                                 + (SystemClock.uptimeMillis() - t) + "ms");
1669                     }
1670                 }
1671             };
1672             runOnMainThread(r);
1673         }
1674 
scheduleManagedHeuristicRunnable(final ManagedProfileHeuristic heuristic, final UserHandle user, final List<LauncherActivityInfo> apps)1675         private void scheduleManagedHeuristicRunnable(final ManagedProfileHeuristic heuristic,
1676                 final UserHandle user, final List<LauncherActivityInfo> apps) {
1677             if (heuristic != null) {
1678                 // Assume the app lists now is updated.
1679                 mIsManagedHeuristicAppsUpdated = false;
1680                 final Runnable managedHeuristicRunnable = new Runnable() {
1681                     @Override
1682                     public void run() {
1683                         if (mIsManagedHeuristicAppsUpdated) {
1684                             // If app list is updated, we need to reschedule it otherwise old app
1685                             // list will override everything in processUserApps().
1686                             sWorker.post(new Runnable() {
1687                                 public void run() {
1688                                     final List<LauncherActivityInfo> updatedApps =
1689                                             mLauncherApps.getActivityList(null, user);
1690                                     scheduleManagedHeuristicRunnable(heuristic, user,
1691                                             updatedApps);
1692                                 }
1693                             });
1694                         } else {
1695                             heuristic.processUserApps(apps);
1696                         }
1697                     }
1698                 };
1699                 runOnMainThread(new Runnable() {
1700                     @Override
1701                     public void run() {
1702                         // Check isLoadingWorkspace on the UI thread, as it is updated on the UI
1703                         // thread.
1704                         if (mIsLoadingAndBindingWorkspace) {
1705                             synchronized (mBindCompleteRunnables) {
1706                                 mBindCompleteRunnables.add(managedHeuristicRunnable);
1707                             }
1708                         } else {
1709                             runOnWorkerThread(managedHeuristicRunnable);
1710                         }
1711                     }
1712                 });
1713             }
1714         }
1715 
loadAllApps()1716         private void loadAllApps() {
1717             final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1718 
1719             final Callbacks oldCallbacks = mCallbacks.get();
1720             if (oldCallbacks == null) {
1721                 // This launcher has exited and nobody bothered to tell us.  Just bail.
1722                 Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
1723                 return;
1724             }
1725 
1726             final List<UserHandle> profiles = mUserManager.getUserProfiles();
1727 
1728             // Clear the list of apps
1729             mBgAllAppsList.clear();
1730             for (UserHandle user : profiles) {
1731                 // Query for the set of apps
1732                 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1733                 final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user);
1734                 if (DEBUG_LOADERS) {
1735                     Log.d(TAG, "getActivityList took "
1736                             + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
1737                     Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
1738                 }
1739                 // Fail if we don't have any apps
1740                 // TODO: Fix this. Only fail for the current user.
1741                 if (apps == null || apps.isEmpty()) {
1742                     return;
1743                 }
1744                 boolean quietMode = mUserManager.isQuietModeEnabled(user);
1745                 // Create the ApplicationInfos
1746                 for (int i = 0; i < apps.size(); i++) {
1747                     LauncherActivityInfo app = apps.get(i);
1748                     // This builds the icon bitmaps.
1749                     mBgAllAppsList.add(new AppInfo(app, user, quietMode), app);
1750                 }
1751                 final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
1752                 if (heuristic != null) {
1753                     scheduleManagedHeuristicRunnable(heuristic, user, apps);
1754                 }
1755             }
1756             // Huh? Shouldn't this be inside the Runnable below?
1757             final ArrayList<AppInfo> added = mBgAllAppsList.added;
1758             mBgAllAppsList.added = new ArrayList<AppInfo>();
1759 
1760             // Post callback on main thread
1761             mHandler.post(new Runnable() {
1762                 public void run() {
1763 
1764                     final long bindTime = SystemClock.uptimeMillis();
1765                     final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1766                     if (callbacks != null) {
1767                         callbacks.bindAllApplications(added);
1768                         if (DEBUG_LOADERS) {
1769                             Log.d(TAG, "bound " + added.size() + " apps in "
1770                                     + (SystemClock.uptimeMillis() - bindTime) + "ms");
1771                         }
1772                     } else {
1773                         Log.i(TAG, "not binding apps: no Launcher activity");
1774                     }
1775                 }
1776             });
1777             // Cleanup any data stored for a deleted user.
1778             ManagedProfileHeuristic.processAllUsers(profiles, mContext);
1779             if (DEBUG_LOADERS) {
1780                 Log.d(TAG, "Icons processed in "
1781                         + (SystemClock.uptimeMillis() - loadTime) + "ms");
1782             }
1783         }
1784 
loadDeepShortcuts()1785         private void loadDeepShortcuts() {
1786             sBgDataModel.deepShortcutMap.clear();
1787             DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(mContext);
1788             mHasShortcutHostPermission = shortcutManager.hasHostPermission();
1789             if (mHasShortcutHostPermission) {
1790                 for (UserHandle user : mUserManager.getUserProfiles()) {
1791                     if (mUserManager.isUserUnlocked(user)) {
1792                         List<ShortcutInfoCompat> shortcuts =
1793                                 shortcutManager.queryForAllShortcuts(user);
1794                         sBgDataModel.updateDeepShortcutMap(null, user, shortcuts);
1795                     }
1796                 }
1797             }
1798         }
1799     }
1800 
bindDeepShortcuts()1801     public void bindDeepShortcuts() {
1802         final MultiHashMap<ComponentKey, String> shortcutMapCopy =
1803                 sBgDataModel.deepShortcutMap.clone();
1804         Runnable r = new Runnable() {
1805             @Override
1806             public void run() {
1807                 Callbacks callbacks = getCallback();
1808                 if (callbacks != null) {
1809                     callbacks.bindDeepShortcutMap(shortcutMapCopy);
1810                 }
1811             }
1812         };
1813         runOnMainThread(r);
1814     }
1815 
1816     /**
1817      * Refreshes the cached shortcuts if the shortcut permission has changed.
1818      * Current implementation simply reloads the workspace, but it can be optimized to
1819      * use partial updates similar to {@link UserManagerCompat}
1820      */
refreshShortcutsIfRequired()1821     public void refreshShortcutsIfRequired() {
1822         if (Utilities.ATLEAST_NOUGAT_MR1) {
1823             sWorker.removeCallbacks(mShortcutPermissionCheckRunnable);
1824             sWorker.post(mShortcutPermissionCheckRunnable);
1825         }
1826     }
1827 
1828     /**
1829      * Called when the icons for packages have been updated in the icon cache.
1830      */
onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandle user)1831     public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandle user) {
1832         // If any package icon has changed (app was updated while launcher was dead),
1833         // update the corresponding shortcuts.
1834         enqueueModelUpdateTask(new CacheDataUpdatedTask(
1835                 CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages));
1836     }
1837 
enqueueModelUpdateTask(BaseModelUpdateTask task)1838     void enqueueModelUpdateTask(BaseModelUpdateTask task) {
1839         if (!mModelLoaded && mLoaderTask == null) {
1840             if (DEBUG_LOADERS) {
1841                 Log.d(TAG, "enqueueModelUpdateTask Ignoring task since loader is pending=" + task);
1842             }
1843             return;
1844         }
1845         task.init(this);
1846         runOnWorkerThread(task);
1847     }
1848 
1849     /**
1850      * A task to be executed on the current callbacks on the UI thread.
1851      * If there is no current callbacks, the task is ignored.
1852      */
1853     public interface CallbackTask {
1854 
execute(Callbacks callbacks)1855         void execute(Callbacks callbacks);
1856     }
1857 
1858     /**
1859      * A runnable which changes/updates the data model of the launcher based on certain events.
1860      */
1861     public static abstract class BaseModelUpdateTask implements Runnable {
1862 
1863         private LauncherModel mModel;
1864         private DeferredHandler mUiHandler;
1865 
1866         /* package private */
init(LauncherModel model)1867         void init(LauncherModel model) {
1868             mModel = model;
1869             mUiHandler = mModel.mHandler;
1870         }
1871 
1872         @Override
run()1873         public void run() {
1874             if (!mModel.mHasLoaderCompletedOnce) {
1875                 // Loader has not yet run.
1876                 return;
1877             }
1878             execute(mModel.mApp, sBgDataModel, mModel.mBgAllAppsList);
1879         }
1880 
1881         /**
1882          * Execute the actual task. Called on the worker thread.
1883          */
execute( LauncherAppState app, BgDataModel dataModel, AllAppsList apps)1884         public abstract void execute(
1885                 LauncherAppState app, BgDataModel dataModel, AllAppsList apps);
1886 
1887         /**
1888          * Schedules a {@param task} to be executed on the current callbacks.
1889          */
scheduleCallbackTask(final CallbackTask task)1890         public final void scheduleCallbackTask(final CallbackTask task) {
1891             final Callbacks callbacks = mModel.getCallback();
1892             mUiHandler.post(new Runnable() {
1893                 public void run() {
1894                     Callbacks cb = mModel.getCallback();
1895                     if (callbacks == cb && cb != null) {
1896                         task.execute(callbacks);
1897                     }
1898                 }
1899             });
1900         }
1901 
getModelWriter()1902         public ModelWriter getModelWriter() {
1903             // Updates from model task, do not deal with icon position in hotseat.
1904             return mModel.getWriter(false /* hasVerticalHotseat */);
1905         }
1906     }
1907 
updateAndBindShortcutInfo(final ShortcutInfo si, final ShortcutInfoCompat info)1908     public void updateAndBindShortcutInfo(final ShortcutInfo si, final ShortcutInfoCompat info) {
1909         updateAndBindShortcutInfo(new Provider<ShortcutInfo>() {
1910             @Override
1911             public ShortcutInfo get() {
1912                 si.updateFromDeepShortcutInfo(info, mApp.getContext());
1913                 si.iconBitmap = LauncherIcons.createShortcutIcon(info, mApp.getContext());
1914                 return si;
1915             }
1916         });
1917     }
1918 
1919     /**
1920      * Utility method to update a shortcut on the background thread.
1921      */
updateAndBindShortcutInfo(final Provider<ShortcutInfo> shortcutProvider)1922     public void updateAndBindShortcutInfo(final Provider<ShortcutInfo> shortcutProvider) {
1923         enqueueModelUpdateTask(new ExtendedModelTask() {
1924             @Override
1925             public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
1926                 ShortcutInfo info = shortcutProvider.get();
1927                 ArrayList<ShortcutInfo> update = new ArrayList<>();
1928                 update.add(info);
1929                 bindUpdatedShortcuts(update, info.user);
1930             }
1931         });
1932     }
1933 
bindWidgetsModel(final Callbacks callbacks)1934     private void bindWidgetsModel(final Callbacks callbacks) {
1935         final MultiHashMap<PackageItemInfo, WidgetItem> widgets
1936                 = mBgWidgetsModel.getWidgetsMap().clone();
1937         mHandler.post(new Runnable() {
1938             @Override
1939             public void run() {
1940                 Callbacks cb = getCallback();
1941                 if (callbacks == cb && cb != null) {
1942                     callbacks.bindAllWidgets(widgets);
1943                 }
1944             }
1945         });
1946     }
1947 
refreshAndBindWidgetsAndShortcuts(final Callbacks callbacks, final boolean bindFirst, @Nullable final PackageUserKey packageUser)1948     public void refreshAndBindWidgetsAndShortcuts(final Callbacks callbacks,
1949             final boolean bindFirst, @Nullable final PackageUserKey packageUser) {
1950         runOnWorkerThread(new Runnable() {
1951             @Override
1952             public void run() {
1953                 if (bindFirst && !mBgWidgetsModel.isEmpty()) {
1954                     bindWidgetsModel(callbacks);
1955                 }
1956                 ArrayList<WidgetItem> widgets = mBgWidgetsModel.update(
1957                         mApp.getContext(), packageUser);
1958                 bindWidgetsModel(callbacks);
1959 
1960                 // update the Widget entries inside DB on the worker thread.
1961                 mApp.getWidgetCache().removeObsoletePreviews(widgets, packageUser);
1962             }
1963         });
1964     }
1965 
isValidProvider(AppWidgetProviderInfo provider)1966     static boolean isValidProvider(AppWidgetProviderInfo provider) {
1967         return (provider != null) && (provider.provider != null)
1968                 && (provider.provider.getPackageName() != null);
1969     }
1970 
dumpState(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)1971     public void dumpState(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
1972         if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
1973             writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size());
1974             for (AppInfo info : mBgAllAppsList.data) {
1975                 writer.println(prefix + "   title=\"" + info.title + "\" iconBitmap=" + info.iconBitmap
1976                         + " componentName=" + info.componentName.getPackageName());
1977             }
1978         }
1979         sBgDataModel.dump(prefix, fd, writer, args);
1980     }
1981 
getCallback()1982     public Callbacks getCallback() {
1983         return mCallbacks != null ? mCallbacks.get() : null;
1984     }
1985 
1986     /**
1987      * @return {@link FolderInfo} if its already loaded.
1988      */
findFolderById(Long folderId)1989     public FolderInfo findFolderById(Long folderId) {
1990         synchronized (sBgDataModel) {
1991             return sBgDataModel.folders.get(folderId);
1992         }
1993     }
1994 
1995     @Thunk class DeferredMainThreadExecutor implements Executor {
1996 
1997         @Override
execute(Runnable command)1998         public void execute(Runnable command) {
1999             runOnMainThread(command);
2000         }
2001     }
2002 
2003     /**
2004      * @return the looper for the worker thread which can be used to start background tasks.
2005      */
getWorkerLooper()2006     public static Looper getWorkerLooper() {
2007         return sWorkerThread.getLooper();
2008     }
2009 }
2010