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