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