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