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