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