LauncherModel.java revision 511ab64d25ee45518b816afa9110b3160edf3f7b
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 = true;
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        Log.d(TAG, "onReceive intent=" + intent);
274
275        // Use the app as the context.
276        context = mApp;
277
278        final String packageName = intent.getData().getSchemeSpecificPart();
279
280        ArrayList<ApplicationInfo> added = null;
281        ArrayList<ApplicationInfo> removed = null;
282        ArrayList<ApplicationInfo> modified = null;
283        boolean update = false;
284        boolean remove = false;
285
286        synchronized (mLock) {
287            if (mBeforeFirstLoad) {
288                // If we haven't even loaded yet, don't bother, since we'll just pick
289                // up the changes.
290                return;
291            }
292
293            final String action = intent.getAction();
294            final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
295
296            if (packageName == null || packageName.length() == 0) {
297                // they sent us a bad intent
298                return;
299            }
300
301            if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
302                mAllAppsList.updatePackage(context, packageName);
303                update = true;
304            } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
305                if (!replacing) {
306                    mAllAppsList.removePackage(packageName);
307                    remove = true;
308                }
309                // else, we are replacing the package, so a PACKAGE_ADDED will be sent
310                // later, we will update the package at this time
311            } else {
312                if (!replacing) {
313                    mAllAppsList.addPackage(context, packageName);
314                } else {
315                    mAllAppsList.updatePackage(context, packageName);
316                    update = true;
317                }
318            }
319
320            if (mAllAppsList.added.size() > 0) {
321                added = mAllAppsList.added;
322                mAllAppsList.added = new ArrayList<ApplicationInfo>();
323            }
324            if (mAllAppsList.removed.size() > 0) {
325                removed = mAllAppsList.removed;
326                mAllAppsList.removed = new ArrayList<ApplicationInfo>();
327                for (ApplicationInfo info: removed) {
328                    AppInfoCache.remove(info.intent.getComponent());
329                }
330            }
331            if (mAllAppsList.modified.size() > 0) {
332                modified = mAllAppsList.modified;
333                mAllAppsList.modified = new ArrayList<ApplicationInfo>();
334            }
335
336            final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
337            if (callbacks == null) {
338                Log.d(TAG, "nobody to tell about the new app");
339                return;
340            }
341
342            if (added != null) {
343                final ArrayList<ApplicationInfo> addedFinal = added;
344                mHandler.post(new Runnable() {
345                    public void run() {
346                        callbacks.bindPackageAdded(addedFinal);
347                    }
348                });
349            }
350            if (update || modified != null) {
351                final ArrayList<ApplicationInfo> modifiedFinal = modified;
352                mHandler.post(new Runnable() {
353                    public void run() {
354                        callbacks.bindPackageUpdated(packageName, modifiedFinal);
355                    }
356                });
357            }
358            if (remove || removed != null) {
359                final ArrayList<ApplicationInfo> removedFinal = removed;
360                mHandler.post(new Runnable() {
361                    public void run() {
362                        callbacks.bindPackageRemoved(packageName, removedFinal);
363                    }
364                });
365            }
366        }
367    }
368
369    public class Loader {
370        private static final int ITEMS_CHUNK = 6;
371
372        private LoaderThread mLoaderThread;
373
374        private int mLastWorkspaceSeq = 0;
375        private int mWorkspaceSeq = 1;
376
377        private int mLastAllAppsSeq = 0;
378        private int mAllAppsSeq = 1;
379
380        final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
381        final ArrayList<LauncherAppWidgetInfo> mAppWidgets = new ArrayList<LauncherAppWidgetInfo>();
382        final HashMap<Long, FolderInfo> mFolders = new HashMap<Long, FolderInfo>();
383
384        /**
385         * Call this from the ui thread so the handler is initialized on the correct thread.
386         */
387        public Loader() {
388        }
389
390        public void startLoader(Context context, boolean isLaunching) {
391            synchronized (mLock) {
392                Log.d(TAG, "startLoader isLaunching=" + isLaunching);
393                // Don't bother to start the thread if we know it's not going to do anything
394                if (mCallbacks.get() != null) {
395                    LoaderThread oldThread = mLoaderThread;
396                    if (oldThread != null) {
397                        if (oldThread.isLaunching()) {
398                            // don't downgrade isLaunching if we're already running
399                            isLaunching = true;
400                        }
401                        oldThread.stopLocked();
402                    }
403                    mLoaderThread = new LoaderThread(context, oldThread, isLaunching);
404                    mLoaderThread.start();
405                }
406            }
407        }
408
409        public void stopLoader() {
410            synchronized (mLock) {
411                if (mLoaderThread != null) {
412                    mLoaderThread.stopLocked();
413                }
414            }
415        }
416
417        public void setWorkspaceDirty() {
418            synchronized (mLock) {
419                mWorkspaceSeq++;
420            }
421        }
422
423        public void setAllAppsDirty() {
424            synchronized (mLock) {
425                mAllAppsSeq++;
426            }
427        }
428
429        /**
430         * Runnable for the thread that loads the contents of the launcher:
431         *   - workspace icons
432         *   - widgets
433         *   - all apps icons
434         */
435        private class LoaderThread extends Thread {
436            private Context mContext;
437            private Thread mWaitThread;
438            private boolean mIsLaunching;
439            private boolean mStopped;
440            private boolean mWorkspaceDoneBinding;
441
442            LoaderThread(Context context, Thread waitThread, boolean isLaunching) {
443                mContext = context;
444                mWaitThread = waitThread;
445                mIsLaunching = isLaunching;
446            }
447
448            boolean isLaunching() {
449                return mIsLaunching;
450            }
451
452            /**
453             * If another LoaderThread was supplied, we need to wait for that to finish before
454             * we start our processing.  This keeps the ordering of the setting and clearing
455             * of the dirty flags correct by making sure we don't start processing stuff until
456             * they've had a chance to re-set them.  We do this waiting the worker thread, not
457             * the ui thread to avoid ANRs.
458             */
459            private void waitForOtherThread() {
460                if (mWaitThread != null) {
461                    boolean done = false;
462                    while (!done) {
463                        try {
464                            mWaitThread.join();
465                            done = true;
466                        } catch (InterruptedException ex) {
467                            // Ignore
468                        }
469                    }
470                    mWaitThread = null;
471                }
472            }
473
474            public void run() {
475                waitForOtherThread();
476
477                // Elevate priority when Home launches for the first time to avoid
478                // starving at boot time. Staring at a blank home is not cool.
479                synchronized (mLock) {
480                    android.os.Process.setThreadPriority(mIsLaunching
481                            ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
482                }
483
484                // Load the workspace only if it's dirty.
485                int workspaceSeq;
486                boolean workspaceDirty;
487                synchronized (mLock) {
488                    workspaceSeq = mWorkspaceSeq;
489                    workspaceDirty = mWorkspaceSeq != mLastWorkspaceSeq;
490                }
491                if (workspaceDirty) {
492                    loadWorkspace();
493                }
494                synchronized (mLock) {
495                    // If we're not stopped, and nobody has incremented mWorkspaceSeq.
496                    if (mStopped) {
497                        return;
498                    }
499                    if (workspaceSeq == mWorkspaceSeq) {
500                        mLastWorkspaceSeq = mWorkspaceSeq;
501                    }
502                }
503
504                // Bind the workspace
505                bindWorkspace();
506
507                // Wait until the either we're stopped or the other threads are done.
508                // This way we don't start loading all apps until the workspace has settled
509                // down.
510                synchronized (LoaderThread.this) {
511                    mHandler.postIdle(new Runnable() {
512                            public void run() {
513                                synchronized (LoaderThread.this) {
514                                    mWorkspaceDoneBinding = true;
515                                    Log.d(TAG, "done with workspace");
516                                    LoaderThread.this.notify();
517                                }
518                            }
519                        });
520                    Log.d(TAG, "waiting to be done with workspace");
521                    while (!mStopped && !mWorkspaceDoneBinding) {
522                        try {
523                            this.wait();
524                        } catch (InterruptedException ex) {
525                            // Ignore
526                        }
527                    }
528                    Log.d(TAG, "done waiting to be done with workspace");
529                }
530
531                // Load all apps if they're dirty
532                int allAppsSeq;
533                boolean allAppsDirty;
534                synchronized (mLock) {
535                    allAppsSeq = mAllAppsSeq;
536                    allAppsDirty = mAllAppsSeq != mLastAllAppsSeq;
537                    //Log.d(TAG, "mAllAppsSeq=" + mAllAppsSeq
538                    //          + " mLastAllAppsSeq=" + mLastAllAppsSeq + " allAppsDirty");
539                }
540                if (allAppsDirty) {
541                    loadAllApps();
542                }
543                synchronized (mLock) {
544                    // If we're not stopped, and nobody has incremented mAllAppsSeq.
545                    if (mStopped) {
546                        return;
547                    }
548                    if (allAppsSeq == mAllAppsSeq) {
549                        mLastAllAppsSeq = mAllAppsSeq;
550                    }
551                }
552
553                // Bind all apps
554                if (allAppsDirty) {
555                    bindAllApps();
556                }
557
558                // Clear out this reference, otherwise we end up holding it until all of the
559                // callback runnables are done.
560                mContext = null;
561
562                synchronized (mLock) {
563                    // Setting the reference is atomic, but we can't do it inside the other critical
564                    // sections.
565                    mLoaderThread = null;
566                }
567            }
568
569            public void stopLocked() {
570                synchronized (LoaderThread.this) {
571                    mStopped = true;
572                    this.notify();
573                }
574            }
575
576            /**
577             * Gets the callbacks object.  If we've been stopped, or if the launcher object
578             * has somehow been garbage collected, return null instead.
579             */
580            Callbacks tryGetCallbacks() {
581                synchronized (mLock) {
582                    if (mStopped) {
583                        return null;
584                    }
585
586                    final Callbacks callbacks = mCallbacks.get();
587                    if (callbacks == null) {
588                        Log.w(TAG, "no mCallbacks");
589                        return null;
590                    }
591
592                    return callbacks;
593                }
594            }
595
596            private void loadWorkspace() {
597                long t = SystemClock.uptimeMillis();
598
599                final Context context = mContext;
600                final ContentResolver contentResolver = context.getContentResolver();
601                final PackageManager manager = context.getPackageManager();
602
603                /* TODO
604                if (mLocaleChanged) {
605                    updateShortcutLabels(contentResolver, manager);
606                }
607                */
608
609                mItems.clear();
610                mAppWidgets.clear();
611
612                final Cursor c = contentResolver.query(
613                        LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
614
615                try {
616                    final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
617                    final int intentIndex = c.getColumnIndexOrThrow
618                            (LauncherSettings.Favorites.INTENT);
619                    final int titleIndex = c.getColumnIndexOrThrow
620                            (LauncherSettings.Favorites.TITLE);
621                    final int iconTypeIndex = c.getColumnIndexOrThrow(
622                            LauncherSettings.Favorites.ICON_TYPE);
623                    final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
624                    final int iconPackageIndex = c.getColumnIndexOrThrow(
625                            LauncherSettings.Favorites.ICON_PACKAGE);
626                    final int iconResourceIndex = c.getColumnIndexOrThrow(
627                            LauncherSettings.Favorites.ICON_RESOURCE);
628                    final int containerIndex = c.getColumnIndexOrThrow(
629                            LauncherSettings.Favorites.CONTAINER);
630                    final int itemTypeIndex = c.getColumnIndexOrThrow(
631                            LauncherSettings.Favorites.ITEM_TYPE);
632                    final int appWidgetIdIndex = c.getColumnIndexOrThrow(
633                            LauncherSettings.Favorites.APPWIDGET_ID);
634                    final int screenIndex = c.getColumnIndexOrThrow(
635                            LauncherSettings.Favorites.SCREEN);
636                    final int cellXIndex = c.getColumnIndexOrThrow
637                            (LauncherSettings.Favorites.CELLX);
638                    final int cellYIndex = c.getColumnIndexOrThrow
639                            (LauncherSettings.Favorites.CELLY);
640                    final int spanXIndex = c.getColumnIndexOrThrow
641                            (LauncherSettings.Favorites.SPANX);
642                    final int spanYIndex = c.getColumnIndexOrThrow(
643                            LauncherSettings.Favorites.SPANY);
644                    final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
645                    final int displayModeIndex = c.getColumnIndexOrThrow(
646                            LauncherSettings.Favorites.DISPLAY_MODE);
647
648                    ApplicationInfo info;
649                    String intentDescription;
650                    Widget widgetInfo;
651                    LauncherAppWidgetInfo appWidgetInfo;
652                    int container;
653                    long id;
654                    Intent intent;
655
656                    while (!mStopped && c.moveToNext()) {
657                        try {
658                            int itemType = c.getInt(itemTypeIndex);
659
660                            switch (itemType) {
661                            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
662                            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
663                                intentDescription = c.getString(intentIndex);
664                                try {
665                                    intent = Intent.parseUri(intentDescription, 0);
666                                } catch (URISyntaxException e) {
667                                    continue;
668                                }
669
670                                if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
671                                    info = getApplicationInfo(manager, intent, context);
672                                } else {
673                                    info = getApplicationInfoShortcut(c, context, iconTypeIndex,
674                                            iconPackageIndex, iconResourceIndex, iconIndex);
675                                }
676
677                                if (info == null) {
678                                    info = new ApplicationInfo();
679                                    info.icon = manager.getDefaultActivityIcon();
680                                }
681
682                                if (info != null) {
683                                    info.title = c.getString(titleIndex);
684                                    info.intent = intent;
685
686                                    info.id = c.getLong(idIndex);
687                                    container = c.getInt(containerIndex);
688                                    info.container = container;
689                                    info.screen = c.getInt(screenIndex);
690                                    info.cellX = c.getInt(cellXIndex);
691                                    info.cellY = c.getInt(cellYIndex);
692
693                                    switch (container) {
694                                    case LauncherSettings.Favorites.CONTAINER_DESKTOP:
695                                        mItems.add(info);
696                                        break;
697                                    default:
698                                        // Item is in a user folder
699                                        UserFolderInfo folderInfo =
700                                                findOrMakeUserFolder(mFolders, container);
701                                        folderInfo.add(info);
702                                        break;
703                                    }
704                                }
705                                break;
706
707                            case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
708                                id = c.getLong(idIndex);
709                                UserFolderInfo folderInfo = findOrMakeUserFolder(mFolders, id);
710
711                                folderInfo.title = c.getString(titleIndex);
712
713                                folderInfo.id = id;
714                                container = c.getInt(containerIndex);
715                                folderInfo.container = container;
716                                folderInfo.screen = c.getInt(screenIndex);
717                                folderInfo.cellX = c.getInt(cellXIndex);
718                                folderInfo.cellY = c.getInt(cellYIndex);
719
720                                switch (container) {
721                                    case LauncherSettings.Favorites.CONTAINER_DESKTOP:
722                                        mItems.add(folderInfo);
723                                        break;
724                                }
725
726                                mFolders.put(folderInfo.id, folderInfo);
727                                break;
728
729                            case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER:
730
731                                id = c.getLong(idIndex);
732                                LiveFolderInfo liveFolderInfo = findOrMakeLiveFolder(mFolders, id);
733
734                                intentDescription = c.getString(intentIndex);
735                                intent = null;
736                                if (intentDescription != null) {
737                                    try {
738                                        intent = Intent.parseUri(intentDescription, 0);
739                                    } catch (URISyntaxException e) {
740                                        // Ignore, a live folder might not have a base intent
741                                    }
742                                }
743
744                                liveFolderInfo.title = c.getString(titleIndex);
745                                liveFolderInfo.id = id;
746                                container = c.getInt(containerIndex);
747                                liveFolderInfo.container = container;
748                                liveFolderInfo.screen = c.getInt(screenIndex);
749                                liveFolderInfo.cellX = c.getInt(cellXIndex);
750                                liveFolderInfo.cellY = c.getInt(cellYIndex);
751                                liveFolderInfo.uri = Uri.parse(c.getString(uriIndex));
752                                liveFolderInfo.baseIntent = intent;
753                                liveFolderInfo.displayMode = c.getInt(displayModeIndex);
754
755                                loadLiveFolderIcon(context, c, iconTypeIndex, iconPackageIndex,
756                                        iconResourceIndex, liveFolderInfo);
757
758                                switch (container) {
759                                    case LauncherSettings.Favorites.CONTAINER_DESKTOP:
760                                        mItems.add(liveFolderInfo);
761                                        break;
762                                }
763                                mFolders.put(liveFolderInfo.id, liveFolderInfo);
764                                break;
765
766                            case LauncherSettings.Favorites.ITEM_TYPE_WIDGET_SEARCH:
767                                widgetInfo = Widget.makeSearch();
768
769                                container = c.getInt(containerIndex);
770                                if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
771                                    Log.e(TAG, "Widget found where container "
772                                            + "!= CONTAINER_DESKTOP  ignoring!");
773                                    continue;
774                                }
775
776                                widgetInfo.id = c.getLong(idIndex);
777                                widgetInfo.screen = c.getInt(screenIndex);
778                                widgetInfo.container = container;
779                                widgetInfo.cellX = c.getInt(cellXIndex);
780                                widgetInfo.cellY = c.getInt(cellYIndex);
781
782                                mItems.add(widgetInfo);
783                                break;
784
785                            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
786                                // Read all Launcher-specific widget details
787                                int appWidgetId = c.getInt(appWidgetIdIndex);
788                                appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId);
789                                appWidgetInfo.id = c.getLong(idIndex);
790                                appWidgetInfo.screen = c.getInt(screenIndex);
791                                appWidgetInfo.cellX = c.getInt(cellXIndex);
792                                appWidgetInfo.cellY = c.getInt(cellYIndex);
793                                appWidgetInfo.spanX = c.getInt(spanXIndex);
794                                appWidgetInfo.spanY = c.getInt(spanYIndex);
795
796                                container = c.getInt(containerIndex);
797                                if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
798                                    Log.e(TAG, "Widget found where container "
799                                            + "!= CONTAINER_DESKTOP -- ignoring!");
800                                    continue;
801                                }
802                                appWidgetInfo.container = c.getInt(containerIndex);
803
804                                mAppWidgets.add(appWidgetInfo);
805                                break;
806                            }
807                        } catch (Exception e) {
808                            Log.w(TAG, "Desktop items loading interrupted:", e);
809                        }
810                    }
811                } finally {
812                    c.close();
813                }
814                Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
815            }
816
817            /**
818             * Read everything out of our database.
819             */
820            private void bindWorkspace() {
821                final long t = SystemClock.uptimeMillis();
822
823                // Don't use these two variables in any of the callback runnables.
824                // Otherwise we hold a reference to them.
825                Callbacks callbacks = mCallbacks.get();
826                if (callbacks == null) {
827                    // This launcher has exited and nobody bothered to tell us.  Just bail.
828                    Log.w(TAG, "LoaderThread running with no launcher");
829                    return;
830                }
831
832                int N;
833                // Tell the workspace that we're about to start firing items at it
834                mHandler.post(new Runnable() {
835                    public void run() {
836                        Callbacks callbacks = tryGetCallbacks();
837                        if (callbacks != null) {
838                            callbacks.startBinding();
839                        }
840                    }
841                });
842                // Add the items to the workspace.
843                N = mItems.size();
844                for (int i=0; i<N; i+=ITEMS_CHUNK) {
845                    final int start = i;
846                    final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
847                    mHandler.post(new Runnable() {
848                        public void run() {
849                            Callbacks callbacks = tryGetCallbacks();
850                            if (callbacks != null) {
851                                callbacks.bindItems(mItems, start, start+chunkSize);
852                            }
853                        }
854                    });
855                }
856                mHandler.post(new Runnable() {
857                    public void run() {
858                        Callbacks callbacks = tryGetCallbacks();
859                        if (callbacks != null) {
860                            callbacks.bindFolders(mFolders);
861                        }
862                    }
863                });
864                // Wait until the queue goes empty.
865                mHandler.postIdle(new Runnable() {
866                    public void run() {
867                        Log.d(TAG, "Going to start binding widgets soon.");
868                    }
869                });
870                // Bind the widgets, one at a time.
871                // WARNING: this is calling into the workspace from the background thread,
872                // but since getCurrentScreen() just returns the int, we should be okay.  This
873                // is just a hint for the order, and if it's wrong, we'll be okay.
874                // TODO: instead, we should have that push the current screen into here.
875                final int currentScreen = callbacks.getCurrentWorkspaceScreen();
876                N = mAppWidgets.size();
877                // once for the current screen
878                for (int i=0; i<N; i++) {
879                    final LauncherAppWidgetInfo widget = mAppWidgets.get(i);
880                    if (widget.screen == currentScreen) {
881                        mHandler.post(new Runnable() {
882                            public void run() {
883                                Callbacks callbacks = tryGetCallbacks();
884                                if (callbacks != null) {
885                                    callbacks.bindAppWidget(widget);
886                                }
887                            }
888                        });
889                    }
890                }
891                // once for the other screens
892                for (int i=0; i<N; i++) {
893                    final LauncherAppWidgetInfo widget = mAppWidgets.get(i);
894                    if (widget.screen != currentScreen) {
895                        mHandler.post(new Runnable() {
896                            public void run() {
897                                Callbacks callbacks = tryGetCallbacks();
898                                if (callbacks != null) {
899                                    callbacks.bindAppWidget(widget);
900                                }
901                            }
902                        });
903                    }
904                }
905                // Tell the workspace that we're done.
906                mHandler.post(new Runnable() {
907                    public void run() {
908                        Callbacks callbacks = tryGetCallbacks();
909                        if (callbacks != null) {
910                            callbacks.finishBindingItems();
911                        }
912                    }
913                });
914                // If we're profiling, this is the last thing in the queue.
915                mHandler.post(new Runnable() {
916                    public void run() {
917                        Log.d(TAG, "bound workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
918                        if (Launcher.PROFILE_ROTATE) {
919                            android.os.Debug.stopMethodTracing();
920                        }
921                    }
922                });
923            }
924
925            private void loadAllApps() {
926                final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
927                mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
928
929                final Callbacks callbacks = tryGetCallbacks();
930                if (callbacks == null) {
931                    return;
932                }
933
934                final Context context = mContext;
935                final PackageManager packageManager = context.getPackageManager();
936
937                final List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
938
939                synchronized (mLock) {
940                    mBeforeFirstLoad = false;
941
942                    mAllAppsList.clear();
943                    if (apps != null) {
944                        long t = SystemClock.uptimeMillis();
945
946                        int N = apps.size();
947                        Utilities.BubbleText bubble = new Utilities.BubbleText(context);
948                        for (int i=0; i<N && !mStopped; i++) {
949                            // This builds the icon bitmaps.
950                            mAllAppsList.add(AppInfoCache.cache(apps.get(i), context, bubble));
951                        }
952                        Collections.sort(mAllAppsList.data, sComparator);
953                        Collections.sort(mAllAppsList.added, sComparator);
954                        Log.d(TAG, "cached app icons in " + (SystemClock.uptimeMillis()-t) + "ms");
955                    }
956                }
957            }
958
959            private void bindAllApps() {
960                synchronized (mLock) {
961                    final ArrayList<ApplicationInfo> results = mAllAppsList.added;
962                    mAllAppsList.added = new ArrayList<ApplicationInfo>();
963                    mHandler.post(new Runnable() {
964                        public void run() {
965                            final long t = SystemClock.uptimeMillis();
966                            final int count = results.size();
967
968                            Callbacks callbacks = tryGetCallbacks();
969                            if (callbacks != null) {
970                                callbacks.bindAllApplications(results);
971                            }
972
973                            Log.d(TAG, "bound app " + count + " icons in "
974                                + (SystemClock.uptimeMillis()-t) + "ms");
975                        }
976                    });
977                }
978            }
979        }
980    }
981
982    /**
983     * Make an ApplicationInfo object for an application.
984     */
985    private static ApplicationInfo getApplicationInfo(PackageManager manager, Intent intent,
986                                                      Context context) {
987        final ResolveInfo resolveInfo = manager.resolveActivity(intent, 0);
988
989        if (resolveInfo == null) {
990            return null;
991        }
992
993        final ApplicationInfo info = new ApplicationInfo();
994        final ActivityInfo activityInfo = resolveInfo.activityInfo;
995        info.icon = Utilities.createIconThumbnail(activityInfo.loadIcon(manager), context);
996        if (info.title == null || info.title.length() == 0) {
997            info.title = activityInfo.loadLabel(manager);
998        }
999        if (info.title == null) {
1000            info.title = "";
1001        }
1002        info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
1003        return info;
1004    }
1005
1006    /**
1007     * Make an ApplicationInfo object for a sortcut
1008     */
1009    private static ApplicationInfo getApplicationInfoShortcut(Cursor c, Context context,
1010            int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex) {
1011
1012        final ApplicationInfo info = new ApplicationInfo();
1013        info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
1014
1015        int iconType = c.getInt(iconTypeIndex);
1016        switch (iconType) {
1017        case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
1018            String packageName = c.getString(iconPackageIndex);
1019            String resourceName = c.getString(iconResourceIndex);
1020            PackageManager packageManager = context.getPackageManager();
1021            try {
1022                Resources resources = packageManager.getResourcesForApplication(packageName);
1023                final int id = resources.getIdentifier(resourceName, null, null);
1024                info.icon = Utilities.createIconThumbnail(resources.getDrawable(id), context);
1025            } catch (Exception e) {
1026                info.icon = packageManager.getDefaultActivityIcon();
1027            }
1028            info.iconResource = new Intent.ShortcutIconResource();
1029            info.iconResource.packageName = packageName;
1030            info.iconResource.resourceName = resourceName;
1031            info.customIcon = false;
1032            break;
1033        case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
1034            byte[] data = c.getBlob(iconIndex);
1035            try {
1036                Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
1037                info.icon = new FastBitmapDrawable(
1038                        Utilities.createBitmapThumbnail(bitmap, context));
1039            } catch (Exception e) {
1040                packageManager = context.getPackageManager();
1041                info.icon = packageManager.getDefaultActivityIcon();
1042            }
1043            info.filtered = true;
1044            info.customIcon = true;
1045            break;
1046        default:
1047            info.icon = context.getPackageManager().getDefaultActivityIcon();
1048            info.customIcon = false;
1049            break;
1050        }
1051        return info;
1052    }
1053
1054    private static void loadLiveFolderIcon(Context context, Cursor c, int iconTypeIndex,
1055            int iconPackageIndex, int iconResourceIndex, LiveFolderInfo liveFolderInfo) {
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                liveFolderInfo.icon = resources.getDrawable(id);
1067            } catch (Exception e) {
1068                liveFolderInfo.icon =
1069                        context.getResources().getDrawable(R.drawable.ic_launcher_folder);
1070            }
1071            liveFolderInfo.iconResource = new Intent.ShortcutIconResource();
1072            liveFolderInfo.iconResource.packageName = packageName;
1073            liveFolderInfo.iconResource.resourceName = resourceName;
1074            break;
1075        default:
1076            liveFolderInfo.icon =
1077                    context.getResources().getDrawable(R.drawable.ic_launcher_folder);
1078        }
1079    }
1080
1081    /**
1082     * Return an existing UserFolderInfo object if we have encountered this ID previously,
1083     * or make a new one.
1084     */
1085    private static UserFolderInfo findOrMakeUserFolder(HashMap<Long, FolderInfo> folders, long id) {
1086        // See if a placeholder was created for us already
1087        FolderInfo folderInfo = folders.get(id);
1088        if (folderInfo == null || !(folderInfo instanceof UserFolderInfo)) {
1089            // No placeholder -- create a new instance
1090            folderInfo = new UserFolderInfo();
1091            folders.put(id, folderInfo);
1092        }
1093        return (UserFolderInfo) folderInfo;
1094    }
1095
1096    /**
1097     * Return an existing UserFolderInfo object if we have encountered this ID previously, or make a
1098     * new one.
1099     */
1100    private static LiveFolderInfo findOrMakeLiveFolder(HashMap<Long, FolderInfo> folders, long id) {
1101        // See if a placeholder was created for us already
1102        FolderInfo folderInfo = folders.get(id);
1103        if (folderInfo == null || !(folderInfo instanceof LiveFolderInfo)) {
1104            // No placeholder -- create a new instance
1105            folderInfo = new LiveFolderInfo();
1106            folders.put(id, folderInfo);
1107        }
1108        return (LiveFolderInfo) folderInfo;
1109    }
1110
1111    private static void updateShortcutLabels(ContentResolver resolver, PackageManager manager) {
1112        final Cursor c = resolver.query(LauncherSettings.Favorites.CONTENT_URI,
1113                new String[] { LauncherSettings.Favorites._ID, LauncherSettings.Favorites.TITLE,
1114                        LauncherSettings.Favorites.INTENT, LauncherSettings.Favorites.ITEM_TYPE },
1115                null, null, null);
1116
1117        final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1118        final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
1119        final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
1120        final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
1121
1122        // boolean changed = false;
1123
1124        try {
1125            while (c.moveToNext()) {
1126                try {
1127                    if (c.getInt(itemTypeIndex) !=
1128                            LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
1129                        continue;
1130                    }
1131
1132                    final String intentUri = c.getString(intentIndex);
1133                    if (intentUri != null) {
1134                        final Intent shortcut = Intent.parseUri(intentUri, 0);
1135                        if (Intent.ACTION_MAIN.equals(shortcut.getAction())) {
1136                            final ComponentName name = shortcut.getComponent();
1137                            if (name != null) {
1138                                final ActivityInfo activityInfo = manager.getActivityInfo(name, 0);
1139                                final String title = c.getString(titleIndex);
1140                                String label = getLabel(manager, activityInfo);
1141
1142                                if (title == null || !title.equals(label)) {
1143                                    final ContentValues values = new ContentValues();
1144                                    values.put(LauncherSettings.Favorites.TITLE, label);
1145
1146                                    resolver.update(
1147                                            LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
1148                                            values, "_id=?",
1149                                            new String[] { String.valueOf(c.getLong(idIndex)) });
1150
1151                                    // changed = true;
1152                                }
1153                            }
1154                        }
1155                    }
1156                } catch (URISyntaxException e) {
1157                    // Ignore
1158                } catch (PackageManager.NameNotFoundException e) {
1159                    // Ignore
1160                }
1161            }
1162        } finally {
1163            c.close();
1164        }
1165
1166        // if (changed) resolver.notifyChange(Settings.Favorites.CONTENT_URI, null);
1167    }
1168
1169    private static String getLabel(PackageManager manager, ActivityInfo activityInfo) {
1170        String label = activityInfo.loadLabel(manager).toString();
1171        if (label == null) {
1172            label = manager.getApplicationLabel(activityInfo.applicationInfo).toString();
1173            if (label == null) {
1174                label = activityInfo.name;
1175            }
1176        }
1177        return label;
1178    }
1179
1180    private static final Collator sCollator = Collator.getInstance();
1181    private static final Comparator<ApplicationInfo> sComparator
1182            = new Comparator<ApplicationInfo>() {
1183        public final int compare(ApplicationInfo a, ApplicationInfo b) {
1184            return sCollator.compare(a.title.toString(), b.title.toString());
1185        }
1186    };
1187}
1188