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