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.appwidget.AppWidgetManager;
     20 import android.appwidget.AppWidgetProviderInfo;
     21 import android.content.BroadcastReceiver;
     22 import android.content.ComponentName;
     23 import android.content.ContentProviderClient;
     24 import android.content.ContentResolver;
     25 import android.content.ContentValues;
     26 import android.content.Intent;
     27 import android.content.Intent.ShortcutIconResource;
     28 import android.content.Context;
     29 import android.content.pm.ActivityInfo;
     30 import android.content.pm.PackageManager;
     31 import android.content.pm.ProviderInfo;
     32 import android.content.pm.ResolveInfo;
     33 import android.content.res.Resources;
     34 import android.database.Cursor;
     35 import android.graphics.Bitmap;
     36 import android.graphics.BitmapFactory;
     37 import android.net.Uri;
     38 import android.os.Handler;
     39 import android.os.HandlerThread;
     40 import android.os.Parcelable;
     41 import android.os.RemoteException;
     42 import android.util.Log;
     43 import android.os.Process;
     44 import android.os.SystemClock;
     45 
     46 import java.lang.ref.WeakReference;
     47 import java.net.URISyntaxException;
     48 import java.text.Collator;
     49 import java.util.ArrayList;
     50 import java.util.Arrays;
     51 import java.util.Collections;
     52 import java.util.Comparator;
     53 import java.util.HashMap;
     54 import java.util.List;
     55 
     56 import com.android.launcher.R;
     57 
     58 /**
     59  * Maintains in-memory state of the Launcher. It is expected that there should be only one
     60  * LauncherModel object held in a static. Also provide APIs for updating the database state
     61  * for the Launcher.
     62  */
     63 public class LauncherModel extends BroadcastReceiver {
     64     static final boolean DEBUG_LOADERS = false;
     65     static final String TAG = "Launcher.Model";
     66 
     67     private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
     68     private int mBatchSize; // 0 is all apps at once
     69     private int mAllAppsLoadDelay; // milliseconds between batches
     70 
     71     private final LauncherApplication mApp;
     72     private final Object mLock = new Object();
     73     private DeferredHandler mHandler = new DeferredHandler();
     74     private LoaderTask mLoaderTask;
     75 
     76     private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
     77     static {
     78         sWorkerThread.start();
     79     }
     80     private static final Handler sWorker = new Handler(sWorkerThread.getLooper());
     81 
     82     // We start off with everything not loaded.  After that, we assume that
     83     // our monitoring of the package manager provides all updates and we never
     84     // need to do a requery.  These are only ever touched from the loader thread.
     85     private boolean mWorkspaceLoaded;
     86     private boolean mAllAppsLoaded;
     87 
     88     private WeakReference<Callbacks> mCallbacks;
     89 
     90     private AllAppsList mAllAppsList; // only access in worker thread
     91     private IconCache mIconCache;
     92     final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
     93     final ArrayList<LauncherAppWidgetInfo> mAppWidgets = new ArrayList<LauncherAppWidgetInfo>();
     94     final HashMap<Long, FolderInfo> mFolders = new HashMap<Long, FolderInfo>();
     95 
     96     private Bitmap mDefaultIcon;
     97 
     98     public interface Callbacks {
     99         public boolean setLoadOnResume();
    100         public int getCurrentWorkspaceScreen();
    101         public void startBinding();
    102         public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end);
    103         public void bindFolders(HashMap<Long,FolderInfo> folders);
    104         public void finishBindingItems();
    105         public void bindAppWidget(LauncherAppWidgetInfo info);
    106         public void bindAllApplications(ArrayList<ApplicationInfo> apps);
    107         public void bindAppsAdded(ArrayList<ApplicationInfo> apps);
    108         public void bindAppsUpdated(ArrayList<ApplicationInfo> apps);
    109         public void bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent);
    110         public boolean isAllAppsVisible();
    111     }
    112 
    113     LauncherModel(LauncherApplication app, IconCache iconCache) {
    114         mApp = app;
    115         mAllAppsList = new AllAppsList(iconCache);
    116         mIconCache = iconCache;
    117 
    118         mDefaultIcon = Utilities.createIconBitmap(
    119                 app.getPackageManager().getDefaultActivityIcon(), app);
    120 
    121         mAllAppsLoadDelay = app.getResources().getInteger(R.integer.config_allAppsBatchLoadDelay);
    122 
    123         mBatchSize = app.getResources().getInteger(R.integer.config_allAppsBatchSize);
    124     }
    125 
    126     public Bitmap getFallbackIcon() {
    127         return Bitmap.createBitmap(mDefaultIcon);
    128     }
    129 
    130     /**
    131      * Adds an item to the DB if it was not created previously, or move it to a new
    132      * <container, screen, cellX, cellY>
    133      */
    134     static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
    135             int screen, int cellX, int cellY) {
    136         if (item.container == ItemInfo.NO_ID) {
    137             // From all apps
    138             addItemToDatabase(context, item, container, screen, cellX, cellY, false);
    139         } else {
    140             // From somewhere else
    141             moveItemInDatabase(context, item, container, screen, cellX, cellY);
    142         }
    143     }
    144 
    145     /**
    146      * Move an item in the DB to a new <container, screen, cellX, cellY>
    147      */
    148     static void moveItemInDatabase(Context context, ItemInfo item, long container, int screen,
    149             int cellX, int cellY) {
    150         item.container = container;
    151         item.screen = screen;
    152         item.cellX = cellX;
    153         item.cellY = cellY;
    154 
    155         final Uri uri = LauncherSettings.Favorites.getContentUri(item.id, false);
    156         final ContentValues values = new ContentValues();
    157         final ContentResolver cr = context.getContentResolver();
    158 
    159         values.put(LauncherSettings.Favorites.CONTAINER, item.container);
    160         values.put(LauncherSettings.Favorites.CELLX, item.cellX);
    161         values.put(LauncherSettings.Favorites.CELLY, item.cellY);
    162         values.put(LauncherSettings.Favorites.SCREEN, item.screen);
    163 
    164         sWorker.post(new Runnable() {
    165                 public void run() {
    166                     cr.update(uri, values, null, null);
    167                 }
    168             });
    169     }
    170 
    171     /**
    172      * Returns true if the shortcuts already exists in the database.
    173      * we identify a shortcut by its title and intent.
    174      */
    175     static boolean shortcutExists(Context context, String title, Intent intent) {
    176         final ContentResolver cr = context.getContentResolver();
    177         Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
    178             new String[] { "title", "intent" }, "title=? and intent=?",
    179             new String[] { title, intent.toUri(0) }, null);
    180         boolean result = false;
    181         try {
    182             result = c.moveToFirst();
    183         } finally {
    184             c.close();
    185         }
    186         return result;
    187     }
    188 
    189     /**
    190      * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList.
    191      */
    192     FolderInfo getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id) {
    193         final ContentResolver cr = context.getContentResolver();
    194         Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null,
    195                 "_id=? and (itemType=? or itemType=?)",
    196                 new String[] { String.valueOf(id),
    197                         String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER),
    198                         String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER) }, null);
    199 
    200         try {
    201             if (c.moveToFirst()) {
    202                 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
    203                 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
    204                 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
    205                 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
    206                 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
    207                 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
    208 
    209                 FolderInfo folderInfo = null;
    210                 switch (c.getInt(itemTypeIndex)) {
    211                     case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
    212                         folderInfo = findOrMakeUserFolder(folderList, id);
    213                         break;
    214                     case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER:
    215                         folderInfo = findOrMakeLiveFolder(folderList, id);
    216                         break;
    217                 }
    218 
    219                 folderInfo.title = c.getString(titleIndex);
    220                 folderInfo.id = id;
    221                 folderInfo.container = c.getInt(containerIndex);
    222                 folderInfo.screen = c.getInt(screenIndex);
    223                 folderInfo.cellX = c.getInt(cellXIndex);
    224                 folderInfo.cellY = c.getInt(cellYIndex);
    225 
    226                 return folderInfo;
    227             }
    228         } finally {
    229             c.close();
    230         }
    231 
    232         return null;
    233     }
    234 
    235     /**
    236      * Add an item to the database in a specified container. Sets the container, screen, cellX and
    237      * cellY fields of the item. Also assigns an ID to the item.
    238      */
    239     static void addItemToDatabase(Context context, ItemInfo item, long container,
    240             int screen, int cellX, int cellY, boolean notify) {
    241         item.container = container;
    242         item.screen = screen;
    243         item.cellX = cellX;
    244         item.cellY = cellY;
    245 
    246         final ContentValues values = new ContentValues();
    247         final ContentResolver cr = context.getContentResolver();
    248 
    249         item.onAddToDatabase(values);
    250 
    251         Uri result = cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI :
    252                 LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values);
    253 
    254         if (result != null) {
    255             item.id = Integer.parseInt(result.getPathSegments().get(1));
    256         }
    257     }
    258 
    259     /**
    260      * Update an item to the database in a specified container.
    261      */
    262     static void updateItemInDatabase(Context context, ItemInfo item) {
    263         final ContentValues values = new ContentValues();
    264         final ContentResolver cr = context.getContentResolver();
    265 
    266         item.onAddToDatabase(values);
    267 
    268         cr.update(LauncherSettings.Favorites.getContentUri(item.id, false), values, null, null);
    269     }
    270 
    271     /**
    272      * Removes the specified item from the database
    273      * @param context
    274      * @param item
    275      */
    276     static void deleteItemFromDatabase(Context context, ItemInfo item) {
    277         final ContentResolver cr = context.getContentResolver();
    278         final Uri uriToDelete = LauncherSettings.Favorites.getContentUri(item.id, false);
    279         sWorker.post(new Runnable() {
    280                 public void run() {
    281                     cr.delete(uriToDelete, null, null);
    282                 }
    283             });
    284     }
    285 
    286     /**
    287      * Remove the contents of the specified folder from the database
    288      */
    289     static void deleteUserFolderContentsFromDatabase(Context context, UserFolderInfo info) {
    290         final ContentResolver cr = context.getContentResolver();
    291 
    292         cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null);
    293         cr.delete(LauncherSettings.Favorites.CONTENT_URI,
    294                 LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
    295     }
    296 
    297     /**
    298      * Set this as the current Launcher activity object for the loader.
    299      */
    300     public void initialize(Callbacks callbacks) {
    301         synchronized (mLock) {
    302             mCallbacks = new WeakReference<Callbacks>(callbacks);
    303         }
    304     }
    305 
    306     /**
    307      * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
    308      * ACTION_PACKAGE_CHANGED.
    309      */
    310     public void onReceive(Context context, Intent intent) {
    311         if (DEBUG_LOADERS) Log.d(TAG, "onReceive intent=" + intent);
    312 
    313         final String action = intent.getAction();
    314 
    315         if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
    316                 || Intent.ACTION_PACKAGE_REMOVED.equals(action)
    317                 || Intent.ACTION_PACKAGE_ADDED.equals(action)) {
    318             final String packageName = intent.getData().getSchemeSpecificPart();
    319             final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
    320 
    321             int op = PackageUpdatedTask.OP_NONE;
    322 
    323             if (packageName == null || packageName.length() == 0) {
    324                 // they sent us a bad intent
    325                 return;
    326             }
    327 
    328             if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
    329                 op = PackageUpdatedTask.OP_UPDATE;
    330             } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
    331                 if (!replacing) {
    332                     op = PackageUpdatedTask.OP_REMOVE;
    333                 }
    334                 // else, we are replacing the package, so a PACKAGE_ADDED will be sent
    335                 // later, we will update the package at this time
    336             } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
    337                 if (!replacing) {
    338                     op = PackageUpdatedTask.OP_ADD;
    339                 } else {
    340                     op = PackageUpdatedTask.OP_UPDATE;
    341                 }
    342             }
    343 
    344             if (op != PackageUpdatedTask.OP_NONE) {
    345                 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName }));
    346             }
    347 
    348         } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
    349             // First, schedule to add these apps back in.
    350             String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
    351             enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packages));
    352             // Then, rebind everything.
    353             boolean runLoader = true;
    354             if (mCallbacks != null) {
    355                 Callbacks callbacks = mCallbacks.get();
    356                 if (callbacks != null) {
    357                     // If they're paused, we can skip loading, because they'll do it again anyway
    358                     if (callbacks.setLoadOnResume()) {
    359                         runLoader = false;
    360                     }
    361                 }
    362             }
    363             if (runLoader) {
    364                 startLoader(mApp, false);
    365             }
    366 
    367         } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
    368             String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
    369             enqueuePackageUpdated(new PackageUpdatedTask(
    370                         PackageUpdatedTask.OP_UNAVAILABLE, packages));
    371 
    372         }
    373     }
    374 
    375     public void startLoader(Context context, boolean isLaunching) {
    376         synchronized (mLock) {
    377             if (DEBUG_LOADERS) {
    378                 Log.d(TAG, "startLoader isLaunching=" + isLaunching);
    379             }
    380 
    381             // Don't bother to start the thread if we know it's not going to do anything
    382             if (mCallbacks != null && mCallbacks.get() != null) {
    383                 // If there is already one running, tell it to stop.
    384                 LoaderTask oldTask = mLoaderTask;
    385                 if (oldTask != null) {
    386                     if (oldTask.isLaunching()) {
    387                         // don't downgrade isLaunching if we're already running
    388                         isLaunching = true;
    389                     }
    390                     oldTask.stopLocked();
    391                 }
    392                 mLoaderTask = new LoaderTask(context, isLaunching);
    393                 sWorker.post(mLoaderTask);
    394             }
    395         }
    396     }
    397 
    398     public void stopLoader() {
    399         synchronized (mLock) {
    400             if (mLoaderTask != null) {
    401                 mLoaderTask.stopLocked();
    402             }
    403         }
    404     }
    405 
    406     /**
    407      * Runnable for the thread that loads the contents of the launcher:
    408      *   - workspace icons
    409      *   - widgets
    410      *   - all apps icons
    411      */
    412     private class LoaderTask implements Runnable {
    413         private Context mContext;
    414         private Thread mWaitThread;
    415         private boolean mIsLaunching;
    416         private boolean mStopped;
    417         private boolean mLoadAndBindStepFinished;
    418 
    419         LoaderTask(Context context, boolean isLaunching) {
    420             mContext = context;
    421             mIsLaunching = isLaunching;
    422         }
    423 
    424         boolean isLaunching() {
    425             return mIsLaunching;
    426         }
    427 
    428         private void loadAndBindWorkspace() {
    429             // Load the workspace
    430 
    431             // For now, just always reload the workspace.  It's ~100 ms vs. the
    432             // binding which takes many hundreds of ms.
    433             // We can reconsider.
    434             if (DEBUG_LOADERS) {
    435                 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
    436             }
    437             if (true || !mWorkspaceLoaded) {
    438                 loadWorkspace();
    439                 if (mStopped) {
    440                     return;
    441                 }
    442                 mWorkspaceLoaded = true;
    443             }
    444 
    445             // Bind the workspace
    446             bindWorkspace();
    447         }
    448 
    449         private void waitForIdle() {
    450             // Wait until the either we're stopped or the other threads are done.
    451             // This way we don't start loading all apps until the workspace has settled
    452             // down.
    453             synchronized (LoaderTask.this) {
    454                 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
    455 
    456                 mHandler.postIdle(new Runnable() {
    457                         public void run() {
    458                             synchronized (LoaderTask.this) {
    459                                 mLoadAndBindStepFinished = true;
    460                                 if (DEBUG_LOADERS) {
    461                                     Log.d(TAG, "done with previous binding step");
    462                                 }
    463                                 LoaderTask.this.notify();
    464                             }
    465                         }
    466                     });
    467 
    468                 while (!mStopped && !mLoadAndBindStepFinished) {
    469                     try {
    470                         this.wait();
    471                     } catch (InterruptedException ex) {
    472                         // Ignore
    473                     }
    474                 }
    475                 if (DEBUG_LOADERS) {
    476                     Log.d(TAG, "waited "
    477                             + (SystemClock.uptimeMillis()-workspaceWaitTime)
    478                             + "ms for previous step to finish binding");
    479                 }
    480             }
    481         }
    482 
    483         public void run() {
    484             // Optimize for end-user experience: if the Launcher is up and // running with the
    485             // All Apps interface in the foreground, load All Apps first. Otherwise, load the
    486             // workspace first (default).
    487             final Callbacks cbk = mCallbacks.get();
    488             final boolean loadWorkspaceFirst = cbk != null ? (!cbk.isAllAppsVisible()) : true;
    489 
    490             keep_running: {
    491                 // Elevate priority when Home launches for the first time to avoid
    492                 // starving at boot time. Staring at a blank home is not cool.
    493                 synchronized (mLock) {
    494                     android.os.Process.setThreadPriority(mIsLaunching
    495                             ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
    496                 }
    497 
    498                 if (loadWorkspaceFirst) {
    499                     if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
    500                     loadAndBindWorkspace();
    501                 } else {
    502                     if (DEBUG_LOADERS) Log.d(TAG, "step 1: special: loading all apps");
    503                     loadAndBindAllApps();
    504                 }
    505 
    506                 if (mStopped) {
    507                     break keep_running;
    508                 }
    509 
    510                 // Whew! Hard work done.  Slow us down, and wait until the UI thread has
    511                 // settled down.
    512                 synchronized (mLock) {
    513                     if (mIsLaunching) {
    514                         android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    515                     }
    516                 }
    517                 waitForIdle();
    518 
    519                 // second step
    520                 if (loadWorkspaceFirst) {
    521                     if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
    522                     loadAndBindAllApps();
    523                 } else {
    524                     if (DEBUG_LOADERS) Log.d(TAG, "step 2: special: loading workspace");
    525                     loadAndBindWorkspace();
    526                 }
    527             }
    528 
    529             // Clear out this reference, otherwise we end up holding it until all of the
    530             // callback runnables are done.
    531             mContext = null;
    532 
    533             synchronized (mLock) {
    534                 // If we are still the last one to be scheduled, remove ourselves.
    535                 if (mLoaderTask == this) {
    536                     mLoaderTask = null;
    537                 }
    538             }
    539 
    540             // Trigger a gc to try to clean up after the stuff is done, since the
    541             // renderscript allocations aren't charged to the java heap.
    542             if (mStopped) {
    543                 mHandler.post(new Runnable() {
    544                         public void run() {
    545                             System.gc();
    546                         }
    547                     });
    548             } else {
    549                 mHandler.postIdle(new Runnable() {
    550                         public void run() {
    551                             System.gc();
    552                         }
    553                     });
    554             }
    555         }
    556 
    557         public void stopLocked() {
    558             synchronized (LoaderTask.this) {
    559                 mStopped = true;
    560                 this.notify();
    561             }
    562         }
    563 
    564         /**
    565          * Gets the callbacks object.  If we've been stopped, or if the launcher object
    566          * has somehow been garbage collected, return null instead.  Pass in the Callbacks
    567          * object that was around when the deferred message was scheduled, and if there's
    568          * a new Callbacks object around then also return null.  This will save us from
    569          * calling onto it with data that will be ignored.
    570          */
    571         Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
    572             synchronized (mLock) {
    573                 if (mStopped) {
    574                     return null;
    575                 }
    576 
    577                 if (mCallbacks == null) {
    578                     return null;
    579                 }
    580 
    581                 final Callbacks callbacks = mCallbacks.get();
    582                 if (callbacks != oldCallbacks) {
    583                     return null;
    584                 }
    585                 if (callbacks == null) {
    586                     Log.w(TAG, "no mCallbacks");
    587                     return null;
    588                 }
    589 
    590                 return callbacks;
    591             }
    592         }
    593 
    594         // check & update map of what's occupied; used to discard overlapping/invalid items
    595         private boolean checkItemPlacement(ItemInfo occupied[][][], ItemInfo item) {
    596             if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
    597                 return true;
    598             }
    599 
    600             for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
    601                 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
    602                     if (occupied[item.screen][x][y] != null) {
    603                         Log.e(TAG, "Error loading shortcut " + item
    604                             + " into cell (" + item.screen + ":"
    605                             + x + "," + y
    606                             + ") occupied by "
    607                             + occupied[item.screen][x][y]);
    608                         return false;
    609                     }
    610                 }
    611             }
    612             for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
    613                 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
    614                     occupied[item.screen][x][y] = item;
    615                 }
    616             }
    617             return true;
    618         }
    619 
    620         private void loadWorkspace() {
    621             final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
    622 
    623             final Context context = mContext;
    624             final ContentResolver contentResolver = context.getContentResolver();
    625             final PackageManager manager = context.getPackageManager();
    626             final AppWidgetManager widgets = AppWidgetManager.getInstance(context);
    627             final boolean isSafeMode = manager.isSafeMode();
    628 
    629             mItems.clear();
    630             mAppWidgets.clear();
    631             mFolders.clear();
    632 
    633             final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
    634 
    635             final Cursor c = contentResolver.query(
    636                     LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
    637 
    638             final ItemInfo occupied[][][] = new ItemInfo[Launcher.SCREEN_COUNT][Launcher.NUMBER_CELLS_X][Launcher.NUMBER_CELLS_Y];
    639 
    640             try {
    641                 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
    642                 final int intentIndex = c.getColumnIndexOrThrow
    643                         (LauncherSettings.Favorites.INTENT);
    644                 final int titleIndex = c.getColumnIndexOrThrow
    645                         (LauncherSettings.Favorites.TITLE);
    646                 final int iconTypeIndex = c.getColumnIndexOrThrow(
    647                         LauncherSettings.Favorites.ICON_TYPE);
    648                 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
    649                 final int iconPackageIndex = c.getColumnIndexOrThrow(
    650                         LauncherSettings.Favorites.ICON_PACKAGE);
    651                 final int iconResourceIndex = c.getColumnIndexOrThrow(
    652                         LauncherSettings.Favorites.ICON_RESOURCE);
    653                 final int containerIndex = c.getColumnIndexOrThrow(
    654                         LauncherSettings.Favorites.CONTAINER);
    655                 final int itemTypeIndex = c.getColumnIndexOrThrow(
    656                         LauncherSettings.Favorites.ITEM_TYPE);
    657                 final int appWidgetIdIndex = c.getColumnIndexOrThrow(
    658                         LauncherSettings.Favorites.APPWIDGET_ID);
    659                 final int screenIndex = c.getColumnIndexOrThrow(
    660                         LauncherSettings.Favorites.SCREEN);
    661                 final int cellXIndex = c.getColumnIndexOrThrow
    662                         (LauncherSettings.Favorites.CELLX);
    663                 final int cellYIndex = c.getColumnIndexOrThrow
    664                         (LauncherSettings.Favorites.CELLY);
    665                 final int spanXIndex = c.getColumnIndexOrThrow
    666                         (LauncherSettings.Favorites.SPANX);
    667                 final int spanYIndex = c.getColumnIndexOrThrow(
    668                         LauncherSettings.Favorites.SPANY);
    669                 final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
    670                 final int displayModeIndex = c.getColumnIndexOrThrow(
    671                         LauncherSettings.Favorites.DISPLAY_MODE);
    672 
    673                 ShortcutInfo info;
    674                 String intentDescription;
    675                 LauncherAppWidgetInfo appWidgetInfo;
    676                 int container;
    677                 long id;
    678                 Intent intent;
    679 
    680                 while (!mStopped && c.moveToNext()) {
    681                     try {
    682                         int itemType = c.getInt(itemTypeIndex);
    683 
    684                         switch (itemType) {
    685                         case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
    686                         case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
    687                             intentDescription = c.getString(intentIndex);
    688                             try {
    689                                 intent = Intent.parseUri(intentDescription, 0);
    690                             } catch (URISyntaxException e) {
    691                                 continue;
    692                             }
    693 
    694                             if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
    695                                 info = getShortcutInfo(manager, intent, context, c, iconIndex,
    696                                         titleIndex);
    697                             } else {
    698                                 info = getShortcutInfo(c, context, iconTypeIndex,
    699                                         iconPackageIndex, iconResourceIndex, iconIndex,
    700                                         titleIndex);
    701                             }
    702 
    703                             if (info != null) {
    704                                 updateSavedIcon(context, info, c, iconIndex);
    705 
    706                                 info.intent = intent;
    707                                 info.id = c.getLong(idIndex);
    708                                 container = c.getInt(containerIndex);
    709                                 info.container = container;
    710                                 info.screen = c.getInt(screenIndex);
    711                                 info.cellX = c.getInt(cellXIndex);
    712                                 info.cellY = c.getInt(cellYIndex);
    713 
    714                                 // check & update map of what's occupied
    715                                 if (!checkItemPlacement(occupied, info)) {
    716                                     break;
    717                                 }
    718 
    719                                 switch (container) {
    720                                 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
    721                                     mItems.add(info);
    722                                     break;
    723                                 default:
    724                                     // Item is in a user folder
    725                                     UserFolderInfo folderInfo =
    726                                             findOrMakeUserFolder(mFolders, container);
    727                                     folderInfo.add(info);
    728                                     break;
    729                                 }
    730                             } else {
    731                                 // Failed to load the shortcut, probably because the
    732                                 // activity manager couldn't resolve it (maybe the app
    733                                 // was uninstalled), or the db row was somehow screwed up.
    734                                 // Delete it.
    735                                 id = c.getLong(idIndex);
    736                                 Log.e(TAG, "Error loading shortcut " + id + ", removing it");
    737                                 contentResolver.delete(LauncherSettings.Favorites.getContentUri(
    738                                             id, false), null, null);
    739                             }
    740                             break;
    741 
    742                         case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
    743                             id = c.getLong(idIndex);
    744                             UserFolderInfo folderInfo = findOrMakeUserFolder(mFolders, id);
    745 
    746                             folderInfo.title = c.getString(titleIndex);
    747 
    748                             folderInfo.id = id;
    749                             container = c.getInt(containerIndex);
    750                             folderInfo.container = container;
    751                             folderInfo.screen = c.getInt(screenIndex);
    752                             folderInfo.cellX = c.getInt(cellXIndex);
    753                             folderInfo.cellY = c.getInt(cellYIndex);
    754 
    755                             // check & update map of what's occupied
    756                             if (!checkItemPlacement(occupied, folderInfo)) {
    757                                 break;
    758                             }
    759 
    760                             switch (container) {
    761                                 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
    762                                     mItems.add(folderInfo);
    763                                     break;
    764                             }
    765 
    766                             mFolders.put(folderInfo.id, folderInfo);
    767                             break;
    768 
    769                         case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER:
    770                             id = c.getLong(idIndex);
    771                             Uri uri = Uri.parse(c.getString(uriIndex));
    772 
    773                             // Make sure the live folder exists
    774                             final ProviderInfo providerInfo =
    775                                     context.getPackageManager().resolveContentProvider(
    776                                             uri.getAuthority(), 0);
    777 
    778                             if (providerInfo == null && !isSafeMode) {
    779                                 itemsToRemove.add(id);
    780                             } else {
    781                                 LiveFolderInfo liveFolderInfo = findOrMakeLiveFolder(mFolders, id);
    782 
    783                                 intentDescription = c.getString(intentIndex);
    784                                 intent = null;
    785                                 if (intentDescription != null) {
    786                                     try {
    787                                         intent = Intent.parseUri(intentDescription, 0);
    788                                     } catch (URISyntaxException e) {
    789                                         // Ignore, a live folder might not have a base intent
    790                                     }
    791                                 }
    792 
    793                                 liveFolderInfo.title = c.getString(titleIndex);
    794                                 liveFolderInfo.id = id;
    795                                 liveFolderInfo.uri = uri;
    796                                 container = c.getInt(containerIndex);
    797                                 liveFolderInfo.container = container;
    798                                 liveFolderInfo.screen = c.getInt(screenIndex);
    799                                 liveFolderInfo.cellX = c.getInt(cellXIndex);
    800                                 liveFolderInfo.cellY = c.getInt(cellYIndex);
    801                                 liveFolderInfo.baseIntent = intent;
    802                                 liveFolderInfo.displayMode = c.getInt(displayModeIndex);
    803 
    804                                 // check & update map of what's occupied
    805                                 if (!checkItemPlacement(occupied, liveFolderInfo)) {
    806                                     break;
    807                                 }
    808 
    809                                 loadLiveFolderIcon(context, c, iconTypeIndex, iconPackageIndex,
    810                                         iconResourceIndex, liveFolderInfo);
    811 
    812                                 switch (container) {
    813                                     case LauncherSettings.Favorites.CONTAINER_DESKTOP:
    814                                         mItems.add(liveFolderInfo);
    815                                         break;
    816                                 }
    817                                 mFolders.put(liveFolderInfo.id, liveFolderInfo);
    818                             }
    819                             break;
    820 
    821                         case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
    822                             // Read all Launcher-specific widget details
    823                             int appWidgetId = c.getInt(appWidgetIdIndex);
    824                             id = c.getLong(idIndex);
    825 
    826                             final AppWidgetProviderInfo provider =
    827                                     widgets.getAppWidgetInfo(appWidgetId);
    828 
    829                             if (!isSafeMode && (provider == null || provider.provider == null ||
    830                                     provider.provider.getPackageName() == null)) {
    831                                 Log.e(TAG, "Deleting widget that isn't installed anymore: id="
    832                                         + id + " appWidgetId=" + appWidgetId);
    833                                 itemsToRemove.add(id);
    834                             } else {
    835                                 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId);
    836                                 appWidgetInfo.id = id;
    837                                 appWidgetInfo.screen = c.getInt(screenIndex);
    838                                 appWidgetInfo.cellX = c.getInt(cellXIndex);
    839                                 appWidgetInfo.cellY = c.getInt(cellYIndex);
    840                                 appWidgetInfo.spanX = c.getInt(spanXIndex);
    841                                 appWidgetInfo.spanY = c.getInt(spanYIndex);
    842 
    843                                 container = c.getInt(containerIndex);
    844                                 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
    845                                     Log.e(TAG, "Widget found where container "
    846                                             + "!= CONTAINER_DESKTOP -- ignoring!");
    847                                     continue;
    848                                 }
    849                                 appWidgetInfo.container = c.getInt(containerIndex);
    850 
    851                                 // check & update map of what's occupied
    852                                 if (!checkItemPlacement(occupied, appWidgetInfo)) {
    853                                     break;
    854                                 }
    855 
    856                                 mAppWidgets.add(appWidgetInfo);
    857                             }
    858                             break;
    859                         }
    860                     } catch (Exception e) {
    861                         Log.w(TAG, "Desktop items loading interrupted:", e);
    862                     }
    863                 }
    864             } finally {
    865                 c.close();
    866             }
    867 
    868             if (itemsToRemove.size() > 0) {
    869                 ContentProviderClient client = contentResolver.acquireContentProviderClient(
    870                                 LauncherSettings.Favorites.CONTENT_URI);
    871                 // Remove dead items
    872                 for (long id : itemsToRemove) {
    873                     if (DEBUG_LOADERS) {
    874                         Log.d(TAG, "Removed id = " + id);
    875                     }
    876                     // Don't notify content observers
    877                     try {
    878                         client.delete(LauncherSettings.Favorites.getContentUri(id, false),
    879                                 null, null);
    880                     } catch (RemoteException e) {
    881                         Log.w(TAG, "Could not remove id = " + id);
    882                     }
    883                 }
    884             }
    885 
    886             if (DEBUG_LOADERS) {
    887                 Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
    888                 Log.d(TAG, "workspace layout: ");
    889                 for (int y = 0; y < Launcher.NUMBER_CELLS_Y; y++) {
    890                     String line = "";
    891                     for (int s = 0; s < Launcher.SCREEN_COUNT; s++) {
    892                         if (s > 0) {
    893                             line += " | ";
    894                         }
    895                         for (int x = 0; x < Launcher.NUMBER_CELLS_X; x++) {
    896                             line += ((occupied[s][x][y] != null) ? "#" : ".");
    897                         }
    898                     }
    899                     Log.d(TAG, "[ " + line + " ]");
    900                 }
    901             }
    902         }
    903 
    904         /**
    905          * Read everything out of our database.
    906          */
    907         private void bindWorkspace() {
    908             final long t = SystemClock.uptimeMillis();
    909 
    910             // Don't use these two variables in any of the callback runnables.
    911             // Otherwise we hold a reference to them.
    912             final Callbacks oldCallbacks = mCallbacks.get();
    913             if (oldCallbacks == null) {
    914                 // This launcher has exited and nobody bothered to tell us.  Just bail.
    915                 Log.w(TAG, "LoaderTask running with no launcher");
    916                 return;
    917             }
    918 
    919             int N;
    920             // Tell the workspace that we're about to start firing items at it
    921             mHandler.post(new Runnable() {
    922                 public void run() {
    923                     Callbacks callbacks = tryGetCallbacks(oldCallbacks);
    924                     if (callbacks != null) {
    925                         callbacks.startBinding();
    926                     }
    927                 }
    928             });
    929             // Add the items to the workspace.
    930             N = mItems.size();
    931             for (int i=0; i<N; i+=ITEMS_CHUNK) {
    932                 final int start = i;
    933                 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
    934                 mHandler.post(new Runnable() {
    935                     public void run() {
    936                         Callbacks callbacks = tryGetCallbacks(oldCallbacks);
    937                         if (callbacks != null) {
    938                             callbacks.bindItems(mItems, start, start+chunkSize);
    939                         }
    940                     }
    941                 });
    942             }
    943             mHandler.post(new Runnable() {
    944                 public void run() {
    945                     Callbacks callbacks = tryGetCallbacks(oldCallbacks);
    946                     if (callbacks != null) {
    947                         callbacks.bindFolders(mFolders);
    948                     }
    949                 }
    950             });
    951             // Wait until the queue goes empty.
    952             mHandler.post(new Runnable() {
    953                 public void run() {
    954                     if (DEBUG_LOADERS) {
    955                         Log.d(TAG, "Going to start binding widgets soon.");
    956                     }
    957                 }
    958             });
    959             // Bind the widgets, one at a time.
    960             // WARNING: this is calling into the workspace from the background thread,
    961             // but since getCurrentScreen() just returns the int, we should be okay.  This
    962             // is just a hint for the order, and if it's wrong, we'll be okay.
    963             // TODO: instead, we should have that push the current screen into here.
    964             final int currentScreen = oldCallbacks.getCurrentWorkspaceScreen();
    965             N = mAppWidgets.size();
    966             // once for the current screen
    967             for (int i=0; i<N; i++) {
    968                 final LauncherAppWidgetInfo widget = mAppWidgets.get(i);
    969                 if (widget.screen == currentScreen) {
    970                     mHandler.post(new Runnable() {
    971                         public void run() {
    972                             Callbacks callbacks = tryGetCallbacks(oldCallbacks);
    973                             if (callbacks != null) {
    974                                 callbacks.bindAppWidget(widget);
    975                             }
    976                         }
    977                     });
    978                 }
    979             }
    980             // once for the other screens
    981             for (int i=0; i<N; i++) {
    982                 final LauncherAppWidgetInfo widget = mAppWidgets.get(i);
    983                 if (widget.screen != currentScreen) {
    984                     mHandler.post(new Runnable() {
    985                         public void run() {
    986                             Callbacks callbacks = tryGetCallbacks(oldCallbacks);
    987                             if (callbacks != null) {
    988                                 callbacks.bindAppWidget(widget);
    989                             }
    990                         }
    991                     });
    992                 }
    993             }
    994             // Tell the workspace that we're done.
    995             mHandler.post(new Runnable() {
    996                 public void run() {
    997                     Callbacks callbacks = tryGetCallbacks(oldCallbacks);
    998                     if (callbacks != null) {
    999                         callbacks.finishBindingItems();
   1000                     }
   1001                 }
   1002             });
   1003             // If we're profiling, this is the last thing in the queue.
   1004             mHandler.post(new Runnable() {
   1005                 public void run() {
   1006                     if (DEBUG_LOADERS) {
   1007                         Log.d(TAG, "bound workspace in "
   1008                             + (SystemClock.uptimeMillis()-t) + "ms");
   1009                     }
   1010                 }
   1011             });
   1012         }
   1013 
   1014         private void loadAndBindAllApps() {
   1015             if (DEBUG_LOADERS) {
   1016                 Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
   1017             }
   1018             if (!mAllAppsLoaded) {
   1019                 loadAllAppsByBatch();
   1020                 if (mStopped) {
   1021                     return;
   1022                 }
   1023                 mAllAppsLoaded = true;
   1024             } else {
   1025                 onlyBindAllApps();
   1026             }
   1027         }
   1028 
   1029         private void onlyBindAllApps() {
   1030             final Callbacks oldCallbacks = mCallbacks.get();
   1031             if (oldCallbacks == null) {
   1032                 // This launcher has exited and nobody bothered to tell us.  Just bail.
   1033                 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
   1034                 return;
   1035             }
   1036 
   1037             // shallow copy
   1038             final ArrayList<ApplicationInfo> list
   1039                     = (ArrayList<ApplicationInfo>)mAllAppsList.data.clone();
   1040             mHandler.post(new Runnable() {
   1041                 public void run() {
   1042                     final long t = SystemClock.uptimeMillis();
   1043                     final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
   1044                     if (callbacks != null) {
   1045                         callbacks.bindAllApplications(list);
   1046                     }
   1047                     if (DEBUG_LOADERS) {
   1048                         Log.d(TAG, "bound all " + list.size() + " apps from cache in "
   1049                                 + (SystemClock.uptimeMillis()-t) + "ms");
   1050                     }
   1051                 }
   1052             });
   1053 
   1054         }
   1055 
   1056         private void loadAllAppsByBatch() {
   1057             final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
   1058 
   1059             // Don't use these two variables in any of the callback runnables.
   1060             // Otherwise we hold a reference to them.
   1061             final Callbacks oldCallbacks = mCallbacks.get();
   1062             if (oldCallbacks == null) {
   1063                 // This launcher has exited and nobody bothered to tell us.  Just bail.
   1064                 Log.w(TAG, "LoaderTask running with no launcher (loadAllAppsByBatch)");
   1065                 return;
   1066             }
   1067 
   1068             final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
   1069             mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
   1070 
   1071             final PackageManager packageManager = mContext.getPackageManager();
   1072             List<ResolveInfo> apps = null;
   1073 
   1074             int N = Integer.MAX_VALUE;
   1075 
   1076             int startIndex;
   1077             int i=0;
   1078             int batchSize = -1;
   1079             while (i < N && !mStopped) {
   1080                 if (i == 0) {
   1081                     mAllAppsList.clear();
   1082                     final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
   1083                     apps = packageManager.queryIntentActivities(mainIntent, 0);
   1084                     if (DEBUG_LOADERS) {
   1085                         Log.d(TAG, "queryIntentActivities took "
   1086                                 + (SystemClock.uptimeMillis()-qiaTime) + "ms");
   1087                     }
   1088                     if (apps == null) {
   1089                         return;
   1090                     }
   1091                     N = apps.size();
   1092                     if (DEBUG_LOADERS) {
   1093                         Log.d(TAG, "queryIntentActivities got " + N + " apps");
   1094                     }
   1095                     if (N == 0) {
   1096                         // There are no apps?!?
   1097                         return;
   1098                     }
   1099                     if (mBatchSize == 0) {
   1100                         batchSize = N;
   1101                     } else {
   1102                         batchSize = mBatchSize;
   1103                     }
   1104 
   1105                     final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
   1106                     Collections.sort(apps,
   1107                             new ResolveInfo.DisplayNameComparator(packageManager));
   1108                     if (DEBUG_LOADERS) {
   1109                         Log.d(TAG, "sort took "
   1110                                 + (SystemClock.uptimeMillis()-sortTime) + "ms");
   1111                     }
   1112                 }
   1113 
   1114                 final long t2 = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
   1115 
   1116                 startIndex = i;
   1117                 for (int j=0; i<N && j<batchSize; j++) {
   1118                     // This builds the icon bitmaps.
   1119                     mAllAppsList.add(new ApplicationInfo(apps.get(i), mIconCache));
   1120                     i++;
   1121                 }
   1122 
   1123                 final boolean first = i <= batchSize;
   1124                 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
   1125                 final ArrayList<ApplicationInfo> added = mAllAppsList.added;
   1126                 mAllAppsList.added = new ArrayList<ApplicationInfo>();
   1127 
   1128                 mHandler.post(new Runnable() {
   1129                     public void run() {
   1130                         final long t = SystemClock.uptimeMillis();
   1131                         if (callbacks != null) {
   1132                             if (first) {
   1133                                 callbacks.bindAllApplications(added);
   1134                             } else {
   1135                                 callbacks.bindAppsAdded(added);
   1136                             }
   1137                             if (DEBUG_LOADERS) {
   1138                                 Log.d(TAG, "bound " + added.size() + " apps in "
   1139                                     + (SystemClock.uptimeMillis() - t) + "ms");
   1140                             }
   1141                         } else {
   1142                             Log.i(TAG, "not binding apps: no Launcher activity");
   1143                         }
   1144                     }
   1145                 });
   1146 
   1147                 if (DEBUG_LOADERS) {
   1148                     Log.d(TAG, "batch of " + (i-startIndex) + " icons processed in "
   1149                             + (SystemClock.uptimeMillis()-t2) + "ms");
   1150                 }
   1151 
   1152                 if (mAllAppsLoadDelay > 0 && i < N) {
   1153                     try {
   1154                         if (DEBUG_LOADERS) {
   1155                             Log.d(TAG, "sleeping for " + mAllAppsLoadDelay + "ms");
   1156                         }
   1157                         Thread.sleep(mAllAppsLoadDelay);
   1158                     } catch (InterruptedException exc) { }
   1159                 }
   1160             }
   1161 
   1162             if (DEBUG_LOADERS) {
   1163                 Log.d(TAG, "cached all " + N + " apps in "
   1164                         + (SystemClock.uptimeMillis()-t) + "ms"
   1165                         + (mAllAppsLoadDelay > 0 ? " (including delay)" : ""));
   1166             }
   1167         }
   1168 
   1169         public void dumpState() {
   1170             Log.d(TAG, "mLoaderTask.mContext=" + mContext);
   1171             Log.d(TAG, "mLoaderTask.mWaitThread=" + mWaitThread);
   1172             Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching);
   1173             Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
   1174             Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
   1175         }
   1176     }
   1177 
   1178     void enqueuePackageUpdated(PackageUpdatedTask task) {
   1179         sWorker.post(task);
   1180     }
   1181 
   1182     private class PackageUpdatedTask implements Runnable {
   1183         int mOp;
   1184         String[] mPackages;
   1185 
   1186         public static final int OP_NONE = 0;
   1187         public static final int OP_ADD = 1;
   1188         public static final int OP_UPDATE = 2;
   1189         public static final int OP_REMOVE = 3; // uninstlled
   1190         public static final int OP_UNAVAILABLE = 4; // external media unmounted
   1191 
   1192 
   1193         public PackageUpdatedTask(int op, String[] packages) {
   1194             mOp = op;
   1195             mPackages = packages;
   1196         }
   1197 
   1198         public void run() {
   1199             final Context context = mApp;
   1200 
   1201             final String[] packages = mPackages;
   1202             final int N = packages.length;
   1203             switch (mOp) {
   1204                 case OP_ADD:
   1205                     for (int i=0; i<N; i++) {
   1206                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
   1207                         mAllAppsList.addPackage(context, packages[i]);
   1208                     }
   1209                     break;
   1210                 case OP_UPDATE:
   1211                     for (int i=0; i<N; i++) {
   1212                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
   1213                         mAllAppsList.updatePackage(context, packages[i]);
   1214                     }
   1215                     break;
   1216                 case OP_REMOVE:
   1217                 case OP_UNAVAILABLE:
   1218                     for (int i=0; i<N; i++) {
   1219                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
   1220                         mAllAppsList.removePackage(packages[i]);
   1221                     }
   1222                     break;
   1223             }
   1224 
   1225             ArrayList<ApplicationInfo> added = null;
   1226             ArrayList<ApplicationInfo> removed = null;
   1227             ArrayList<ApplicationInfo> modified = null;
   1228 
   1229             if (mAllAppsList.added.size() > 0) {
   1230                 added = mAllAppsList.added;
   1231                 mAllAppsList.added = new ArrayList<ApplicationInfo>();
   1232             }
   1233             if (mAllAppsList.removed.size() > 0) {
   1234                 removed = mAllAppsList.removed;
   1235                 mAllAppsList.removed = new ArrayList<ApplicationInfo>();
   1236                 for (ApplicationInfo info: removed) {
   1237                     mIconCache.remove(info.intent.getComponent());
   1238                 }
   1239             }
   1240             if (mAllAppsList.modified.size() > 0) {
   1241                 modified = mAllAppsList.modified;
   1242                 mAllAppsList.modified = new ArrayList<ApplicationInfo>();
   1243             }
   1244 
   1245             final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
   1246             if (callbacks == null) {
   1247                 Log.w(TAG, "Nobody to tell about the new app.  Launcher is probably loading.");
   1248                 return;
   1249             }
   1250 
   1251             if (added != null) {
   1252                 final ArrayList<ApplicationInfo> addedFinal = added;
   1253                 mHandler.post(new Runnable() {
   1254                     public void run() {
   1255                         if (callbacks == mCallbacks.get()) {
   1256                             callbacks.bindAppsAdded(addedFinal);
   1257                         }
   1258                     }
   1259                 });
   1260             }
   1261             if (modified != null) {
   1262                 final ArrayList<ApplicationInfo> modifiedFinal = modified;
   1263                 mHandler.post(new Runnable() {
   1264                     public void run() {
   1265                         if (callbacks == mCallbacks.get()) {
   1266                             callbacks.bindAppsUpdated(modifiedFinal);
   1267                         }
   1268                     }
   1269                 });
   1270             }
   1271             if (removed != null) {
   1272                 final boolean permanent = mOp != OP_UNAVAILABLE;
   1273                 final ArrayList<ApplicationInfo> removedFinal = removed;
   1274                 mHandler.post(new Runnable() {
   1275                     public void run() {
   1276                         if (callbacks == mCallbacks.get()) {
   1277                             callbacks.bindAppsRemoved(removedFinal, permanent);
   1278                         }
   1279                     }
   1280                 });
   1281             }
   1282         }
   1283     }
   1284 
   1285     /**
   1286      * This is called from the code that adds shortcuts from the intent receiver.  This
   1287      * doesn't have a Cursor, but
   1288      */
   1289     public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context) {
   1290         return getShortcutInfo(manager, intent, context, null, -1, -1);
   1291     }
   1292 
   1293     /**
   1294      * Make an ShortcutInfo object for a shortcut that is an application.
   1295      *
   1296      * If c is not null, then it will be used to fill in missing data like the title and icon.
   1297      */
   1298     public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context,
   1299             Cursor c, int iconIndex, int titleIndex) {
   1300         Bitmap icon = null;
   1301         final ShortcutInfo info = new ShortcutInfo();
   1302 
   1303         ComponentName componentName = intent.getComponent();
   1304         if (componentName == null) {
   1305             return null;
   1306         }
   1307 
   1308         // TODO: See if the PackageManager knows about this case.  If it doesn't
   1309         // then return null & delete this.
   1310 
   1311         // the resource -- This may implicitly give us back the fallback icon,
   1312         // but don't worry about that.  All we're doing with usingFallbackIcon is
   1313         // to avoid saving lots of copies of that in the database, and most apps
   1314         // have icons anyway.
   1315         final ResolveInfo resolveInfo = manager.resolveActivity(intent, 0);
   1316         if (resolveInfo != null) {
   1317             icon = mIconCache.getIcon(componentName, resolveInfo);
   1318         }
   1319         // the db
   1320         if (icon == null) {
   1321             if (c != null) {
   1322                 icon = getIconFromCursor(c, iconIndex);
   1323             }
   1324         }
   1325         // the fallback icon
   1326         if (icon == null) {
   1327             icon = getFallbackIcon();
   1328             info.usingFallbackIcon = true;
   1329         }
   1330         info.setIcon(icon);
   1331 
   1332         // from the resource
   1333         if (resolveInfo != null) {
   1334             info.title = resolveInfo.activityInfo.loadLabel(manager);
   1335         }
   1336         // from the db
   1337         if (info.title == null) {
   1338             if (c != null) {
   1339                 info.title =  c.getString(titleIndex);
   1340             }
   1341         }
   1342         // fall back to the class name of the activity
   1343         if (info.title == null) {
   1344             info.title = componentName.getClassName();
   1345         }
   1346         info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
   1347         return info;
   1348     }
   1349 
   1350     /**
   1351      * Make an ShortcutInfo object for a shortcut that isn't an application.
   1352      */
   1353     private ShortcutInfo getShortcutInfo(Cursor c, Context context,
   1354             int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex,
   1355             int titleIndex) {
   1356 
   1357         Bitmap icon = null;
   1358         final ShortcutInfo info = new ShortcutInfo();
   1359         info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
   1360 
   1361         // TODO: If there's an explicit component and we can't install that, delete it.
   1362 
   1363         info.title = c.getString(titleIndex);
   1364 
   1365         int iconType = c.getInt(iconTypeIndex);
   1366         switch (iconType) {
   1367         case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
   1368             String packageName = c.getString(iconPackageIndex);
   1369             String resourceName = c.getString(iconResourceIndex);
   1370             PackageManager packageManager = context.getPackageManager();
   1371             info.customIcon = false;
   1372             // the resource
   1373             try {
   1374                 Resources resources = packageManager.getResourcesForApplication(packageName);
   1375                 if (resources != null) {
   1376                     final int id = resources.getIdentifier(resourceName, null, null);
   1377                     icon = Utilities.createIconBitmap(resources.getDrawable(id), context);
   1378                 }
   1379             } catch (Exception e) {
   1380                 // drop this.  we have other places to look for icons
   1381             }
   1382             // the db
   1383             if (icon == null) {
   1384                 icon = getIconFromCursor(c, iconIndex);
   1385             }
   1386             // the fallback icon
   1387             if (icon == null) {
   1388                 icon = getFallbackIcon();
   1389                 info.usingFallbackIcon = true;
   1390             }
   1391             break;
   1392         case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
   1393             icon = getIconFromCursor(c, iconIndex);
   1394             if (icon == null) {
   1395                 icon = getFallbackIcon();
   1396                 info.customIcon = false;
   1397                 info.usingFallbackIcon = true;
   1398             } else {
   1399                 info.customIcon = true;
   1400             }
   1401             break;
   1402         default:
   1403             icon = getFallbackIcon();
   1404             info.usingFallbackIcon = true;
   1405             info.customIcon = false;
   1406             break;
   1407         }
   1408         info.setIcon(icon);
   1409         return info;
   1410     }
   1411 
   1412     Bitmap getIconFromCursor(Cursor c, int iconIndex) {
   1413         if (false) {
   1414             Log.d(TAG, "getIconFromCursor app="
   1415                     + c.getString(c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE)));
   1416         }
   1417         byte[] data = c.getBlob(iconIndex);
   1418         try {
   1419             return BitmapFactory.decodeByteArray(data, 0, data.length);
   1420         } catch (Exception e) {
   1421             return null;
   1422         }
   1423     }
   1424 
   1425     ShortcutInfo addShortcut(Context context, Intent data,
   1426             CellLayout.CellInfo cellInfo, boolean notify) {
   1427 
   1428         final ShortcutInfo info = infoFromShortcutIntent(context, data);
   1429         addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
   1430                 cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify);
   1431 
   1432         return info;
   1433     }
   1434 
   1435     private ShortcutInfo infoFromShortcutIntent(Context context, Intent data) {
   1436         Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
   1437         String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
   1438         Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
   1439 
   1440         Bitmap icon = null;
   1441         boolean filtered = false;
   1442         boolean customIcon = false;
   1443         ShortcutIconResource iconResource = null;
   1444 
   1445         if (bitmap != null && bitmap instanceof Bitmap) {
   1446             icon = Utilities.createIconBitmap(new FastBitmapDrawable((Bitmap)bitmap), context);
   1447             filtered = true;
   1448             customIcon = true;
   1449         } else {
   1450             Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
   1451             if (extra != null && extra instanceof ShortcutIconResource) {
   1452                 try {
   1453                     iconResource = (ShortcutIconResource) extra;
   1454                     final PackageManager packageManager = context.getPackageManager();
   1455                     Resources resources = packageManager.getResourcesForApplication(
   1456                             iconResource.packageName);
   1457                     final int id = resources.getIdentifier(iconResource.resourceName, null, null);
   1458                     icon = Utilities.createIconBitmap(resources.getDrawable(id), context);
   1459                 } catch (Exception e) {
   1460                     Log.w(TAG, "Could not load shortcut icon: " + extra);
   1461                 }
   1462             }
   1463         }
   1464 
   1465         final ShortcutInfo info = new ShortcutInfo();
   1466 
   1467         if (icon == null) {
   1468             icon = getFallbackIcon();
   1469             info.usingFallbackIcon = true;
   1470         }
   1471         info.setIcon(icon);
   1472 
   1473         info.title = name;
   1474         info.intent = intent;
   1475         info.customIcon = customIcon;
   1476         info.iconResource = iconResource;
   1477 
   1478         return info;
   1479     }
   1480 
   1481     private static void loadLiveFolderIcon(Context context, Cursor c, int iconTypeIndex,
   1482             int iconPackageIndex, int iconResourceIndex, LiveFolderInfo liveFolderInfo) {
   1483 
   1484         int iconType = c.getInt(iconTypeIndex);
   1485         switch (iconType) {
   1486         case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
   1487             String packageName = c.getString(iconPackageIndex);
   1488             String resourceName = c.getString(iconResourceIndex);
   1489             PackageManager packageManager = context.getPackageManager();
   1490             try {
   1491                 Resources resources = packageManager.getResourcesForApplication(packageName);
   1492                 final int id = resources.getIdentifier(resourceName, null, null);
   1493                 liveFolderInfo.icon = Utilities.createIconBitmap(resources.getDrawable(id),
   1494                         context);
   1495             } catch (Exception e) {
   1496                 liveFolderInfo.icon = Utilities.createIconBitmap(
   1497                         context.getResources().getDrawable(R.drawable.ic_launcher_folder),
   1498                         context);
   1499             }
   1500             liveFolderInfo.iconResource = new Intent.ShortcutIconResource();
   1501             liveFolderInfo.iconResource.packageName = packageName;
   1502             liveFolderInfo.iconResource.resourceName = resourceName;
   1503             break;
   1504         default:
   1505             liveFolderInfo.icon = Utilities.createIconBitmap(
   1506                     context.getResources().getDrawable(R.drawable.ic_launcher_folder),
   1507                     context);
   1508         }
   1509     }
   1510 
   1511     void updateSavedIcon(Context context, ShortcutInfo info, Cursor c, int iconIndex) {
   1512         // If this icon doesn't have a custom icon, check to see
   1513         // what's stored in the DB, and if it doesn't match what
   1514         // we're going to show, store what we are going to show back
   1515         // into the DB.  We do this so when we're loading, if the
   1516         // package manager can't find an icon (for example because
   1517         // the app is on SD) then we can use that instead.
   1518         if (!info.customIcon && !info.usingFallbackIcon) {
   1519             boolean needSave;
   1520             byte[] data = c.getBlob(iconIndex);
   1521             try {
   1522                 if (data != null) {
   1523                     Bitmap saved = BitmapFactory.decodeByteArray(data, 0, data.length);
   1524                     Bitmap loaded = info.getIcon(mIconCache);
   1525                     needSave = !saved.sameAs(loaded);
   1526                 } else {
   1527                     needSave = true;
   1528                 }
   1529             } catch (Exception e) {
   1530                 needSave = true;
   1531             }
   1532             if (needSave) {
   1533                 Log.d(TAG, "going to save icon bitmap for info=" + info);
   1534                 // This is slower than is ideal, but this only happens either
   1535                 // after the froyo OTA or when the app is updated with a new
   1536                 // icon.
   1537                 updateItemInDatabase(context, info);
   1538             }
   1539         }
   1540     }
   1541 
   1542     /**
   1543      * Return an existing UserFolderInfo object if we have encountered this ID previously,
   1544      * or make a new one.
   1545      */
   1546     private static UserFolderInfo findOrMakeUserFolder(HashMap<Long, FolderInfo> folders, long id) {
   1547         // See if a placeholder was created for us already
   1548         FolderInfo folderInfo = folders.get(id);
   1549         if (folderInfo == null || !(folderInfo instanceof UserFolderInfo)) {
   1550             // No placeholder -- create a new instance
   1551             folderInfo = new UserFolderInfo();
   1552             folders.put(id, folderInfo);
   1553         }
   1554         return (UserFolderInfo) folderInfo;
   1555     }
   1556 
   1557     /**
   1558      * Return an existing UserFolderInfo object if we have encountered this ID previously, or make a
   1559      * new one.
   1560      */
   1561     private static LiveFolderInfo findOrMakeLiveFolder(HashMap<Long, FolderInfo> folders, long id) {
   1562         // See if a placeholder was created for us already
   1563         FolderInfo folderInfo = folders.get(id);
   1564         if (folderInfo == null || !(folderInfo instanceof LiveFolderInfo)) {
   1565             // No placeholder -- create a new instance
   1566             folderInfo = new LiveFolderInfo();
   1567             folders.put(id, folderInfo);
   1568         }
   1569         return (LiveFolderInfo) folderInfo;
   1570     }
   1571 
   1572     private static String getLabel(PackageManager manager, ActivityInfo activityInfo) {
   1573         String label = activityInfo.loadLabel(manager).toString();
   1574         if (label == null) {
   1575             label = manager.getApplicationLabel(activityInfo.applicationInfo).toString();
   1576             if (label == null) {
   1577                 label = activityInfo.name;
   1578             }
   1579         }
   1580         return label;
   1581     }
   1582 
   1583     private static final Collator sCollator = Collator.getInstance();
   1584     public static final Comparator<ApplicationInfo> APP_NAME_COMPARATOR
   1585             = new Comparator<ApplicationInfo>() {
   1586         public final int compare(ApplicationInfo a, ApplicationInfo b) {
   1587             return sCollator.compare(a.title.toString(), b.title.toString());
   1588         }
   1589     };
   1590 
   1591     public void dumpState() {
   1592         Log.d(TAG, "mCallbacks=" + mCallbacks);
   1593         ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mAllAppsList.data);
   1594         ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mAllAppsList.added);
   1595         ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mAllAppsList.removed);
   1596         ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mAllAppsList.modified);
   1597         Log.d(TAG, "mItems size=" + mItems.size());
   1598         if (mLoaderTask != null) {
   1599             mLoaderTask.dumpState();
   1600         } else {
   1601             Log.d(TAG, "mLoaderTask=null");
   1602         }
   1603     }
   1604 }
   1605