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