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