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