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