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