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