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