LauncherModel.java 89.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

Joe Onorato's avatar
Joe Onorato committed
17
package com.android.launcher2;
18

19
import android.app.SearchManager;
20 21
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
22
import android.content.BroadcastReceiver;
23
import android.content.ComponentName;
24
import android.content.ContentProviderClient;
25 26
import android.content.ContentResolver;
import android.content.ContentValues;
27
import android.content.Context;
28
import android.content.Intent;
29
import android.content.Intent.ShortcutIconResource;
30
import android.content.pm.ActivityInfo;
31
import android.content.pm.PackageInfo;
32
import android.content.pm.PackageManager;
33
import android.content.pm.PackageManager.NameNotFoundException;
34
import android.content.pm.ResolveInfo;
35
import android.content.res.Configuration;
36 37 38 39 40
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
41
import android.os.Environment;
42 43
import android.os.Handler;
import android.os.HandlerThread;
44
import android.os.Parcelable;
45
import android.os.Process;
46
import android.os.RemoteException;
47
import android.os.SystemClock;
48
import android.util.Log;
49

50 51
import com.android.launcher.R;
import com.android.launcher2.InstallWidgetReceiver.WidgetMimeTypeHandlerData;
Romain Guy's avatar
Romain Guy committed
52

53 54 55 56 57 58 59 60 61
import java.lang.ref.WeakReference;
import java.net.URISyntaxException;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;

62 63 64
/**
 * Maintains in-memory state of the Launcher. It is expected that there should be only one
 * LauncherModel object held in a static. Also provide APIs for updating the database state
65
 * for the Launcher.
66
 */
67
public class LauncherModel extends BroadcastReceiver {
68
    static final boolean DEBUG_LOADERS = false;
69 70
    static final String TAG = "Launcher.Model";

71
    private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
72
    private final boolean mAppsCanBeOnExternalStorage;
73
    private int mBatchSize; // 0 is all apps at once
74
    private int mAllAppsLoadDelay; // milliseconds between batches
75

76
    private final LauncherApplication mApp;
77 78
    private final Object mLock = new Object();
    private DeferredHandler mHandler = new DeferredHandler();
79
    private LoaderTask mLoaderTask;
80

81 82 83 84 85 86
    private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
    static {
        sWorkerThread.start();
    }
    private static final Handler sWorker = new Handler(sWorkerThread.getLooper());

87 88 89 90 91 92
    // We start off with everything not loaded.  After that, we assume that
    // our monitoring of the package manager provides all updates and we never
    // need to do a requery.  These are only ever touched from the loader thread.
    private boolean mWorkspaceLoaded;
    private boolean mAllAppsLoaded;

93 94
    private WeakReference<Callbacks> mCallbacks;

95 96 97 98 99 100 101 102 103 104
    // < only access in worker thread >
    private AllAppsList mAllAppsList;

    // sItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
    // LauncherModel to their ids
    static final HashMap<Long, ItemInfo> sItemsIdMap = new HashMap<Long, ItemInfo>();

    // sItems is passed to bindItems, which expects a list of all folders and shortcuts created by
    //       LauncherModel that are directly on the home screen (however, no widgets or shortcuts
    //       within folders).
105
    static final ArrayList<ItemInfo> sWorkspaceItems = new ArrayList<ItemInfo>();
106

107 108 109 110 111 112
    // sAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
    static final ArrayList<LauncherAppWidgetInfo> sAppWidgets =
        new ArrayList<LauncherAppWidgetInfo>();

    // sFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
    static final HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>();
113 114 115 116

    // sDbIconCache is the set of ItemInfos that need to have their icons updated in the database
    static final HashMap<Object, byte[]> sDbIconCache = new HashMap<Object, byte[]>();

117 118 119
    // </ only access in worker thread >

    private IconCache mIconCache;
120
    private Bitmap mDefaultIcon;
121

122 123
    private static int mCellCountX;
    private static int mCellCountY;
124

125
    protected int mPreviousConfigMcc;
126

127
    public interface Callbacks {
128
        public boolean setLoadOnResume();
129 130 131
        public int getCurrentWorkspaceScreen();
        public void startBinding();
        public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end);
132
        public void bindFolders(HashMap<Long,FolderInfo> folders);
133 134 135
        public void finishBindingItems();
        public void bindAppWidget(LauncherAppWidgetInfo info);
        public void bindAllApplications(ArrayList<ApplicationInfo> apps);
136 137
        public void bindAppsAdded(ArrayList<ApplicationInfo> apps);
        public void bindAppsUpdated(ArrayList<ApplicationInfo> apps);
138
        public void bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent);
139
        public void bindPackagesUpdated();
140
        public boolean isAllAppsVisible();
141
        public boolean isAllAppsButtonRank(int rank);
142
        public void bindSearchablesChanged();
143
    }
144

145
    LauncherModel(LauncherApplication app, IconCache iconCache) {
146
        mAppsCanBeOnExternalStorage = !Environment.isExternalStorageEmulated();
147
        mApp = app;
148 149 150 151
        mAllAppsList = new AllAppsList(iconCache);
        mIconCache = iconCache;

        mDefaultIcon = Utilities.createIconBitmap(
Michael Jurka's avatar
Michael Jurka committed
152
                mIconCache.getFullResDefaultActivityIcon(), app);
153

154 155 156
        final Resources res = app.getResources();
        mAllAppsLoadDelay = res.getInteger(R.integer.config_allAppsBatchLoadDelay);
        mBatchSize = res.getInteger(R.integer.config_allAppsBatchSize);
157 158
        Configuration config = res.getConfiguration();
        mPreviousConfigMcc = config.mcc;
159 160
    }

161
    public Bitmap getFallbackIcon() {
162
        return Bitmap.createBitmap(mDefaultIcon);
163
    }
164

165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
    public void unbindWorkspaceItems() {
        sWorker.post(new Runnable() {
            @Override
            public void run() {
                unbindWorkspaceItemsOnMainThread();
            }
        });
    }

    /** Unbinds all the sWorkspaceItems on the main thread, and return a copy of sWorkspaceItems
     * that is save to reference from the main thread. */
    private ArrayList<ItemInfo> unbindWorkspaceItemsOnMainThread() {
        // Ensure that we don't use the same workspace items data structure on the main thread
        // by making a copy of workspace items first.
        final ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>(sWorkspaceItems);
Michael Jurka's avatar
Michael Jurka committed
180
        final ArrayList<ItemInfo> appWidgets = new ArrayList<ItemInfo>(sAppWidgets);
181
        mHandler.post(new Runnable() {
Michael Jurka's avatar
Michael Jurka committed
182
            @Override
183 184 185 186
            public void run() {
               for (ItemInfo item : workspaceItems) {
                   item.unbind();
               }
Michael Jurka's avatar
Michael Jurka committed
187 188 189
               for (ItemInfo item : appWidgets) {
                   item.unbind();
               }
190 191 192 193
            }
        });

        return workspaceItems;
194 195
    }

196 197 198 199 200 201 202 203 204 205 206 207 208 209
    /**
     * Adds an item to the DB if it was not created previously, or move it to a new
     * <container, screen, cellX, cellY>
     */
    static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
            int screen, int cellX, int cellY) {
        if (item.container == ItemInfo.NO_ID) {
            // From all apps
            addItemToDatabase(context, item, container, screen, cellX, cellY, false);
        } else {
            // From somewhere else
            moveItemInDatabase(context, item, container, screen, cellX, cellY);
        }
    }
210

Michael Jurka's avatar
Michael Jurka committed
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
    static void updateItemInDatabaseHelper(Context context, final ContentValues values,
            final ItemInfo item, final String callingFunction) {
        final long itemId = item.id;
        final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false);
        final ContentResolver cr = context.getContentResolver();

        Runnable r = new Runnable() {
            public void run() {
                cr.update(uri, values, null, null);

                ItemInfo modelItem = sItemsIdMap.get(itemId);
                if (item != modelItem) {
                    // the modelItem needs to match up perfectly with item if our model is to be
                    // consistent with the database-- for now, just require modelItem == item
                    String msg = "item: " + ((item != null) ? item.toString() : "null") +
                        "modelItem: " + ((modelItem != null) ? modelItem.toString() : "null") +
                        "Error: ItemInfo passed to " + callingFunction + " doesn't match original";
                    throw new RuntimeException(msg);
                }

                // Items are added/removed from the corresponding FolderInfo elsewhere, such
                // as in Workspace.onDrop. Here, we just add/remove them from the list of items
                // that are on the desktop, as appropriate
                if (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
                        modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
                    if (!sWorkspaceItems.contains(modelItem)) {
                        sWorkspaceItems.add(modelItem);
                    }
                } else {
                    sWorkspaceItems.remove(modelItem);
                }
            }
        };

        if (sWorkerThread.getThreadId() == Process.myTid()) {
            r.run();
        } else {
            sWorker.post(r);
        }
    }
251

252 253 254
    /**
     * Move an item in the DB to a new <container, screen, cellX, cellY>
     */
255 256
    static void moveItemInDatabase(Context context, final ItemInfo item, final long container,
            final int screen, final int cellX, final int cellY) {
257 258 259
        item.container = container;
        item.cellX = cellX;
        item.cellY = cellY;
Michael Jurka's avatar
Michael Jurka committed
260

261 262 263 264 265 266 267 268
        // We store hotseat items in canonical form which is this orientation invariant position
        // in the hotseat
        if (context instanceof Launcher && screen < 0 &&
                container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
            item.screen = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
        } else {
            item.screen = screen;
        }
269

270 271
        final ContentValues values = new ContentValues();
        values.put(LauncherSettings.Favorites.CONTAINER, item.container);
272 273
        values.put(LauncherSettings.Favorites.CELLX, item.cellX);
        values.put(LauncherSettings.Favorites.CELLY, item.cellY);
274
        values.put(LauncherSettings.Favorites.SCREEN, item.screen);
275

Michael Jurka's avatar
Michael Jurka committed
276
        updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
277 278
    }

279
    /**
280
     * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
281
     */
282 283 284
    static void modifyItemInDatabase(Context context, final ItemInfo item, final long container,
            final int screen, final int cellX, final int cellY, final int spanX, final int spanY) {
        item.container = container;
285 286
        item.cellX = cellX;
        item.cellY = cellY;
287 288 289 290 291 292 293 294 295 296 297
        item.spanX = spanX;
        item.spanY = spanY;

        // We store hotseat items in canonical form which is this orientation invariant position
        // in the hotseat
        if (context instanceof Launcher && screen < 0 &&
                container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
            item.screen = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
        } else {
            item.screen = screen;
        }
298 299 300

        final ContentValues values = new ContentValues();
        values.put(LauncherSettings.Favorites.CONTAINER, item.container);
301 302 303 304 305
        values.put(LauncherSettings.Favorites.CELLX, item.cellX);
        values.put(LauncherSettings.Favorites.CELLY, item.cellY);
        values.put(LauncherSettings.Favorites.SPANX, item.spanX);
        values.put(LauncherSettings.Favorites.SPANY, item.spanY);
        values.put(LauncherSettings.Favorites.SCREEN, item.screen);
306

307 308
        updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
    }
Michael Jurka's avatar
Michael Jurka committed
309 310 311 312 313 314 315 316 317

    /**
     * Update an item to the database in a specified container.
     */
    static void updateItemInDatabase(Context context, final ItemInfo item) {
        final ContentValues values = new ContentValues();
        item.onAddToDatabase(values);
        item.updateValuesWithCoordinates(values, item.cellX, item.cellY);
        updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
318 319
    }

320
    /**
321 322
     * Returns true if the shortcuts already exists in the database.
     * we identify a shortcut by its title and intent.
323
     */
324 325 326 327 328 329 330 331 332 333 334 335
    static boolean shortcutExists(Context context, String title, Intent intent) {
        final ContentResolver cr = context.getContentResolver();
        Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
            new String[] { "title", "intent" }, "title=? and intent=?",
            new String[] { title, intent.toUri(0) }, null);
        boolean result = false;
        try {
            result = c.moveToFirst();
        } finally {
            c.close();
        }
        return result;
336 337
    }

338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
    /**
     * Returns an ItemInfo array containing all the items in the LauncherModel.
     * The ItemInfo.id is not set through this function.
     */
    static ArrayList<ItemInfo> getItemsInLocalCoordinates(Context context) {
        ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
        final ContentResolver cr = context.getContentResolver();
        Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, new String[] {
                LauncherSettings.Favorites.ITEM_TYPE, LauncherSettings.Favorites.CONTAINER,
                LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.CELLX, LauncherSettings.Favorites.CELLY,
                LauncherSettings.Favorites.SPANX, LauncherSettings.Favorites.SPANY }, null, null, null);

        final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
        final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
        final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
        final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
        final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
        final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
        final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);

        try {
            while (c.moveToNext()) {
Michael Jurka's avatar
Michael Jurka committed
360
                ItemInfo item = new ItemInfo();
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
                item.cellX = c.getInt(cellXIndex);
                item.cellY = c.getInt(cellYIndex);
                item.spanX = c.getInt(spanXIndex);
                item.spanY = c.getInt(spanYIndex);
                item.container = c.getInt(containerIndex);
                item.itemType = c.getInt(itemTypeIndex);
                item.screen = c.getInt(screenIndex);

                items.add(item);
            }
        } catch (Exception e) {
            items.clear();
        } finally {
            c.close();
        }

        return items;
    }

380
    /**
381
     * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList.
382
     */
383 384 385 386 387
    FolderInfo getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id) {
        final ContentResolver cr = context.getContentResolver();
        Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null,
                "_id=? and (itemType=? or itemType=?)",
                new String[] { String.valueOf(id),
388
                        String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)}, null);
389

390 391 392 393 394 395 396 397
        try {
            if (c.moveToFirst()) {
                final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
                final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
                final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
                final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
                final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
                final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
398

399 400
                FolderInfo folderInfo = null;
                switch (c.getInt(itemTypeIndex)) {
401 402
                    case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                        folderInfo = findOrMakeFolder(folderList, id);
403 404
                        break;
                }
405

406 407 408 409
                folderInfo.title = c.getString(titleIndex);
                folderInfo.id = id;
                folderInfo.container = c.getInt(containerIndex);
                folderInfo.screen = c.getInt(screenIndex);
410 411
                folderInfo.cellX = c.getInt(cellXIndex);
                folderInfo.cellY = c.getInt(cellYIndex);
412

413 414 415 416
                return folderInfo;
            }
        } finally {
            c.close();
417
        }
418

419 420
        return null;
    }
421

422 423 424 425
    /**
     * Add an item to the database in a specified container. Sets the container, screen, cellX and
     * cellY fields of the item. Also assigns an ID to the item.
     */
426 427
    static void addItemToDatabase(Context context, final ItemInfo item, final long container,
            final int screen, final int cellX, final int cellY, final boolean notify) {
428 429 430
        item.container = container;
        item.cellX = cellX;
        item.cellY = cellY;
431 432 433 434 435 436 437 438
        // We store hotseat items in canonical form which is this orientation invariant position
        // in the hotseat
        if (context instanceof Launcher && screen < 0 &&
                container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
            item.screen = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
        } else {
            item.screen = screen;
        }
439

440 441 442
        final ContentValues values = new ContentValues();
        final ContentResolver cr = context.getContentResolver();
        item.onAddToDatabase(values);
443

444
        LauncherApplication app = (LauncherApplication) context.getApplicationContext();
445 446
        item.id = app.getLauncherProvider().generateNewId();
        values.put(LauncherSettings.Favorites._ID, item.id);
447
        item.updateValuesWithCoordinates(values, item.cellX, item.cellY);
448

Michael Jurka's avatar
Michael Jurka committed
449
        Runnable r = new Runnable() {
450 451 452
            public void run() {
                cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI :
                        LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values);
453

454 455 456 457 458
                if (sItemsIdMap.containsKey(item.id)) {
                    // we should not be adding new items in the db with the same id
                    throw new RuntimeException("Error: ItemInfo id (" + item.id + ") passed to " +
                        "addItemToDatabase already exists." + item.toString());
                }
459 460 461 462
                sItemsIdMap.put(item.id, item);
                switch (item.itemType) {
                    case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                        sFolders.put(item.id, (FolderInfo) item);
463
                        // Fall through
464 465
                    case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                    case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
466 467
                        if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
                                item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
468
                            sWorkspaceItems.add(item);
469 470 471 472 473 474 475
                        }
                        break;
                    case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
                        sAppWidgets.add((LauncherAppWidgetInfo) item);
                        break;
                }
            }
Michael Jurka's avatar
Michael Jurka committed
476 477 478 479 480 481 482
        };

        if (sWorkerThread.getThreadId() == Process.myTid()) {
            r.run();
        } else {
            sWorker.post(r);
        }
483 484
    }

485 486 487
    /**
     * Creates a new unique child id, for a given cell span across all layouts.
     */
488
    static int getCellLayoutChildId(
489 490
            long container, int screen, int localCellX, int localCellY, int spanX, int spanY) {
        return (((int) container & 0xFF) << 24)
491
                | (screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF);
492 493
    }

494 495
    static int getCellCountX() {
        return mCellCountX;
496 497
    }

498 499
    static int getCellCountY() {
        return mCellCountY;
500 501 502 503 504 505 506
    }

    /**
     * Updates the model orientation helper to take into account the current layout dimensions
     * when performing local/canonical coordinate transformations.
     */
    static void updateWorkspaceLayoutCells(int shortAxisCellCount, int longAxisCellCount) {
507 508
        mCellCountX = shortAxisCellCount;
        mCellCountY = longAxisCellCount;
509 510
    }

511
    /**
Michael Jurka's avatar
Michael Jurka committed
512 513 514
     * Removes the specified item from the database
     * @param context
     * @param item
515
     */
Michael Jurka's avatar
Michael Jurka committed
516
    static void deleteItemFromDatabase(Context context, final ItemInfo item) {
517
        final ContentResolver cr = context.getContentResolver();
Michael Jurka's avatar
Michael Jurka committed
518
        final Uri uriToDelete = LauncherSettings.Favorites.getContentUri(item.id, false);
519
        Runnable r = new Runnable() {
520
            public void run() {
Michael Jurka's avatar
Michael Jurka committed
521 522 523 524 525 526 527 528 529 530 531 532 533
                cr.delete(uriToDelete, null, null);
                switch (item.itemType) {
                    case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                        sFolders.remove(item.id);
                        sWorkspaceItems.remove(item);
                        break;
                    case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                    case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                        sWorkspaceItems.remove(item);
                        break;
                    case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
                        sAppWidgets.remove((LauncherAppWidgetInfo) item);
                        break;
534
                }
Michael Jurka's avatar
Michael Jurka committed
535 536
                sItemsIdMap.remove(item.id);
                sDbIconCache.remove(item);
537
            }
538 539 540 541 542 543
        };
        if (sWorkerThread.getThreadId() == Process.myTid()) {
            r.run();
        } else {
            sWorker.post(r);
        }
544 545
    }

546 547 548
    /**
     * Remove the contents of the specified folder from the database
     */
549
    static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) {
550
        final ContentResolver cr = context.getContentResolver();
551

Michael Jurka's avatar
Michael Jurka committed
552 553 554 555 556 557 558 559 560 561 562 563 564
        Runnable r = new Runnable() {
            public void run() {
                cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null);
                sItemsIdMap.remove(info.id);
                sFolders.remove(info.id);
                sDbIconCache.remove(info);
                sWorkspaceItems.remove(info);

                cr.delete(LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
                        LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
                for (ItemInfo childInfo : info.contents) {
                    sItemsIdMap.remove(childInfo.id);
                    sDbIconCache.remove(childInfo);
565
                }
Michael Jurka's avatar
Michael Jurka committed
566 567 568 569 570 571 572
            }
        };
        if (sWorkerThread.getThreadId() == Process.myTid()) {
            r.run();
        } else {
            sWorker.post(r);
        }
573
    }
574

575 576 577 578 579 580
    /**
     * Set this as the current Launcher activity object for the loader.
     */
    public void initialize(Callbacks callbacks) {
        synchronized (mLock) {
            mCallbacks = new WeakReference<Callbacks>(callbacks);
581 582 583
        }
    }

584 585 586 587
    /**
     * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
     * ACTION_PACKAGE_CHANGED.
     */
588
    @Override
589
    public void onReceive(Context context, Intent intent) {
590
        if (DEBUG_LOADERS) Log.d(TAG, "onReceive intent=" + intent);
591

592
        final String action = intent.getAction();
593

594 595 596 597 598
        if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
                || Intent.ACTION_PACKAGE_REMOVED.equals(action)
                || Intent.ACTION_PACKAGE_ADDED.equals(action)) {
            final String packageName = intent.getData().getSchemeSpecificPart();
            final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
599

600
            int op = PackageUpdatedTask.OP_NONE;
601

602 603 604 605
            if (packageName == null || packageName.length() == 0) {
                // they sent us a bad intent
                return;
            }
606

607 608 609 610 611
            if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
                op = PackageUpdatedTask.OP_UPDATE;
            } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
                if (!replacing) {
                    op = PackageUpdatedTask.OP_REMOVE;
612
                }
613 614 615 616 617 618 619
                // else, we are replacing the package, so a PACKAGE_ADDED will be sent
                // later, we will update the package at this time
            } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
                if (!replacing) {
                    op = PackageUpdatedTask.OP_ADD;
                } else {
                    op = PackageUpdatedTask.OP_UPDATE;
620
                }
621
            }
622

623 624
            if (op != PackageUpdatedTask.OP_NONE) {
                enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName }));
625 626
            }

627
        } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
628 629 630 631
            // First, schedule to add these apps back in.
            String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
            enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packages));
            // Then, rebind everything.
632
            startLoaderFromBackground();
633 634 635 636
        } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
            String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
            enqueuePackageUpdated(new PackageUpdatedTask(
                        PackageUpdatedTask.OP_UNAVAILABLE, packages));
637
        } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
638 639 640 641 642 643 644
            // If we have changed locale we need to clear out the labels in all apps/workspace.
            forceReload();
        } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
             // Check if configuration change was an mcc/mnc change which would affect app resources
             // and we would need to clear out the labels in all apps/workspace. Same handling as
             // above for ACTION_LOCALE_CHANGED
             Configuration currentConfig = context.getResources().getConfiguration();
645
             if (mPreviousConfigMcc != currentConfig.mcc) {
646
                   Log.d(TAG, "Reload apps on config change. curr_mcc:"
647
                       + currentConfig.mcc + " prevmcc:" + mPreviousConfigMcc);
648 649 650
                   forceReload();
             }
             // Update previousConfig
651
             mPreviousConfigMcc = currentConfig.mcc;
652 653
        } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) ||
                   SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) {
654 655 656 657 658
            if (mCallbacks != null) {
                Callbacks callbacks = mCallbacks.get();
                if (callbacks != null) {
                    callbacks.bindSearchablesChanged();
                }
659
            }
660 661 662
        }
    }

663
    private void forceReload() {
664 665 666 667 668 669 670 671 672
        resetLoadedState(true, true);

        // Do this here because if the launcher activity is running it will be restarted.
        // If it's not running startLoaderFromBackground will merely tell it that it needs
        // to reload.
        startLoaderFromBackground();
    }

    public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
673 674 675 676
        synchronized (mLock) {
            // Stop any existing loaders first, so they don't set mAllAppsLoaded or
            // mWorkspaceLoaded to true later
            stopLoaderLocked();
677 678
            if (resetAllAppsLoaded) mAllAppsLoaded = false;
            if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
679 680 681
        }
    }

682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699
    /**
     * When the launcher is in the background, it's possible for it to miss paired
     * configuration changes.  So whenever we trigger the loader from the background
     * tell the launcher that it needs to re-run the loader when it comes back instead
     * of doing it now.
     */
    public void startLoaderFromBackground() {
        boolean runLoader = false;
        if (mCallbacks != null) {
            Callbacks callbacks = mCallbacks.get();
            if (callbacks != null) {
                // Only actually run the loader if they're not paused.
                if (!callbacks.setLoadOnResume()) {
                    runLoader = true;
                }
            }
        }
        if (runLoader) {
700
            startLoader(false);
701
        }
702
    }
703

704 705 706 707 708 709 710 711 712 713 714 715 716 717
    // If there is already a loader task running, tell it to stop.
    // returns true if isLaunching() was true on the old task
    private boolean stopLoaderLocked() {
        boolean isLaunching = false;
        LoaderTask oldTask = mLoaderTask;
        if (oldTask != null) {
            if (oldTask.isLaunching()) {
                isLaunching = true;
            }
            oldTask.stopLocked();
        }
        return isLaunching;
    }

718
    public void startLoader(boolean isLaunching) {
719 720 721 722
        synchronized (mLock) {
            if (DEBUG_LOADERS) {
                Log.d(TAG, "startLoader isLaunching=" + isLaunching);
            }
723

724 725 726
            // Don't bother to start the thread if we know it's not going to do anything
            if (mCallbacks != null && mCallbacks.get() != null) {
                // If there is already one running, tell it to stop.
727 728
                // also, don't downgrade isLaunching if we're already running
                isLaunching = isLaunching || stopLoaderLocked();
729
                mLoaderTask = new LoaderTask(mApp, isLaunching);
Winson Chung's avatar
Winson Chung committed
730
                sWorkerThread.setPriority(Thread.NORM_PRIORITY);
731
                sWorker.post(mLoaderTask);
732 733
            }
        }
734
    }
735

736 737 738 739
    public void stopLoader() {
        synchronized (mLock) {
            if (mLoaderTask != null) {
                mLoaderTask.stopLocked();
740
            }
741
        }
742
    }
743

744 745 746 747
    public boolean isAllAppsLoaded() {
        return mAllAppsLoaded;
    }

748 749 750 751 752 753 754 755 756 757 758 759
    /**
     * Runnable for the thread that loads the contents of the launcher:
     *   - workspace icons
     *   - widgets
     *   - all apps icons
     */
    private class LoaderTask implements Runnable {
        private Context mContext;
        private Thread mWaitThread;
        private boolean mIsLaunching;
        private boolean mStopped;
        private boolean mLoadAndBindStepFinished;
760
        private HashMap<Object, CharSequence> mLabelCache;
761 762 763 764

        LoaderTask(Context context, boolean isLaunching) {
            mContext = context;
            mIsLaunching = isLaunching;
765
            mLabelCache = new HashMap<Object, CharSequence>();
766
        }
767

768 769 770
        boolean isLaunching() {
            return mIsLaunching;
        }
771

772 773 774 775 776
        private void loadAndBindWorkspace() {
            // Load the workspace
            if (DEBUG_LOADERS) {
                Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
            }
777

778
            if (!mWorkspaceLoaded) {
779
                loadWorkspace();
780 781 782 783 784
                synchronized (LoaderTask.this) {
                    if (mStopped) {
                        return;
                    }
                    mWorkspaceLoaded = true;
785
                }
786
            }
787

788 789 790
            // Bind the workspace
            bindWorkspace();
        }
791

792 793 794 795 796 797
        private void waitForIdle() {
            // Wait until the either we're stopped or the other threads are done.
            // This way we don't start loading all apps until the workspace has settled
            // down.
            synchronized (LoaderTask.this) {
                final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
798

799 800 801 802 803 804
                mHandler.postIdle(new Runnable() {
                        public void run() {
                            synchronized (LoaderTask.this) {
                                mLoadAndBindStepFinished = true;
                                if (DEBUG_LOADERS) {
                                    Log.d(TAG, "done with previous binding step");
805
                                }
806
                                LoaderTask.this.notify();
807 808
                            }
                        }
809 810 811 812 813 814 815
                    });

                while (!mStopped && !mLoadAndBindStepFinished) {
                    try {
                        this.wait();
                    } catch (InterruptedException ex) {
                        // Ignore
816
                    }
817 818 819
                }
                if (DEBUG_LOADERS) {
                    Log.d(TAG, "waited "
820
                            + (SystemClock.uptimeMillis()-workspaceWaitTime)
821
                            + "ms for previous step to finish binding");
822
                }
823
            }
824
        }
825

826 827 828 829 830 831
        public void run() {
            // Optimize for end-user experience: if the Launcher is up and // running with the
            // All Apps interface in the foreground, load All Apps first. Otherwise, load the
            // workspace first (default).
            final Callbacks cbk = mCallbacks.get();
            final boolean loadWorkspaceFirst = cbk != null ? (!cbk.isAllAppsVisible()) : true;
832

833
            keep_running: {
834 835
                // Elevate priority when Home launches for the first time to avoid
                // starving at boot time. Staring at a blank home is not cool.
836
                synchronized (mLock) {
837 838
                    if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " +
                            (mIsLaunching ? "DEFAULT" : "BACKGROUND"));
839 840
                    android.os.Process.setThreadPriority(mIsLaunching
                            ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
841
                }
842 843 844 845 846
                if (loadWorkspaceFirst) {
                    if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
                    loadAndBindWorkspace();
                } else {
                    if (DEBUG_LOADERS) Log.d(TAG, "step 1: special: loading all apps");
847
                    loadAndBindAllApps();
848
                }
849

850 851 852 853 854 855
                if (mStopped) {
                    break keep_running;
                }

                // Whew! Hard work done.  Slow us down, and wait until the UI thread has
                // settled down.
856
                synchronized (mLock) {
857
                    if (mIsLaunching) {
858
                        if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND");
859
                        android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
860 861
                    }
                }
862
                waitForIdle();
863

864 865 866
                // second step
                if (loadWorkspaceFirst) {
                    if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
867
                    loadAndBindAllApps();
868 869 870 871
                } else {
                    if (DEBUG_LOADERS) Log.d(TAG, "step 2: special: loading workspace");
                    loadAndBindWorkspace();
                }
Winson Chung's avatar
Winson Chung committed
872 873 874 875 876

                // Restore the default thread priority after we are done loading items
                synchronized (mLock) {
                    android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
                }
877
            }
878

879 880 881

            // Update the saved icons if necessary
            if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons");
882 883
            for (Object key : sDbIconCache.keySet()) {
                updateSavedIcon(mContext, (ShortcutInfo) key, sDbIconCache.get(key));
884
            }
885
            sDbIconCache.clear();
886

887 888 889
            // Clear out this reference, otherwise we end up holding it until all of the
            // callback runnables are done.
            mContext = null;
890

891 892 893 894
            synchronized (mLock) {
                // If we are still the last one to be scheduled, remove ourselves.
                if (mLoaderTask == this) {
                    mLoaderTask = null;
895
                }
896 897
            }
        }
898

899 900 901 902
        public void stopLocked() {
            synchronized (LoaderTask.this) {
                mStopped = true;
                this.notify();
903
            }
904
        }
905

906 907 908 909 910 911 912 913 914 915 916 917
        /**
         * Gets the callbacks object.  If we've been stopped, or if the launcher object
         * has somehow been garbage collected, return null instead.  Pass in the Callbacks
         * object that was around when the deferred message was scheduled, and if there's
         * a new Callbacks object around then also return null.  This will save us from
         * calling onto it with data that will be ignored.
         */
        Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
            synchronized (mLock) {
                if (mStopped) {
                    return null;
                }
918

919 920 921
                if (mCallbacks == null) {
                    return null;
                }
922

923 924 925 926 927 928 929
                final Callbacks callbacks = mCallbacks.get();
                if (callbacks != oldCallbacks) {
                    return null;
                }
                if (callbacks == null) {
                    Log.w(TAG, "no mCallbacks");
                    return null;
930
                }
931 932

                return callbacks;
933
            }
934
        }
935

936 937
        // check & update map of what's occupied; used to discard overlapping/invalid items
        private boolean checkItemPlacement(ItemInfo occupied[][][], ItemInfo item) {
Winson Chung's avatar
Winson Chung committed
938 939 940
            int containerIndex = item.screen;
            if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
                // Return early if we detect that an item is under the hotseat button
941
                if (mCallbacks == null || mCallbacks.get().isAllAppsButtonRank(item.screen)) {
Winson Chung's avatar
Winson Chung committed
942 943
                    return false;
                }
944 945 946 947 948 949 950 951 952 953 954 955

                // We use the last index to refer to the hotseat and the screen as the rank, so
                // test and update the occupied state accordingly
                if (occupied[Launcher.SCREEN_COUNT][item.screen][0] != null) {
                    Log.e(TAG, "Error loading shortcut into hotseat " + item
                        + " into position (" + item.screen + ":" + item.cellX + "," + item.cellY
                        + ") occupied by " + occupied[Launcher.SCREEN_COUNT][item.screen][0]);
                    return false;
                } else {
                    occupied[Launcher.SCREEN_COUNT][item.screen][0] = item;
                    return true;
                }
Winson Chung's avatar
Winson Chung committed
956 957
            } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
                // Skip further checking if it is not the hotseat or workspace container
958 959
                return true;
            }
Winson Chung's avatar
Winson Chung committed
960

961
            // Check if any workspace icons overlap with each other
962 963
            for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
                for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
Winson Chung's avatar
Winson Chung committed
964
                    if (occupied[containerIndex][x][y] != null) {
965
                        Log.e(TAG, "Error loading shortcut " + item
Winson Chung's avatar
Winson Chung committed
966
                            + " into cell (" + containerIndex + "-" + item.screen + ":"
967
                            + x + "," + y
968
                            + ") occupied by "
Winson Chung's avatar
Winson Chung committed
969
                            + occupied[containerIndex][x][y]);
970
                        return false;
971 972
                    }
                }
973 974 975
            }
            for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
                for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
Winson Chung's avatar
Winson Chung committed
976
                    occupied[containerIndex][x][y] = item;
977 978
                }
            }
Winson Chung's avatar
Winson Chung committed
979

980 981
            return true;
        }
982

983 984
        private void loadWorkspace() {
            final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
985

986 987 988 989 990
            final Context context = mContext;
            final ContentResolver contentResolver = context.getContentResolver();
            final PackageManager manager = context.getPackageManager();
            final AppWidgetManager widgets = AppWidgetManager.getInstance(context);
            final boolean isSafeMode = manager.isSafeMode();
991

992 993 994
            // Make sure the default workspace is loaded, if needed
            mApp.getLauncherProvider().loadDefaultFavoritesIfNecessary();

995
            sWorkspaceItems.clear();
996 997 998
            sAppWidgets.clear();
            sFolders.clear();
            sItemsIdMap.clear();
999
            sDbIconCache.clear();
1000

1001
            final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
1002

1003
            final Cursor c = contentResolver.query(
1004
                    LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
1005

Winson Chung's avatar
Winson Chung committed
1006
            // +1 for the hotseat (it can be larger than the workspace)
1007 1008
            // Load workspace in reverse order to ensure that latest items are loaded first (and
            // before any earlier duplicates)
1009
            final ItemInfo occupied[][][] =
Winson Chung's avatar
Winson Chung committed
1010
                    new ItemInfo[Launcher.SCREEN_COUNT + 1][mCellCountX + 1][mCellCountY + 1];
1011

1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040
            try {
                final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
                final int intentIndex = c.getColumnIndexOrThrow
                        (LauncherSettings.Favorites.INTENT);
                final int titleIndex = c.getColumnIndexOrThrow
                        (LauncherSettings.Favorites.TITLE);
                final int iconTypeIndex = c.getColumnIndexOrThrow(
                        LauncherSettings.Favorites.ICON_TYPE);
                final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
                final int iconPackageIndex = c.getColumnIndexOrThrow(
                        LauncherSettings.Favorites.ICON_PACKAGE);
                final int iconResourceIndex = c.getColumnIndexOrThrow(
                        LauncherSettings.Favorites.ICON_RESOURCE);
                final int containerIndex = c.getColumnIndexOrThrow(
                        LauncherSettings.Favorites.CONTAINER);
                final int itemTypeIndex = c.getColumnIndexOrThrow(
                        LauncherSettings.Favorites.ITEM_TYPE);
                final int appWidgetIdIndex = c.getColumnIndexOrThrow(
                        LauncherSettings.Favorites.APPWIDGET_ID);
                final int screenIndex = c.getColumnIndexOrThrow(
                        LauncherSettings.Favorites.SCREEN);
                final int cellXIndex = c.getColumnIndexOrThrow
                        (LauncherSettings.Favorites.CELLX);
                final int cellYIndex = c.getColumnIndexOrThrow
                        (LauncherSettings.Favorites.CELLY);
                final int spanXIndex = c.getColumnIndexOrThrow
                        (LauncherSettings.Favorites.SPANX);
                final int spanYIndex = c.getColumnIndexOrThrow(
                        LauncherSettings.Favorites.SPANY);
1041 1042 1043
                //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
                //final int displayModeIndex = c.getColumnIndexOrThrow(
                //        LauncherSettings.Favorites.DISPLAY_MODE);
1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067

                ShortcutInfo info;
                String intentDescription;
                LauncherAppWidgetInfo appWidgetInfo;
                int container;
                long id;
                Intent intent;

                while (!mStopped && c.moveToNext()) {
                    try {
                        int itemType = c.getInt(itemTypeIndex);

                        switch (itemType) {
                        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                        case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                            intentDescription = c.getString(intentIndex);
                            try {
                                intent = Intent.parseUri(intentDescription, 0);
                            } catch (URISyntaxException e) {
                                continue;
                            }

                            if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
                                info = getShortcutInfo(manager, intent, context, c, iconIndex,
1068
                                        titleIndex, mLabelCache);
1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080
                            } else {
                                info = getShortcutInfo(c, context, iconTypeIndex,
                                        iconPackageIndex, iconResourceIndex, iconIndex,
                                        titleIndex);
                            }

                            if (info != null) {
                                info.intent = intent;
                                info.id = c.getLong(idIndex);
                                container = c.getInt(containerIndex);
                                info.container = container;
                                info.screen = c.getInt(screenIndex);
1081 1082
                                info.cellX = c.getInt(cellXIndex);
                                info.cellY = c.getInt(cellYIndex);
1083 1084 1085 1086

                                // check & update map of what's occupied
                                if (!checkItemPlacement(occupied, info)) {
                                    break;
1087
                                }
1088

1089 1090
                                switch (container) {
                                case LauncherSettings.Favorites.CONTAINER_DESKTOP:
1091
                                case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
1092
                                    sWorkspaceItems.add(info);
1093 1094 1095
                                    break;
                                default:
                                    // Item is in a user folder
1096
                                    FolderInfo folderInfo =
1097
                                            findOrMakeFolder(sFolders, container);
1098 1099
                                    folderInfo.add(info);
                                    break;
1100
                                }
1101
                                sItemsIdMap.put(info.id, info);
1102 1103 1104

                                // now that we've loaded everthing re-save it with the
                                // icon in case it disappears somehow.
1105
                                queueIconToBeChecked(sDbIconCache, info, c, iconIndex);
1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116
                            } else {
                                // Failed to load the shortcut, probably because the
                                // activity manager couldn't resolve it (maybe the app
                                // was uninstalled), or the db row was somehow screwed up.
                                // Delete it.
                                id = c.getLong(idIndex);
                                Log.e(TAG, "Error loading shortcut " + id + ", removing it");
                                contentResolver.delete(LauncherSettings.Favorites.getContentUri(
                                            id, false), null, null);
                            }
                            break;
1117

1118
                        case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1119
                            id = c.getLong(idIndex);
1120
                            FolderInfo folderInfo = findOrMakeFolder(sFolders, id);
1121

1122
                            folderInfo.title = c.getString(titleIndex);
1123 1124 1125 1126
                            folderInfo.id = id;
                            container = c.getInt(containerIndex);
                            folderInfo.container = container;
                            folderInfo.screen = c.getInt(screenIndex);
1127 1128
                            folderInfo.cellX = c.getInt(cellXIndex);
                            folderInfo.cellY = c.getInt(cellYIndex);
1129

1130 1131
                            // check & update map of what's occupied
                            if (!checkItemPlacement(occupied, folderInfo)) {
1132
                                break;
1133 1134 1135
                            }
                            switch (container) {
                                case LauncherSettings.Favorites.CONTAINER_DESKTOP:
1136
                                case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
1137
                                    sWorkspaceItems.add(folderInfo);
1138 1139 1140
                                    break;
                            }

1141 1142
                            sItemsIdMap.put(folderInfo.id, folderInfo);
                            sFolders.put(folderInfo.id, folderInfo);
1143 1144 1145 1146 1147 1148 1149 1150 1151
                            break;

                        case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
                            // Read all Launcher-specific widget details
                            int appWidgetId = c.getInt(appWidgetIdIndex);
                            id = c.getLong(idIndex);

                            final AppWidgetProviderInfo provider =
                                    widgets.getAppWidgetInfo(appWidgetId);
1152

1153 1154
                            if (!isSafeMode && (provider == null || provider.provider == null ||
                                    provider.provider.getPackageName() == null)) {
1155 1156 1157 1158
                                String log = "Deleting widget that isn't installed anymore: id="
                                    + id + " appWidgetId=" + appWidgetId;
                                Log.e(TAG, log); 
                                Launcher.sDumpLogs.add(log);
1159 1160
                                itemsToRemove.add(id);
                            } else {
Michael Jurka's avatar
Michael Jurka committed
1161
                                appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId);
1162 1163
                                appWidgetInfo.id = id;
                                appWidgetInfo.screen = c.getInt(screenIndex);
1164 1165 1166 1167
                                appWidgetInfo.cellX = c.getInt(cellXIndex);
                                appWidgetInfo.cellY = c.getInt(cellYIndex);
                                appWidgetInfo.spanX = c.getInt(spanXIndex);
                                appWidgetInfo.spanY = c.getInt(spanYIndex);
1168

1169
                                container = c.getInt(containerIndex);
1170 1171
                                if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
                                    container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1172
                                    Log.e(TAG, "Widget found where container "
1173
                                        + "!= CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
1174
                                    continue;
1175
                                }
1176
                                appWidgetInfo.container = c.getInt(containerIndex);
1177

1178 1179 1180
                                // check & update map of what's occupied
                                if (!checkItemPlacement(occupied, appWidgetInfo)) {
                                    break;
1181
                                }
1182 1183
                                sItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
                                sAppWidgets.add(appWidgetInfo);
1184
                            }
1185
                            break;
1186
                        }
1187 1188
                    } catch (Exception e) {
                        Log.w(TAG, "Desktop items loading interrupted:", e);
1189 1190
                    }
                }
1191 1192 1193
            } finally {
                c.close();
            }
1194

1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208
            if (itemsToRemove.size() > 0) {
                ContentProviderClient client = contentResolver.acquireContentProviderClient(
                                LauncherSettings.Favorites.CONTENT_URI);
                // Remove dead items
                for (long id : itemsToRemove) {
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "Removed id = " + id);
                    }
                    // Don't notify content observers
                    try {
                        client.delete(LauncherSettings.Favorites.getContentUri(id, false),
                                null, null);
                    } catch (RemoteException e) {
                        Log.w(TAG, "Could not remove id = " + id);
1209 1210
                    }
                }
1211
            }
1212

1213 1214 1215
            if (DEBUG_LOADERS) {
                Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
                Log.d(TAG, "workspace layout: ");
1216
                for (int y = 0; y < mCellCountY; y++) {
1217 1218 1219 1220 1221
                    String line = "";
                    for (int s = 0; s < Launcher.SCREEN_COUNT; s++) {
                        if (s > 0) {
                            line += " | ";
                        }
1222
                        for (int x = 0; x < mCellCountX; x++) {
1223
                            line += ((occupied[s][x][y] != null) ? "#" : ".");
1224 1225
                        }
                    }
1226
                    Log.d(TAG, "[ " + line + " ]");
1227
                }
1228
            }
1229
        }
1230

1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244
        /**
         * Read everything out of our database.
         */
        private void bindWorkspace() {
            final long t = SystemClock.uptimeMillis();

            // Don't use these two variables in any of the callback runnables.
            // Otherwise we hold a reference to them.
            final Callbacks oldCallbacks = mCallbacks.get();
            if (oldCallbacks == null) {
                // This launcher has exited and nobody bothered to tell us.  Just bail.
                Log.w(TAG, "LoaderTask running with no launcher");
                return;
            }
1245

1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278
            // Get the list of workspace items to load and unbind the existing ShortcutInfos
            // before we call startBinding() below.
            final int currentScreen = oldCallbacks.getCurrentWorkspaceScreen();
            final ArrayList<ItemInfo> tmpWorkspaceItems = unbindWorkspaceItemsOnMainThread();
            // Order the items for loading as follows: current workspace, hotseat, everything else
            Collections.sort(tmpWorkspaceItems, new Comparator<ItemInfo>() {
                @Override
                public int compare(ItemInfo lhs, ItemInfo rhs) {
                    int cellCountX = LauncherModel.getCellCountX();
                    int cellCountY = LauncherModel.getCellCountY();
                    int screenOffset = cellCountX * cellCountY;
                    int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat
                    long lr = (lhs.container * containerOffset + lhs.screen * screenOffset +
                            lhs.cellY * cellCountX + lhs.cellX);
                    long rr = (rhs.container * containerOffset + rhs.screen * screenOffset +
                            rhs.cellY * cellCountX + rhs.cellX);
                    return (int) (lr - rr);
                }
            });
            // Precondition: the items are ordered by page, screen
            final ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
            for (ItemInfo ii : tmpWorkspaceItems) {
                // Prepend the current items, hotseat items, append everything else
                if (ii.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
                        ii.screen == currentScreen) {
                    workspaceItems.add(0, ii);
                } else if (ii.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
                    workspaceItems.add(0, ii);
                } else {
                    workspaceItems.add(ii);
                }
            }

1279 1280 1281 1282 1283 1284 1285 1286 1287
            // Tell the workspace that we're about to start firing items at it
            mHandler.post(new Runnable() {
                public void run() {
                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        callbacks.startBinding();
                    }
                }
            });
1288

1289
            // Add the items to the workspace.
1290
            int N = workspaceItems.size();
1291 1292 1293
            for (int i=0; i<N; i+=ITEMS_CHUNK) {
                final int start = i;
                final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
1294 1295
                mHandler.post(new Runnable() {
                    public void run() {
1296
                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1297
                        if (callbacks != null) {
1298
                            callbacks.bindItems(workspaceItems, start, start+chunkSize);
1299 1300 1301
                        }
                    }
                });
1302
            }
1303 1304
            // Ensure that we don't use the same folders data structure on the main thread
            final HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>(sFolders);
1305 1306 1307 1308
            mHandler.post(new Runnable() {
                public void run() {
                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
1309
                        callbacks.bindFolders(folders);
1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325
                    }
                }
            });
            // Wait until the queue goes empty.
            mHandler.post(new Runnable() {
                public void run() {
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "Going to start binding widgets soon.");
                    }
                }
            });
            // Bind the widgets, one at a time.
            // WARNING: this is calling into the workspace from the background thread,
            // but since getCurrentScreen() just returns the int, we should be okay.  This
            // is just a hint for the order, and if it's wrong, we'll be okay.
            // TODO: instead, we should have that push the current screen into here.
1326
            N = sAppWidgets.size();
1327 1328
            // once for the current screen
            for (int i=0; i<N; i++) {
1329
                final LauncherAppWidgetInfo widget = sAppWidgets.get(i);
1330
                if (widget.screen == currentScreen) {
1331 1332
                    mHandler.post(new Runnable() {
                        public void run() {
1333
                            Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1334
                            if (callbacks != null) {
1335
                                callbacks.bindAppWidget(widget);
1336 1337 1338 1339
                            }
                        }
                    });
                }
1340 1341 1342
            }
            // once for the other screens
            for (int i=0; i<N; i++) {
1343
                final LauncherAppWidgetInfo widget = sAppWidgets.get(i);
1344 1345 1346 1347 1348 1349
                if (widget.screen != currentScreen) {
                    mHandler.post(new Runnable() {
                        public void run() {
                            Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                            if (callbacks != null) {
                                callbacks.bindAppWidget(widget);
1350
                            }
1351 1352 1353 1354 1355 1356 1357 1358 1359 1360
                        }
                    });
                }
            }
            // Tell the workspace that we're done.
            mHandler.post(new Runnable() {
                public void run() {
                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        callbacks.finishBindingItems();
1361 1362
                    }
                }
1363 1364 1365 1366 1367 1368 1369
            });
            // If we're profiling, this is the last thing in the queue.
            mHandler.post(new Runnable() {
                public void run() {
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "bound workspace in "
                            + (SystemClock.uptimeMillis()-t) + "ms");
1370 1371
                    }
                }
1372 1373 1374 1375 1376 1377 1378 1379 1380
            });
        }

        private void loadAndBindAllApps() {
            if (DEBUG_LOADERS) {
                Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
            }
            if (!mAllAppsLoaded) {
                loadAllAppsByBatch();
1381 1382 1383 1384 1385
                synchronized (LoaderTask.this) {
                    if (mStopped) {
                        return;
                    }
                    mAllAppsLoaded = true;
1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400
                }
            } else {
                onlyBindAllApps();
            }
        }

        private void onlyBindAllApps() {
            final Callbacks oldCallbacks = mCallbacks.get();
            if (oldCallbacks == null) {
                // This launcher has exited and nobody bothered to tell us.  Just bail.
                Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
                return;
            }

            // shallow copy
1401
            @SuppressWarnings("unchecked")
1402
            final ArrayList<ApplicationInfo> list
1403
                    = (ArrayList<ApplicationInfo>) mAllAppsList.data.clone();
1404 1405 1406 1407 1408 1409
            mHandler.post(new Runnable() {
                public void run() {
                    final long t = SystemClock.uptimeMillis();
                    final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        callbacks.bindAllApplications(list);
1410
                    }
1411 1412
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "bound all " + list.size() + " apps from cache in "
1413
                                + (SystemClock.uptimeMillis()-t) + "ms");
1414
                    }
1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429
                }
            });

        }

        private void loadAllAppsByBatch() {
            final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;

            // Don't use these two variables in any of the callback runnables.
            // Otherwise we hold a reference to them.
            final Callbacks oldCallbacks = mCallbacks.get();
            if (oldCallbacks == null) {
                // This launcher has exited and nobody bothered to tell us.  Just bail.
                Log.w(TAG, "LoaderTask running with no launcher (loadAllAppsByBatch)");
                return;
1430
            }
1431

1432 1433
            final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
            mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
1434

1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452
            final PackageManager packageManager = mContext.getPackageManager();
            List<ResolveInfo> apps = null;

            int N = Integer.MAX_VALUE;

            int startIndex;
            int i=0;
            int batchSize = -1;
            while (i < N && !mStopped) {
                if (i == 0) {
                    mAllAppsList.clear();
                    final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
                    apps = packageManager.queryIntentActivities(mainIntent, 0);
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "queryIntentActivities took "
                                + (SystemClock.uptimeMillis()-qiaTime) + "ms");
                    }
                    if (apps == null) {
1453 1454
                        return;
                    }
1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470
                    N = apps.size();
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "queryIntentActivities got " + N + " apps");
                    }
                    if (N == 0) {
                        // There are no apps?!?
                        return;
                    }
                    if (mBatchSize == 0) {
                        batchSize = N;
                    } else {
                        batchSize = mBatchSize;
                    }

                    final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
                    Collections.sort(apps,
1471
                            new LauncherModel.ShortcutNameComparator(packageManager, mLabelCache));
1472 1473 1474 1475
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "sort took "
                                + (SystemClock.uptimeMillis()-sortTime) + "ms");
                    }
1476 1477
                }

1478 1479 1480 1481 1482
                final long t2 = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;

                startIndex = i;
                for (int j=0; i<N && j<batchSize; j++) {
                    // This builds the icon bitmaps.
1483
                    mAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i),
Michael Jurka's avatar
Michael Jurka committed
1484
                            mIconCache, mLabelCache));
1485
                    i++;
1486 1487
                }

1488 1489 1490 1491 1492
                final boolean first = i <= batchSize;
                final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                final ArrayList<ApplicationInfo> added = mAllAppsList.added;
                mAllAppsList.added = new ArrayList<ApplicationInfo>();

1493 1494 1495 1496
                mHandler.post(new Runnable() {
                    public void run() {
                        final long t = SystemClock.uptimeMillis();
                        if (callbacks != null) {
1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507
                            if (first) {
                                callbacks.bindAllApplications(added);
                            } else {
                                callbacks.bindAppsAdded(added);
                            }
                            if (DEBUG_LOADERS) {
                                Log.d(TAG, "bound " + added.size() + " apps in "
                                    + (SystemClock.uptimeMillis() - t) + "ms");
                            }
                        } else {
                            Log.i(TAG, "not binding apps: no Launcher activity");
1508 1509 1510 1511
                        }
                    }
                });

1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524
                if (DEBUG_LOADERS) {
                    Log.d(TAG, "batch of " + (i-startIndex) + " icons processed in "
                            + (SystemClock.uptimeMillis()-t2) + "ms");
                }

                if (mAllAppsLoadDelay > 0 && i < N) {
                    try {
                        if (DEBUG_LOADERS) {
                            Log.d(TAG, "sleeping for " + mAllAppsLoadDelay + "ms");
                        }
                        Thread.sleep(mAllAppsLoadDelay);
                    } catch (InterruptedException exc) { }
                }
1525 1526
            }

1527 1528 1529 1530 1531 1532
            if (DEBUG_LOADERS) {
                Log.d(TAG, "cached all " + N + " apps in "
                        + (SystemClock.uptimeMillis()-t) + "ms"
                        + (mAllAppsLoadDelay > 0 ? " (including delay)" : ""));
            }
        }
1533

1534 1535 1536 1537 1538 1539
        public void dumpState() {
            Log.d(TAG, "mLoaderTask.mContext=" + mContext);
            Log.d(TAG, "mLoaderTask.mWaitThread=" + mWaitThread);
            Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching);
            Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
            Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
1540
            Log.d(TAG, "mItems size=" + sWorkspaceItems.size());
1541 1542
        }
    }
1543

1544
    void enqueuePackageUpdated(PackageUpdatedTask task) {
1545
        sWorker.post(task);
1546
    }
1547

1548 1549 1550
    private class PackageUpdatedTask implements Runnable {
        int mOp;
        String[] mPackages;
1551

1552 1553 1554 1555 1556
        public static final int OP_NONE = 0;
        public static final int OP_ADD = 1;
        public static final int OP_UPDATE = 2;
        public static final int OP_REMOVE = 3; // uninstlled
        public static final int OP_UNAVAILABLE = 4; // external media unmounted
1557

1558

1559 1560 1561 1562
        public PackageUpdatedTask(int op, String[] packages) {
            mOp = op;
            mPackages = packages;
        }
1563

1564 1565
        public void run() {
            final Context context = mApp;
1566

1567 1568 1569 1570 1571 1572 1573
            final String[] packages = mPackages;
            final int N = packages.length;
            switch (mOp) {
                case OP_ADD:
                    for (int i=0; i<N; i++) {
                        if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
                        mAllAppsList.addPackage(context, packages[i]);
1574
                    }
1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589
                    break;
                case OP_UPDATE:
                    for (int i=0; i<N; i++) {
                        if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
                        mAllAppsList.updatePackage(context, packages[i]);
                    }
                    break;
                case OP_REMOVE:
                case OP_UNAVAILABLE:
                    for (int i=0; i<N; i++) {
                        if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
                        mAllAppsList.removePackage(packages[i]);
                    }
                    break;
            }
1590

1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603
            ArrayList<ApplicationInfo> added = null;
            ArrayList<ApplicationInfo> removed = null;
            ArrayList<ApplicationInfo> modified = null;

            if (mAllAppsList.added.size() > 0) {
                added = mAllAppsList.added;
                mAllAppsList.added = new ArrayList<ApplicationInfo>();
            }
            if (mAllAppsList.removed.size() > 0) {
                removed = mAllAppsList.removed;
                mAllAppsList.removed = new ArrayList<ApplicationInfo>();
                for (ApplicationInfo info: removed) {
                    mIconCache.remove(info.intent.getComponent());
1604 1605
                }
            }
1606 1607 1608 1609
            if (mAllAppsList.modified.size() > 0) {
                modified = mAllAppsList.modified;
                mAllAppsList.modified = new ArrayList<ApplicationInfo>();
            }
1610

1611 1612 1613 1614
            final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
            if (callbacks == null) {
                Log.w(TAG, "Nobody to tell about the new app.  Launcher is probably loading.");
                return;
1615 1616
            }

1617 1618 1619 1620
            if (added != null) {
                final ArrayList<ApplicationInfo> addedFinal = added;
                mHandler.post(new Runnable() {
                    public void run() {
1621 1622
                        Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
                        if (callbacks == cb && cb != null) {
1623 1624 1625 1626 1627 1628 1629 1630 1631
                            callbacks.bindAppsAdded(addedFinal);
                        }
                    }
                });
            }
            if (modified != null) {
                final ArrayList<ApplicationInfo> modifiedFinal = modified;
                mHandler.post(new Runnable() {
                    public void run() {
1632 1633
                        Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
                        if (callbacks == cb && cb != null) {
1634 1635 1636 1637 1638 1639 1640 1641 1642 1643
                            callbacks.bindAppsUpdated(modifiedFinal);
                        }
                    }
                });
            }
            if (removed != null) {
                final boolean permanent = mOp != OP_UNAVAILABLE;
                final ArrayList<ApplicationInfo> removedFinal = removed;
                mHandler.post(new Runnable() {
                    public void run() {
1644 1645
                        Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
                        if (callbacks == cb && cb != null) {
1646 1647 1648 1649
                            callbacks.bindAppsRemoved(removedFinal, permanent);
                        }
                    }
                });
1650
            }
1651 1652 1653 1654

            mHandler.post(new Runnable() {
                @Override
                public void run() {
1655 1656
                    Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
                    if (callbacks == cb && cb != null) {
1657 1658 1659 1660
                        callbacks.bindPackagesUpdated();
                    }
                }
            });
1661 1662 1663
        }
    }

1664
    /**
1665 1666
     * This is called from the code that adds shortcuts from the intent receiver.  This
     * doesn't have a Cursor, but
1667
     */
1668
    public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context) {
1669
        return getShortcutInfo(manager, intent, context, null, -1, -1, null);
1670 1671 1672 1673 1674 1675 1676 1677
    }

    /**
     * Make an ShortcutInfo object for a shortcut that is an application.
     *
     * If c is not null, then it will be used to fill in missing data like the title and icon.
     */
    public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context,
1678
            Cursor c, int iconIndex, int titleIndex, HashMap<Object, CharSequence> labelCache) {
1679
        Bitmap icon = null;
Michael Jurka's avatar
Michael Jurka committed
1680
        final ShortcutInfo info = new ShortcutInfo();
1681

1682 1683
        ComponentName componentName = intent.getComponent();
        if (componentName == null) {
1684
            return null;
1685 1686
        }

1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697
        try {
            PackageInfo pi = manager.getPackageInfo(componentName.getPackageName(), 0);
            if (!pi.applicationInfo.enabled) {
                // If we return null here, the corresponding item will be removed from the launcher
                // db and will not appear in the workspace.
                return null;
            }
        } catch (NameNotFoundException e) {
            Log.d(TAG, "getPackInfo failed for package " + componentName.getPackageName());
        }

1698 1699 1700
        // TODO: See if the PackageManager knows about this case.  If it doesn't
        // then return null & delete this.

1701 1702 1703 1704
        // the resource -- This may implicitly give us back the fallback icon,
        // but don't worry about that.  All we're doing with usingFallbackIcon is
        // to avoid saving lots of copies of that in the database, and most apps
        // have icons anyway.
1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724

        // Attempt to use queryIntentActivities to get the ResolveInfo (with IntentFilter info) and
        // if that fails, or is ambiguious, fallback to the standard way of getting the resolve info
        // via resolveActivity().
        ResolveInfo resolveInfo = null;
        ComponentName oldComponent = intent.getComponent();
        Intent newIntent = new Intent(intent.getAction(), null);
        newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        newIntent.setPackage(oldComponent.getPackageName());
        List<ResolveInfo> infos = manager.queryIntentActivities(newIntent, 0);
        for (ResolveInfo i : infos) {
            ComponentName cn = new ComponentName(i.activityInfo.packageName,
                    i.activityInfo.name);
            if (cn.equals(oldComponent)) {
                resolveInfo = i;
            }
        }
        if (resolveInfo == null) {
            resolveInfo = manager.resolveActivity(intent, 0);
        }
1725
        if (resolveInfo != null) {
1726
            icon = mIconCache.getIcon(componentName, resolveInfo, labelCache);
1727 1728 1729 1730
        }
        // the db
        if (icon == null) {
            if (c != null) {
1731
                icon = getIconFromCursor(c, iconIndex, context);
1732
            }
1733
        }
1734 1735 1736 1737 1738 1739 1740 1741 1742
        // the fallback icon
        if (icon == null) {
            icon = getFallbackIcon();
            info.usingFallbackIcon = true;
        }
        info.setIcon(icon);

        // from the resource
        if (resolveInfo != null) {
1743 1744 1745
            ComponentName key = LauncherModel.getComponentNameFromResolveInfo(resolveInfo);
            if (labelCache != null && labelCache.containsKey(key)) {
                info.title = labelCache.get(key);
1746 1747 1748
            } else {
                info.title = resolveInfo.activityInfo.loadLabel(manager);
                if (labelCache != null) {
1749
                    labelCache.put(key, info.title);
1750 1751
                }
            }
1752 1753 1754 1755 1756 1757 1758 1759
        }
        // from the db
        if (info.title == null) {
            if (c != null) {
                info.title =  c.getString(titleIndex);
            }
        }
        // fall back to the class name of the activity
1760
        if (info.title == null) {
1761
            info.title = componentName.getClassName();
1762 1763 1764 1765
        }
        info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
        return info;
    }
1766

1767
    /**
1768
     * Make an ShortcutInfo object for a shortcut that isn't an application.
1769
     */
1770
    private ShortcutInfo getShortcutInfo(Cursor c, Context context,
1771 1772
            int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex,
            int titleIndex) {
1773

1774
        Bitmap icon = null;
Michael Jurka's avatar
Michael Jurka committed
1775
        final ShortcutInfo info = new ShortcutInfo();
1776
        info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
1777

1778 1779
        // TODO: If there's an explicit component and we can't install that, delete it.

1780 1781
        info.title = c.getString(titleIndex);

1782 1783 1784 1785 1786 1787
        int iconType = c.getInt(iconTypeIndex);
        switch (iconType) {
        case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
            String packageName = c.getString(iconPackageIndex);
            String resourceName = c.getString(iconResourceIndex);
            PackageManager packageManager = context.getPackageManager();
1788 1789
            info.customIcon = false;
            // the resource
1790 1791
            try {
                Resources resources = packageManager.getResourcesForApplication(packageName);
1792 1793
                if (resources != null) {
                    final int id = resources.getIdentifier(resourceName, null, null);
Michael Jurka's avatar
Michael Jurka committed
1794 1795
                    icon = Utilities.createIconBitmap(
                            mIconCache.getFullResIcon(resources, id), context);
1796
                }
1797
            } catch (Exception e) {
1798 1799 1800 1801
                // drop this.  we have other places to look for icons
            }
            // the db
            if (icon == null) {
1802
                icon = getIconFromCursor(c, iconIndex, context);
1803 1804 1805 1806 1807
            }
            // the fallback icon
            if (icon == null) {
                icon = getFallbackIcon();
                info.usingFallbackIcon = true;
1808
            }
1809 1810
            break;
        case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
1811
            icon = getIconFromCursor(c, iconIndex, context);
1812 1813 1814 1815 1816 1817
            if (icon == null) {
                icon = getFallbackIcon();
                info.customIcon = false;
                info.usingFallbackIcon = true;
            } else {
                info.customIcon = true;
1818 1819 1820
            }
            break;
        default:
1821
            icon = getFallbackIcon();
1822
            info.usingFallbackIcon = true;
1823 1824
            info.customIcon = false;
            break;
1825
        }
1826
        info.setIcon(icon);
1827
        return info;
1828
    }
1829

1830
    Bitmap getIconFromCursor(Cursor c, int iconIndex, Context context) {
1831 1832 1833
        @SuppressWarnings("all") // suppress dead code warning
        final boolean debug = false;
        if (debug) {
1834 1835 1836 1837 1838
            Log.d(TAG, "getIconFromCursor app="
                    + c.getString(c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE)));
        }
        byte[] data = c.getBlob(iconIndex);
        try {
1839 1840
            return Utilities.createIconBitmap(
                    BitmapFactory.decodeByteArray(data, 0, data.length), context);
1841 1842 1843 1844 1845
        } catch (Exception e) {
            return null;
        }
    }

1846 1847
    ShortcutInfo addShortcut(Context context, Intent data, long container, int screen,
            int cellX, int cellY, boolean notify) {
1848
        final ShortcutInfo info = infoFromShortcutIntent(context, data, null);
1849 1850 1851
        if (info == null) {
            return null;
        }
1852
        addItemToDatabase(context, info, container, screen, cellX, cellY, notify);
1853 1854 1855 1856

        return info;
    }

1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869
    /**
     * Attempts to find an AppWidgetProviderInfo that matches the given component.
     */
    AppWidgetProviderInfo findAppWidgetProviderInfoWithComponent(Context context,
            ComponentName component) {
        List<AppWidgetProviderInfo> widgets =
            AppWidgetManager.getInstance(context).getInstalledProviders();
        for (AppWidgetProviderInfo info : widgets) {
            if (info.provider.equals(component)) {
                return info;
            }
        }
        return null;
1870 1871
    }

1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909
    /**
     * Returns a list of all the widgets that can handle configuration with a particular mimeType.
     */
    List<WidgetMimeTypeHandlerData> resolveWidgetsForMimeType(Context context, String mimeType) {
        final PackageManager packageManager = context.getPackageManager();
        final List<WidgetMimeTypeHandlerData> supportedConfigurationActivities =
            new ArrayList<WidgetMimeTypeHandlerData>();

        final Intent supportsIntent =
            new Intent(InstallWidgetReceiver.ACTION_SUPPORTS_CLIPDATA_MIMETYPE);
        supportsIntent.setType(mimeType);

        // Create a set of widget configuration components that we can test against
        final List<AppWidgetProviderInfo> widgets =
            AppWidgetManager.getInstance(context).getInstalledProviders();
        final HashMap<ComponentName, AppWidgetProviderInfo> configurationComponentToWidget =
            new HashMap<ComponentName, AppWidgetProviderInfo>();
        for (AppWidgetProviderInfo info : widgets) {
            configurationComponentToWidget.put(info.configure, info);
        }

        // Run through each of the intents that can handle this type of clip data, and cross
        // reference them with the components that are actual configuration components
        final List<ResolveInfo> activities = packageManager.queryIntentActivities(supportsIntent,
                PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo info : activities) {
            final ActivityInfo activityInfo = info.activityInfo;
            final ComponentName infoComponent = new ComponentName(activityInfo.packageName,
                    activityInfo.name);
            if (configurationComponentToWidget.containsKey(infoComponent)) {
                supportedConfigurationActivities.add(
                        new InstallWidgetReceiver.WidgetMimeTypeHandlerData(info,
                                configurationComponentToWidget.get(infoComponent)));
            }
        }
        return supportedConfigurationActivities;
    }

1910
    ShortcutInfo infoFromShortcutIntent(Context context, Intent data, Bitmap fallbackIcon) {
1911 1912 1913 1914
        Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
        String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
        Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);

1915 1916 1917 1918 1919 1920
        if (intent == null) {
            // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
            Log.e(TAG, "Can't construct ShorcutInfo with null intent");
            return null;
        }

1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936
        Bitmap icon = null;
        boolean customIcon = false;
        ShortcutIconResource iconResource = null;

        if (bitmap != null && bitmap instanceof Bitmap) {
            icon = Utilities.createIconBitmap(new FastBitmapDrawable((Bitmap)bitmap), context);
            customIcon = true;
        } else {
            Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
            if (extra != null && extra instanceof ShortcutIconResource) {
                try {
                    iconResource = (ShortcutIconResource) extra;
                    final PackageManager packageManager = context.getPackageManager();
                    Resources resources = packageManager.getResourcesForApplication(
                            iconResource.packageName);
                    final int id = resources.getIdentifier(iconResource.resourceName, null, null);
Michael Jurka's avatar
Michael Jurka committed
1937 1938
                    icon = Utilities.createIconBitmap(
                            mIconCache.getFullResIcon(resources, id), context);
1939 1940 1941 1942 1943 1944
                } catch (Exception e) {
                    Log.w(TAG, "Could not load shortcut icon: " + extra);
                }
            }
        }

Michael Jurka's avatar
Michael Jurka committed
1945
        final ShortcutInfo info = new ShortcutInfo();
1946

1947
        if (icon == null) {
1948 1949 1950 1951 1952 1953
            if (fallbackIcon != null) {
                icon = fallbackIcon;
            } else {
                icon = getFallbackIcon();
                info.usingFallbackIcon = true;
            }
1954 1955
        }
        info.setIcon(icon);
1956

1957 1958 1959 1960 1961 1962 1963 1964
        info.title = name;
        info.intent = intent;
        info.customIcon = customIcon;
        info.iconResource = iconResource;

        return info;
    }

1965 1966
    boolean queueIconToBeChecked(HashMap<Object, byte[]> cache, ShortcutInfo info, Cursor c,
            int iconIndex) {
1967 1968
        // If apps can't be on SD, don't even bother.
        if (!mAppsCanBeOnExternalStorage) {
1969
            return false;
1970
        }
1971 1972 1973 1974 1975 1976
        // If this icon doesn't have a custom icon, check to see
        // what's stored in the DB, and if it doesn't match what
        // we're going to show, store what we are going to show back
        // into the DB.  We do this so when we're loading, if the
        // package manager can't find an icon (for example because
        // the app is on SD) then we can use that instead.
1977
        if (!info.customIcon && !info.usingFallbackIcon) {
1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990
            cache.put(info, c.getBlob(iconIndex));
            return true;
        }
        return false;
    }
    void updateSavedIcon(Context context, ShortcutInfo info, byte[] data) {
        boolean needSave = false;
        try {
            if (data != null) {
                Bitmap saved = BitmapFactory.decodeByteArray(data, 0, data.length);
                Bitmap loaded = info.getIcon(mIconCache);
                needSave = !saved.sameAs(loaded);
            } else {
1991 1992
                needSave = true;
            }
1993 1994 1995 1996 1997 1998 1999 2000
        } catch (Exception e) {
            needSave = true;
        }
        if (needSave) {
            Log.d(TAG, "going to save icon bitmap for info=" + info);
            // This is slower than is ideal, but this only happens once
            // or when the app is updated with a new icon.
            updateItemInDatabase(context, info);
2001 2002 2003
        }
    }

2004
    /**
2005
     * Return an existing FolderInfo object if we have encountered this ID previously,
2006
     * or make a new one.
2007
     */
2008
    private static FolderInfo findOrMakeFolder(HashMap<Long, FolderInfo> folders, long id) {
2009 2010
        // See if a placeholder was created for us already
        FolderInfo folderInfo = folders.get(id);
2011
        if (folderInfo == null) {
2012
            // No placeholder -- create a new instance
Michael Jurka's avatar
Michael Jurka committed
2013
            folderInfo = new FolderInfo();
2014
            folders.put(id, folderInfo);
2015
        }
2016
        return folderInfo;
2017 2018
    }

2019
    private static final Collator sCollator = Collator.getInstance();
2020
    public static final Comparator<ApplicationInfo> APP_NAME_COMPARATOR
2021 2022
            = new Comparator<ApplicationInfo>() {
        public final int compare(ApplicationInfo a, ApplicationInfo b) {
2023 2024 2025 2026 2027
            int result = sCollator.compare(a.title.toString(), b.title.toString());
            if (result == 0) {
                result = a.componentName.compareTo(b.componentName);
            }
            return result;
2028
        }
2029
    };
2030 2031 2032 2033 2034 2035 2036 2037
    public static final Comparator<ApplicationInfo> APP_INSTALL_TIME_COMPARATOR
            = new Comparator<ApplicationInfo>() {
        public final int compare(ApplicationInfo a, ApplicationInfo b) {
            if (a.firstInstallTime < b.firstInstallTime) return 1;
            if (a.firstInstallTime > b.firstInstallTime) return -1;
            return 0;
        }
    };
2038 2039 2040 2041 2042 2043
    public static final Comparator<AppWidgetProviderInfo> WIDGET_NAME_COMPARATOR
            = new Comparator<AppWidgetProviderInfo>() {
        public final int compare(AppWidgetProviderInfo a, AppWidgetProviderInfo b) {
            return sCollator.compare(a.label.toString(), b.label.toString());
        }
    };
2044 2045 2046 2047 2048 2049 2050
    static ComponentName getComponentNameFromResolveInfo(ResolveInfo info) {
        if (info.activityInfo != null) {
            return new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
        } else {
            return new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name);
        }
    }
2051 2052
    public static class ShortcutNameComparator implements Comparator<ResolveInfo> {
        private PackageManager mPackageManager;
2053
        private HashMap<Object, CharSequence> mLabelCache;
2054 2055
        ShortcutNameComparator(PackageManager pm) {
            mPackageManager = pm;
2056 2057 2058 2059 2060
            mLabelCache = new HashMap<Object, CharSequence>();
        }
        ShortcutNameComparator(PackageManager pm, HashMap<Object, CharSequence> labelCache) {
            mPackageManager = pm;
            mLabelCache = labelCache;
2061 2062
        }
        public final int compare(ResolveInfo a, ResolveInfo b) {
2063
            CharSequence labelA, labelB;
2064 2065 2066 2067
            ComponentName keyA = LauncherModel.getComponentNameFromResolveInfo(a);
            ComponentName keyB = LauncherModel.getComponentNameFromResolveInfo(b);
            if (mLabelCache.containsKey(keyA)) {
                labelA = mLabelCache.get(keyA);
2068 2069 2070
            } else {
                labelA = a.loadLabel(mPackageManager).toString();

2071
                mLabelCache.put(keyA, labelA);
2072
            }
2073 2074
            if (mLabelCache.containsKey(keyB)) {
                labelB = mLabelCache.get(keyB);
2075 2076 2077
            } else {
                labelB = b.loadLabel(mPackageManager).toString();

2078
                mLabelCache.put(keyB, labelB);
2079
            }
2080 2081 2082
            return sCollator.compare(labelA, labelB);
        }
    };
2083 2084 2085 2086 2087 2088 2089 2090 2091
    public static class WidgetAndShortcutNameComparator implements Comparator<Object> {
        private PackageManager mPackageManager;
        private HashMap<Object, String> mLabelCache;
        WidgetAndShortcutNameComparator(PackageManager pm) {
            mPackageManager = pm;
            mLabelCache = new HashMap<Object, String>();
        }
        public final int compare(Object a, Object b) {
            String labelA, labelB;
2092 2093 2094 2095
            if (mLabelCache.containsKey(a)) {
                labelA = mLabelCache.get(a);
            } else {
                labelA = (a instanceof AppWidgetProviderInfo) ?
2096 2097
                    ((AppWidgetProviderInfo) a).label :
                    ((ResolveInfo) a).loadLabel(mPackageManager).toString();
2098 2099 2100 2101 2102 2103
                mLabelCache.put(a, labelA);
            }
            if (mLabelCache.containsKey(b)) {
                labelB = mLabelCache.get(b);
            } else {
                labelB = (b instanceof AppWidgetProviderInfo) ?
2104 2105
                    ((AppWidgetProviderInfo) b).label :
                    ((ResolveInfo) b).loadLabel(mPackageManager).toString();
2106 2107
                mLabelCache.put(b, labelB);
            }
2108 2109 2110
            return sCollator.compare(labelA, labelB);
        }
    };
2111 2112 2113 2114 2115 2116 2117

    public void dumpState() {
        Log.d(TAG, "mCallbacks=" + mCallbacks);
        ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mAllAppsList.data);
        ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mAllAppsList.added);
        ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mAllAppsList.removed);
        ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mAllAppsList.modified);
2118 2119 2120 2121 2122
        if (mLoaderTask != null) {
            mLoaderTask.dumpState();
        } else {
            Log.d(TAG, "mLoaderTask=null");
        }
2123
    }
2124
}