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