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