OSDN Git Service

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