LauncherModel.java revision 34b02499432896b60cdb9f78c9d1019c80ef6ef5
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.launcher2;
18
19import android.content.ComponentName;
20import android.content.ContentResolver;
21import android.content.ContentValues;
22import android.content.Intent;
23import android.content.Context;
24import android.content.pm.ActivityInfo;
25import android.content.pm.PackageManager;
26import android.content.pm.ResolveInfo;
27import android.content.res.Resources;
28import android.database.Cursor;
29import android.graphics.Bitmap;
30import android.graphics.BitmapFactory;
31import android.graphics.drawable.Drawable;
32import android.net.Uri;
33import static android.util.Log.*;
34import android.util.Log;
35import android.os.Process;
36import android.os.SystemClock;
37
38import java.lang.ref.WeakReference;
39import java.net.URISyntaxException;
40import java.text.Collator;
41import java.util.ArrayList;
42import java.util.Comparator;
43import java.util.Collections;
44import java.util.HashMap;
45import java.util.List;
46
47/**
48 * Maintains in-memory state of the Launcher. It is expected that there should be only one
49 * LauncherModel object held in a static. Also provide APIs for updating the database state
50 * for the Launcher.
51 */
52public class LauncherModel {
53    static final boolean DEBUG_LOADERS = true;
54    static final String TAG = "Launcher.Model";
55
56    private final Object mLock = new Object();
57    private DeferredHandler mHandler = new DeferredHandler();
58    private Loader mLoader = new Loader();
59
60    private WeakReference<Callbacks> mCallbacks;
61
62    private AllAppsList mAllAppsList = new AllAppsList();
63
64    public interface Callbacks {
65        public int getCurrentWorkspaceScreen();
66        public void startBinding();
67        public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end);
68        public void finishBindingItems();
69        public void bindAppWidget(LauncherAppWidgetInfo info);
70        public void bindAllApplications(ArrayList<ApplicationInfo> apps);
71        public void bindPackageAdded(ArrayList<ApplicationInfo> apps);
72        public void bindPackageUpdated(String packageName, ArrayList<ApplicationInfo> apps);
73        public void bindPackageRemoved(String packageName, ArrayList<ApplicationInfo> apps);
74    }
75
76
77    /**
78     * Adds an item to the DB if it was not created previously, or move it to a new
79     * <container, screen, cellX, cellY>
80     */
81    static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
82            int screen, int cellX, int cellY) {
83        if (item.container == ItemInfo.NO_ID) {
84            // From all apps
85            addItemToDatabase(context, item, container, screen, cellX, cellY, false);
86        } else {
87            // From somewhere else
88            moveItemInDatabase(context, item, container, screen, cellX, cellY);
89        }
90    }
91
92    /**
93     * Move an item in the DB to a new <container, screen, cellX, cellY>
94     */
95    static void moveItemInDatabase(Context context, ItemInfo item, long container, int screen,
96            int cellX, int cellY) {
97        item.container = container;
98        item.screen = screen;
99        item.cellX = cellX;
100        item.cellY = cellY;
101
102        final ContentValues values = new ContentValues();
103        final ContentResolver cr = context.getContentResolver();
104
105        values.put(LauncherSettings.Favorites.CONTAINER, item.container);
106        values.put(LauncherSettings.Favorites.CELLX, item.cellX);
107        values.put(LauncherSettings.Favorites.CELLY, item.cellY);
108        values.put(LauncherSettings.Favorites.SCREEN, item.screen);
109
110        cr.update(LauncherSettings.Favorites.getContentUri(item.id, false), values, null, null);
111    }
112
113    /**
114     * Returns true if the shortcuts already exists in the database.
115     * we identify a shortcut by its title and intent.
116     */
117    static boolean shortcutExists(Context context, String title, Intent intent) {
118        final ContentResolver cr = context.getContentResolver();
119        Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
120            new String[] { "title", "intent" }, "title=? and intent=?",
121            new String[] { title, intent.toUri(0) }, null);
122        boolean result = false;
123        try {
124            result = c.moveToFirst();
125        } finally {
126            c.close();
127        }
128        return result;
129    }
130
131    /**
132     * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList.
133     */
134    FolderInfo getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id) {
135        final ContentResolver cr = context.getContentResolver();
136        Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null,
137                "_id=? and (itemType=? or itemType=?)",
138                new String[] { String.valueOf(id),
139                        String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER),
140                        String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER) }, null);
141
142        try {
143            if (c.moveToFirst()) {
144                final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
145                final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
146                final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
147                final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
148                final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
149                final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
150
151                FolderInfo folderInfo = null;
152                switch (c.getInt(itemTypeIndex)) {
153                    case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
154                        folderInfo = findOrMakeUserFolder(folderList, id);
155                        break;
156                    case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER:
157                        folderInfo = findOrMakeLiveFolder(folderList, id);
158                        break;
159                }
160
161                folderInfo.title = c.getString(titleIndex);
162                folderInfo.id = id;
163                folderInfo.container = c.getInt(containerIndex);
164                folderInfo.screen = c.getInt(screenIndex);
165                folderInfo.cellX = c.getInt(cellXIndex);
166                folderInfo.cellY = c.getInt(cellYIndex);
167
168                return folderInfo;
169            }
170        } finally {
171            c.close();
172        }
173
174        return null;
175    }
176
177    /**
178     * Add an item to the database in a specified container. Sets the container, screen, cellX and
179     * cellY fields of the item. Also assigns an ID to the item.
180     */
181    static void addItemToDatabase(Context context, ItemInfo item, long container,
182            int screen, int cellX, int cellY, boolean notify) {
183        item.container = container;
184        item.screen = screen;
185        item.cellX = cellX;
186        item.cellY = cellY;
187
188        final ContentValues values = new ContentValues();
189        final ContentResolver cr = context.getContentResolver();
190
191        item.onAddToDatabase(values);
192
193        Uri result = cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI :
194                LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values);
195
196        if (result != null) {
197            item.id = Integer.parseInt(result.getPathSegments().get(1));
198        }
199    }
200
201    /**
202     * Update an item to the database in a specified container.
203     */
204    static void updateItemInDatabase(Context context, ItemInfo item) {
205        final ContentValues values = new ContentValues();
206        final ContentResolver cr = context.getContentResolver();
207
208        item.onAddToDatabase(values);
209
210        cr.update(LauncherSettings.Favorites.getContentUri(item.id, false), values, null, null);
211    }
212
213    /**
214     * Removes the specified item from the database
215     * @param context
216     * @param item
217     */
218    static void deleteItemFromDatabase(Context context, ItemInfo item) {
219        final ContentResolver cr = context.getContentResolver();
220
221        cr.delete(LauncherSettings.Favorites.getContentUri(item.id, false), null, null);
222    }
223
224    /**
225     * Remove the contents of the specified folder from the database
226     */
227    static void deleteUserFolderContentsFromDatabase(Context context, UserFolderInfo info) {
228        final ContentResolver cr = context.getContentResolver();
229
230        cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null);
231        cr.delete(LauncherSettings.Favorites.CONTENT_URI,
232                LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
233    }
234
235    /**
236     * Set this as the current Launcher activity object for the loader.
237     */
238    public void initialize(Callbacks callbacks) {
239        synchronized (mLock) {
240            mCallbacks = new WeakReference<Callbacks>(callbacks);
241        }
242    }
243
244    public void startLoader(Context context, boolean isLaunching) {
245        mLoader.startLoader(context, isLaunching);
246    }
247
248    public void stopLoader() {
249        mLoader.stopLoader();
250    }
251
252    public void setWorkspaceDirty() {
253        mLoader.setWorkspaceDirty();
254    }
255
256    /**
257     * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
258     * ACTION_PACKAGE_CHANGED.
259     */
260    public void onReceiveIntent(Context context, Intent intent) {
261        final String packageName = intent.getData().getSchemeSpecificPart();
262
263        ArrayList<ApplicationInfo> added = null;
264        ArrayList<ApplicationInfo> removed = null;
265        ArrayList<ApplicationInfo> modified = null;
266        boolean update = false;
267        boolean remove = false;
268
269        synchronized (mLock) {
270            final String action = intent.getAction();
271            final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
272
273            if (packageName == null || packageName.length() == 0) {
274                // they sent us a bad intent
275                return;
276            }
277
278            if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
279                mAllAppsList.updatePackage(context, packageName);
280                update = true;
281            } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
282                if (!replacing) {
283                    mAllAppsList.removePackage(packageName);
284                    remove = true;
285                }
286                // else, we are replacing the package, so a PACKAGE_ADDED will be sent
287                // later, we will update the package at this time
288            } else {
289                if (!replacing) {
290                    mAllAppsList.addPackage(context, packageName);
291                } else {
292                    mAllAppsList.updatePackage(context, packageName);
293                    update = true;
294                }
295            }
296
297            if (mAllAppsList.added.size() > 0) {
298                added = mAllAppsList.added;
299                mAllAppsList.added = new ArrayList();
300            }
301            if (mAllAppsList.removed.size() > 0) {
302                removed = mAllAppsList.removed;
303                mAllAppsList.removed = new ArrayList();
304                for (ApplicationInfo info: removed) {
305                    AppInfoCache.remove(info.intent.getComponent());
306                }
307            }
308            if (mAllAppsList.modified.size() > 0) {
309                modified = mAllAppsList.modified;
310                mAllAppsList.modified = new ArrayList();
311            }
312
313            final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
314            if (callbacks == null) {
315                return;
316            }
317
318            if (added != null) {
319                final ArrayList<ApplicationInfo> addedFinal = added;
320                mHandler.post(new Runnable() {
321                    public void run() {
322                        callbacks.bindPackageAdded(addedFinal);
323                    }
324                });
325            }
326            if (update || modified != null) {
327                final ArrayList<ApplicationInfo> modifiedFinal = modified;
328                mHandler.post(new Runnable() {
329                    public void run() {
330                        callbacks.bindPackageUpdated(packageName, modifiedFinal);
331                    }
332                });
333            }
334            if (remove || removed != null) {
335                final ArrayList<ApplicationInfo> removedFinal = removed;
336                mHandler.post(new Runnable() {
337                    public void run() {
338                        callbacks.bindPackageRemoved(packageName, removedFinal);
339                    }
340                });
341            }
342        }
343    }
344
345    public class Loader {
346        private static final int ITEMS_CHUNK = 6;
347
348        private LoaderThread mLoaderThread;
349
350        private int mLastWorkspaceSeq = 0;
351        private int mWorkspaceSeq = 1;
352
353        private int mLastAllAppsSeq = 0;
354        private int mAllAppsSeq = 1;
355
356        final ArrayList<ItemInfo> mItems = new ArrayList();
357        final ArrayList<LauncherAppWidgetInfo> mAppWidgets = new ArrayList();
358        final HashMap<Long, FolderInfo> folders = new HashMap();
359
360        /**
361         * Call this from the ui thread so the handler is initialized on the correct thread.
362         */
363        public Loader() {
364        }
365
366        public void startLoader(Context context, boolean isLaunching) {
367            synchronized (mLock) {
368                Log.d(TAG, "startLoader isLaunching=" + isLaunching);
369                // Don't bother to start the thread if we know it's not going to do anything
370                if (mCallbacks.get() != null) {
371                    LoaderThread oldThread = mLoaderThread;
372                    if (oldThread != null) {
373                        if (oldThread.isLaunching()) {
374                            // don't downgrade isLaunching if we're already running
375                            isLaunching = true;
376                        }
377                        oldThread.stopLocked();
378                    }
379                    mLoaderThread = new LoaderThread(context, oldThread, isLaunching);
380                    mLoaderThread.start();
381                }
382            }
383        }
384
385        public void stopLoader() {
386            synchronized (mLock) {
387                if (mLoaderThread != null) {
388                    mLoaderThread.stopLocked();
389                }
390            }
391        }
392
393        public void setWorkspaceDirty() {
394            synchronized (mLock) {
395                mWorkspaceSeq++;
396            }
397        }
398
399        public void setAllAppsDirty() {
400            synchronized (mLock) {
401                mAllAppsSeq++;
402            }
403        }
404
405        /**
406         * Runnable for the thread that loads the contents of the launcher:
407         *   - workspace icons
408         *   - widgets
409         *   - all apps icons
410         */
411        private class LoaderThread extends Thread {
412            private Context mContext;
413            private Thread mWaitThread;
414            private boolean mIsLaunching;
415            private boolean mStopped;
416            private boolean mWorkspaceDoneBinding;
417
418            LoaderThread(Context context, Thread waitThread, boolean isLaunching) {
419                mContext = context;
420                mWaitThread = waitThread;
421                mIsLaunching = isLaunching;
422            }
423
424            boolean isLaunching() {
425                return mIsLaunching;
426            }
427
428            /**
429             * If another LoaderThread was supplied, we need to wait for that to finish before
430             * we start our processing.  This keeps the ordering of the setting and clearing
431             * of the dirty flags correct by making sure we don't start processing stuff until
432             * they've had a chance to re-set them.  We do this waiting the worker thread, not
433             * the ui thread to avoid ANRs.
434             */
435            private void waitForOtherThread() {
436                if (mWaitThread != null) {
437                    boolean done = false;
438                    while (!done) {
439                        try {
440                            mWaitThread.join();
441                            done = true;
442                        } catch (InterruptedException ex) {
443                        }
444                    }
445                    mWaitThread = null;
446                }
447            }
448
449            public void run() {
450                waitForOtherThread();
451
452                // Elevate priority when Home launches for the first time to avoid
453                // starving at boot time. Staring at a blank home is not cool.
454                synchronized (mLock) {
455                    android.os.Process.setThreadPriority(mIsLaunching
456                            ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
457                }
458
459                // Load the workspace only if it's dirty.
460                int workspaceSeq;
461                boolean workspaceDirty;
462                synchronized (mLock) {
463                    workspaceSeq = mWorkspaceSeq;
464                    workspaceDirty = mWorkspaceSeq != mLastWorkspaceSeq;
465                }
466                if (workspaceDirty) {
467                    loadWorkspace();
468                }
469                synchronized (mLock) {
470                    // If we're not stopped, and nobody has incremented mWorkspaceSeq.
471                    if (mStopped) {
472                        return;
473                    }
474                    if (workspaceSeq == mWorkspaceSeq) {
475                        mLastWorkspaceSeq = mWorkspaceSeq;
476                    }
477                }
478
479                // Bind the workspace
480                bindWorkspace();
481
482                // Wait until the either we're stopped or the other threads are done.
483                // This way we don't start loading all apps until the workspace has settled
484                // down.
485                synchronized (LoaderThread.this) {
486                    mHandler.post(new Runnable() {
487                            public void run() {
488                                synchronized (LoaderThread.this) {
489                                    mWorkspaceDoneBinding = true;
490                                    Log.d(TAG, "done with workspace");
491                                    LoaderThread.this.notify();
492                                }
493                            }
494                        });
495                    Log.d(TAG, "waiting to be done with workspace");
496                    while (!mStopped && !mWorkspaceDoneBinding) {
497                        try {
498                            this.wait();
499                        } catch (InterruptedException ex) {
500                        }
501                    }
502                    Log.d(TAG, "done waiting to be done with workspace");
503                }
504
505                // Load all apps if they're dirty
506                int allAppsSeq;
507                boolean allAppsDirty;
508                synchronized (mLock) {
509                    allAppsSeq = mAllAppsSeq;
510                    allAppsDirty = mAllAppsSeq != mLastAllAppsSeq;
511                }
512                if (allAppsDirty) {
513                    loadAllApps();
514                }
515                synchronized (mLock) {
516                    // If we're not stopped, and nobody has incremented mAllAppsSeq.
517                    if (mStopped) {
518                        return;
519                    }
520                    if (allAppsSeq == mAllAppsSeq) {
521                        mLastAllAppsSeq = mAllAppsSeq;
522                    }
523                }
524
525                // Bind all apps
526                if (allAppsDirty) {
527                    bindAllApps();
528                }
529
530                // Clear out this reference, otherwise we end up holding it until all of the
531                // callback runnables are done.
532                mContext = null;
533
534                synchronized (mLock) {
535                    // Setting the reference is atomic, but we can't do it inside the other critical
536                    // sections.
537                    mLoaderThread = null;
538                    return;
539                }
540            }
541
542            public void stopLocked() {
543                synchronized (LoaderThread.this) {
544                    mStopped = true;
545                    this.notify();
546                }
547            }
548
549            /**
550             * Gets the callbacks object.  If we've been stopped, or if the launcher object
551             * has somehow been garbage collected, return null instead.
552             */
553            Callbacks tryGetCallbacks() {
554                synchronized (mLock) {
555                    if (mStopped) {
556                        return null;
557                    }
558
559                    final Callbacks callbacks = mCallbacks.get();
560                    if (callbacks == null) {
561                        Log.w(TAG, "no mCallbacks");
562                        return null;
563                    }
564
565                    return callbacks;
566                }
567            }
568
569            private void loadWorkspace() {
570                long t = SystemClock.uptimeMillis();
571
572                final Context context = mContext;
573                final ContentResolver contentResolver = context.getContentResolver();
574                final PackageManager manager = context.getPackageManager();
575
576                /* TODO
577                if (mLocaleChanged) {
578                    updateShortcutLabels(contentResolver, manager);
579                }
580                */
581
582                final Cursor c = contentResolver.query(
583                        LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
584
585                try {
586                    final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
587                    final int intentIndex = c.getColumnIndexOrThrow
588                            (LauncherSettings.Favorites.INTENT);
589                    final int titleIndex = c.getColumnIndexOrThrow
590                            (LauncherSettings.Favorites.TITLE);
591                    final int iconTypeIndex = c.getColumnIndexOrThrow(
592                            LauncherSettings.Favorites.ICON_TYPE);
593                    final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
594                    final int iconPackageIndex = c.getColumnIndexOrThrow(
595                            LauncherSettings.Favorites.ICON_PACKAGE);
596                    final int iconResourceIndex = c.getColumnIndexOrThrow(
597                            LauncherSettings.Favorites.ICON_RESOURCE);
598                    final int containerIndex = c.getColumnIndexOrThrow(
599                            LauncherSettings.Favorites.CONTAINER);
600                    final int itemTypeIndex = c.getColumnIndexOrThrow(
601                            LauncherSettings.Favorites.ITEM_TYPE);
602                    final int appWidgetIdIndex = c.getColumnIndexOrThrow(
603                            LauncherSettings.Favorites.APPWIDGET_ID);
604                    final int screenIndex = c.getColumnIndexOrThrow(
605                            LauncherSettings.Favorites.SCREEN);
606                    final int cellXIndex = c.getColumnIndexOrThrow
607                            (LauncherSettings.Favorites.CELLX);
608                    final int cellYIndex = c.getColumnIndexOrThrow
609                            (LauncherSettings.Favorites.CELLY);
610                    final int spanXIndex = c.getColumnIndexOrThrow
611                            (LauncherSettings.Favorites.SPANX);
612                    final int spanYIndex = c.getColumnIndexOrThrow(
613                            LauncherSettings.Favorites.SPANY);
614                    final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
615                    final int displayModeIndex = c.getColumnIndexOrThrow(
616                            LauncherSettings.Favorites.DISPLAY_MODE);
617
618                    ApplicationInfo info;
619                    String intentDescription;
620                    Widget widgetInfo;
621                    LauncherAppWidgetInfo appWidgetInfo;
622                    int container;
623                    long id;
624                    Intent intent;
625
626                    while (!mStopped && c.moveToNext()) {
627                        try {
628                            int itemType = c.getInt(itemTypeIndex);
629
630                            switch (itemType) {
631                            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
632                            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
633                                intentDescription = c.getString(intentIndex);
634                                try {
635                                    intent = Intent.parseUri(intentDescription, 0);
636                                } catch (URISyntaxException e) {
637                                    continue;
638                                }
639
640                                if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
641                                    info = getApplicationInfo(manager, intent, context);
642                                } else {
643                                    info = getApplicationInfoShortcut(c, context, iconTypeIndex,
644                                            iconPackageIndex, iconResourceIndex, iconIndex);
645                                }
646
647                                if (info == null) {
648                                    info = new ApplicationInfo();
649                                    info.icon = manager.getDefaultActivityIcon();
650                                }
651
652                                if (info != null) {
653                                    info.title = c.getString(titleIndex);
654                                    info.intent = intent;
655
656                                    info.id = c.getLong(idIndex);
657                                    container = c.getInt(containerIndex);
658                                    info.container = container;
659                                    info.screen = c.getInt(screenIndex);
660                                    info.cellX = c.getInt(cellXIndex);
661                                    info.cellY = c.getInt(cellYIndex);
662
663                                    switch (container) {
664                                    case LauncherSettings.Favorites.CONTAINER_DESKTOP:
665                                        mItems.add(info);
666                                        break;
667                                    default:
668                                        // Item is in a user folder
669                                        UserFolderInfo folderInfo =
670                                                findOrMakeUserFolder(folders, container);
671                                        folderInfo.add(info);
672                                        break;
673                                    }
674                                }
675                                break;
676                            case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
677
678                                id = c.getLong(idIndex);
679                                UserFolderInfo folderInfo = findOrMakeUserFolder(folders, id);
680
681                                folderInfo.title = c.getString(titleIndex);
682
683                                folderInfo.id = id;
684                                container = c.getInt(containerIndex);
685                                folderInfo.container = container;
686                                folderInfo.screen = c.getInt(screenIndex);
687                                folderInfo.cellX = c.getInt(cellXIndex);
688                                folderInfo.cellY = c.getInt(cellYIndex);
689
690                                switch (container) {
691                                    case LauncherSettings.Favorites.CONTAINER_DESKTOP:
692                                        mItems.add(folderInfo);
693                                        break;
694                                }
695                                break;
696                            case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER:
697
698                                id = c.getLong(idIndex);
699                                LiveFolderInfo liveFolderInfo = findOrMakeLiveFolder(folders, id);
700
701                                intentDescription = c.getString(intentIndex);
702                                intent = null;
703                                if (intentDescription != null) {
704                                    try {
705                                        intent = Intent.parseUri(intentDescription, 0);
706                                    } catch (URISyntaxException e) {
707                                        // Ignore, a live folder might not have a base intent
708                                    }
709                                }
710
711                                liveFolderInfo.title = c.getString(titleIndex);
712                                liveFolderInfo.id = id;
713                                container = c.getInt(containerIndex);
714                                liveFolderInfo.container = container;
715                                liveFolderInfo.screen = c.getInt(screenIndex);
716                                liveFolderInfo.cellX = c.getInt(cellXIndex);
717                                liveFolderInfo.cellY = c.getInt(cellYIndex);
718                                liveFolderInfo.uri = Uri.parse(c.getString(uriIndex));
719                                liveFolderInfo.baseIntent = intent;
720                                liveFolderInfo.displayMode = c.getInt(displayModeIndex);
721
722                                loadLiveFolderIcon(context, c, iconTypeIndex, iconPackageIndex,
723                                        iconResourceIndex, liveFolderInfo);
724
725                                switch (container) {
726                                    case LauncherSettings.Favorites.CONTAINER_DESKTOP:
727                                        mItems.add(liveFolderInfo);
728                                        break;
729                                }
730                                break;
731                            case LauncherSettings.Favorites.ITEM_TYPE_WIDGET_SEARCH:
732                                widgetInfo = Widget.makeSearch();
733
734                                container = c.getInt(containerIndex);
735                                if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
736                                    Log.e(TAG, "Widget found where container "
737                                            + "!= CONTAINER_DESKTOP  ignoring!");
738                                    continue;
739                                }
740
741                                widgetInfo.id = c.getLong(idIndex);
742                                widgetInfo.screen = c.getInt(screenIndex);
743                                widgetInfo.container = container;
744                                widgetInfo.cellX = c.getInt(cellXIndex);
745                                widgetInfo.cellY = c.getInt(cellYIndex);
746
747                                mItems.add(widgetInfo);
748                                break;
749                            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
750                                // Read all Launcher-specific widget details
751                                int appWidgetId = c.getInt(appWidgetIdIndex);
752                                appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId);
753                                appWidgetInfo.id = c.getLong(idIndex);
754                                appWidgetInfo.screen = c.getInt(screenIndex);
755                                appWidgetInfo.cellX = c.getInt(cellXIndex);
756                                appWidgetInfo.cellY = c.getInt(cellYIndex);
757                                appWidgetInfo.spanX = c.getInt(spanXIndex);
758                                appWidgetInfo.spanY = c.getInt(spanYIndex);
759
760                                container = c.getInt(containerIndex);
761                                if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
762                                    Log.e(TAG, "Widget found where container "
763                                            + "!= CONTAINER_DESKTOP -- ignoring!");
764                                    continue;
765                                }
766                                appWidgetInfo.container = c.getInt(containerIndex);
767
768                                mAppWidgets.add(appWidgetInfo);
769                                break;
770                            }
771                        } catch (Exception e) {
772                            Log.w(TAG, "Desktop items loading interrupted:", e);
773                        }
774                    }
775                } finally {
776                    c.close();
777                }
778                Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
779            }
780
781            /**
782             * Read everything out of our database.
783             */
784            private void bindWorkspace() {
785                final long t = SystemClock.uptimeMillis();
786
787                // Don't use these two variables in any of the callback runnables.
788                // Otherwise we hold a reference to them.
789                Callbacks callbacks = mCallbacks.get();
790                if (callbacks == null) {
791                    // This launcher has exited and nobody bothered to tell us.  Just bail.
792                    Log.w(TAG, "LoaderThread running with no launcher");
793                    return;
794                }
795
796                int N;
797                // Tell the workspace that we're about to start firing items at it
798                mHandler.post(new Runnable() {
799                    public void run() {
800                        Callbacks callbacks = tryGetCallbacks();
801                        if (callbacks != null) {
802                            callbacks.startBinding();
803                        }
804                    }
805                });
806                // Add the items to the workspace.
807                N = mItems.size();
808                for (int i=0; i<N; i+=ITEMS_CHUNK) {
809                    final int start = i;
810                    final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
811                    mHandler.post(new Runnable() {
812                        public void run() {
813                            Callbacks callbacks = tryGetCallbacks();
814                            if (callbacks != null) {
815                                callbacks.bindItems(mItems, start, start+chunkSize);
816                            }
817                        }
818                    });
819                }
820                // Wait until the queue goes empty.
821                mHandler.postIdle(new Runnable() {
822                    public void run() {
823                        Log.d(TAG, "Going to start binding widgets soon.");
824                    }
825                });
826                // Bind the widgets, one at a time.
827                // WARNING: this is calling into the workspace from the background thread,
828                // but since getCurrentScreen() just returns the int, we should be okay.  This
829                // is just a hint for the order, and if it's wrong, we'll be okay.
830                // TODO: instead, we should have that push the current screen into here.
831                final int currentScreen = callbacks.getCurrentWorkspaceScreen();
832                N = mAppWidgets.size();
833                // once for the current screen
834                for (int i=0; i<N; i++) {
835                    final LauncherAppWidgetInfo widget = mAppWidgets.get(i);
836                    if (widget.screen == currentScreen) {
837                        mHandler.post(new Runnable() {
838                            public void run() {
839                                Callbacks callbacks = tryGetCallbacks();
840                                if (callbacks != null) {
841                                    callbacks.bindAppWidget(widget);
842                                }
843                            }
844                        });
845                    }
846                }
847                // once for the other screens
848                for (int i=0; i<N; i++) {
849                    final LauncherAppWidgetInfo widget = mAppWidgets.get(i);
850                    if (widget.screen != currentScreen) {
851                        mHandler.post(new Runnable() {
852                            public void run() {
853                                Callbacks callbacks = tryGetCallbacks();
854                                if (callbacks != null) {
855                                    callbacks.bindAppWidget(widget);
856                                }
857                            }
858                        });
859                    }
860                }
861                // TODO: Bind the folders
862                // Tell the workspace that we're done.
863                mHandler.post(new Runnable() {
864                    public void run() {
865                        Callbacks callbacks = tryGetCallbacks();
866                        if (callbacks != null) {
867                            callbacks.finishBindingItems();
868                        }
869                    }
870                });
871                // If we're profiling, this is the last thing in the queue.
872                mHandler.post(new Runnable() {
873                    public void run() {
874                        Log.d(TAG, "bound workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
875                        if (Launcher.PROFILE_ROTATE) {
876                            android.os.Debug.stopMethodTracing();
877                        }
878                    }
879                });
880            }
881
882            private void loadAllApps() {
883                final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
884                mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
885
886                final Callbacks callbacks = tryGetCallbacks();
887                if (callbacks == null) {
888                    return;
889                }
890
891                final Context context = mContext;
892                final PackageManager packageManager = context.getPackageManager();
893
894                final List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
895
896                synchronized (mLock) {
897                    mAllAppsList.clear();
898                    if (apps != null) {
899                        long t = SystemClock.uptimeMillis();
900
901                        int N = apps.size();
902                        Utilities.BubbleText bubble = new Utilities.BubbleText(context);
903                        for (int i=0; i<N && !mStopped; i++) {
904                            // This builds the icon bitmaps.
905                            mAllAppsList.add(AppInfoCache.cache(apps.get(i), context, bubble));
906                        }
907                        Collections.sort(mAllAppsList.data, sComparator);
908                        Collections.sort(mAllAppsList.added, sComparator);
909                        Log.d(TAG, "cached app icons in " + (SystemClock.uptimeMillis()-t) + "ms");
910                    }
911                }
912            }
913
914            private void bindAllApps() {
915                synchronized (mLock) {
916                    final ArrayList<ApplicationInfo> results = mAllAppsList.added;
917                    mAllAppsList.added = new ArrayList();
918                    mHandler.post(new Runnable() {
919                        public void run() {
920                            final long t = SystemClock.uptimeMillis();
921                            final int count = results.size();
922
923                            Callbacks callbacks = tryGetCallbacks();
924                            if (callbacks != null) {
925                                callbacks.bindAllApplications(results);
926                            }
927
928                            Log.d(TAG, "bound app " + count + " icons in "
929                                + (SystemClock.uptimeMillis()-t) + "ms");
930                        }
931                    });
932                }
933            }
934        }
935    }
936
937    /**
938     * Make an ApplicationInfo object for an application.
939     */
940    private static ApplicationInfo getApplicationInfo(PackageManager manager, Intent intent,
941                                                      Context context) {
942        final ResolveInfo resolveInfo = manager.resolveActivity(intent, 0);
943
944        if (resolveInfo == null) {
945            return null;
946        }
947
948        final ApplicationInfo info = new ApplicationInfo();
949        final ActivityInfo activityInfo = resolveInfo.activityInfo;
950        info.icon = Utilities.createIconThumbnail(activityInfo.loadIcon(manager), context);
951        if (info.title == null || info.title.length() == 0) {
952            info.title = activityInfo.loadLabel(manager);
953        }
954        if (info.title == null) {
955            info.title = "";
956        }
957        info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
958        return info;
959    }
960
961    /**
962     * Make an ApplicationInfo object for a sortcut
963     */
964    private static ApplicationInfo getApplicationInfoShortcut(Cursor c, Context context,
965            int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex) {
966
967        final ApplicationInfo info = new ApplicationInfo();
968        info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
969
970        int iconType = c.getInt(iconTypeIndex);
971        switch (iconType) {
972        case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
973            String packageName = c.getString(iconPackageIndex);
974            String resourceName = c.getString(iconResourceIndex);
975            PackageManager packageManager = context.getPackageManager();
976            try {
977                Resources resources = packageManager.getResourcesForApplication(packageName);
978                final int id = resources.getIdentifier(resourceName, null, null);
979                info.icon = Utilities.createIconThumbnail(resources.getDrawable(id), context);
980            } catch (Exception e) {
981                info.icon = packageManager.getDefaultActivityIcon();
982            }
983            info.iconResource = new Intent.ShortcutIconResource();
984            info.iconResource.packageName = packageName;
985            info.iconResource.resourceName = resourceName;
986            info.customIcon = false;
987            break;
988        case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
989            byte[] data = c.getBlob(iconIndex);
990            try {
991                Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
992                info.icon = new FastBitmapDrawable(
993                        Utilities.createBitmapThumbnail(bitmap, context));
994            } catch (Exception e) {
995                packageManager = context.getPackageManager();
996                info.icon = packageManager.getDefaultActivityIcon();
997            }
998            info.filtered = true;
999            info.customIcon = true;
1000            break;
1001        default:
1002            info.icon = context.getPackageManager().getDefaultActivityIcon();
1003            info.customIcon = false;
1004            break;
1005        }
1006        return info;
1007    }
1008
1009    private static void loadLiveFolderIcon(Context context, Cursor c, int iconTypeIndex,
1010            int iconPackageIndex, int iconResourceIndex, LiveFolderInfo liveFolderInfo) {
1011
1012        int iconType = c.getInt(iconTypeIndex);
1013        switch (iconType) {
1014        case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
1015            String packageName = c.getString(iconPackageIndex);
1016            String resourceName = c.getString(iconResourceIndex);
1017            PackageManager packageManager = context.getPackageManager();
1018            try {
1019                Resources resources = packageManager.getResourcesForApplication(packageName);
1020                final int id = resources.getIdentifier(resourceName, null, null);
1021                liveFolderInfo.icon = resources.getDrawable(id);
1022            } catch (Exception e) {
1023                liveFolderInfo.icon =
1024                        context.getResources().getDrawable(R.drawable.ic_launcher_folder);
1025            }
1026            liveFolderInfo.iconResource = new Intent.ShortcutIconResource();
1027            liveFolderInfo.iconResource.packageName = packageName;
1028            liveFolderInfo.iconResource.resourceName = resourceName;
1029            break;
1030        default:
1031            liveFolderInfo.icon =
1032                    context.getResources().getDrawable(R.drawable.ic_launcher_folder);
1033        }
1034    }
1035
1036    /**
1037     * Return an existing UserFolderInfo object if we have encountered this ID previously,
1038     * or make a new one.
1039     */
1040    private static UserFolderInfo findOrMakeUserFolder(HashMap<Long, FolderInfo> folders, long id) {
1041        // See if a placeholder was created for us already
1042        FolderInfo folderInfo = folders.get(id);
1043        if (folderInfo == null || !(folderInfo instanceof UserFolderInfo)) {
1044            // No placeholder -- create a new instance
1045            folderInfo = new UserFolderInfo();
1046            folders.put(id, folderInfo);
1047        }
1048        return (UserFolderInfo) folderInfo;
1049    }
1050
1051    /**
1052     * Return an existing UserFolderInfo object if we have encountered this ID previously, or make a
1053     * new one.
1054     */
1055    private static LiveFolderInfo findOrMakeLiveFolder(HashMap<Long, FolderInfo> folders, long id) {
1056        // See if a placeholder was created for us already
1057        FolderInfo folderInfo = folders.get(id);
1058        if (folderInfo == null || !(folderInfo instanceof LiveFolderInfo)) {
1059            // No placeholder -- create a new instance
1060            folderInfo = new LiveFolderInfo();
1061            folders.put(id, folderInfo);
1062        }
1063        return (LiveFolderInfo) folderInfo;
1064    }
1065
1066    private static void updateShortcutLabels(ContentResolver resolver, PackageManager manager) {
1067        final Cursor c = resolver.query(LauncherSettings.Favorites.CONTENT_URI,
1068                new String[] { LauncherSettings.Favorites._ID, LauncherSettings.Favorites.TITLE,
1069                        LauncherSettings.Favorites.INTENT, LauncherSettings.Favorites.ITEM_TYPE },
1070                null, null, null);
1071
1072        final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1073        final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
1074        final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
1075        final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
1076
1077        // boolean changed = false;
1078
1079        try {
1080            while (c.moveToNext()) {
1081                try {
1082                    if (c.getInt(itemTypeIndex) !=
1083                            LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
1084                        continue;
1085                    }
1086
1087                    final String intentUri = c.getString(intentIndex);
1088                    if (intentUri != null) {
1089                        final Intent shortcut = Intent.parseUri(intentUri, 0);
1090                        if (Intent.ACTION_MAIN.equals(shortcut.getAction())) {
1091                            final ComponentName name = shortcut.getComponent();
1092                            if (name != null) {
1093                                final ActivityInfo activityInfo = manager.getActivityInfo(name, 0);
1094                                final String title = c.getString(titleIndex);
1095                                String label = getLabel(manager, activityInfo);
1096
1097                                if (title == null || !title.equals(label)) {
1098                                    final ContentValues values = new ContentValues();
1099                                    values.put(LauncherSettings.Favorites.TITLE, label);
1100
1101                                    resolver.update(
1102                                            LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
1103                                            values, "_id=?",
1104                                            new String[] { String.valueOf(c.getLong(idIndex)) });
1105
1106                                    // changed = true;
1107                                }
1108                            }
1109                        }
1110                    }
1111                } catch (URISyntaxException e) {
1112                    // Ignore
1113                } catch (PackageManager.NameNotFoundException e) {
1114                    // Ignore
1115                }
1116            }
1117        } finally {
1118            c.close();
1119        }
1120
1121        // if (changed) resolver.notifyChange(Settings.Favorites.CONTENT_URI, null);
1122    }
1123
1124    private static String getLabel(PackageManager manager, ActivityInfo activityInfo) {
1125        String label = activityInfo.loadLabel(manager).toString();
1126        if (label == null) {
1127            label = manager.getApplicationLabel(activityInfo.applicationInfo).toString();
1128            if (label == null) {
1129                label = activityInfo.name;
1130            }
1131        }
1132        return label;
1133    }
1134
1135    private static final Collator sCollator = Collator.getInstance();
1136    private static final Comparator<ApplicationInfo> sComparator
1137            = new Comparator<ApplicationInfo>() {
1138        public final int compare(ApplicationInfo a, ApplicationInfo b) {
1139            return sCollator.compare(a.title.toString(), b.title.toString());
1140        }
1141    };
1142}
1143