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