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