OSDN Git Service

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