OSDN Git Service

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