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