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