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