LauncherModel.java 92.7 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<String> packageNames, 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

211 212 213 214 215 216
    static void checkItemInfo(final ItemInfo item) {
        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
        final long itemId = item.id;
        Runnable r = new Runnable() {
                public void run() {
                    ItemInfo modelItem = sItemsIdMap.get(itemId);
217
                    if (modelItem != null && item != modelItem) {
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
                        // 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 checkItemInfo doesn't match original";
                        RuntimeException e = new RuntimeException(msg);
                        e.setStackTrace(stackTrace);
                        throw e;
                    }
                }
            };

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

    }

Michael Jurka's avatar
Michael Jurka committed
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
    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);
        }
    }
278

279 280 281
    /**
     * Move an item in the DB to a new <container, screen, cellX, cellY>
     */
282 283
    static void moveItemInDatabase(Context context, final ItemInfo item, final long container,
            final int screen, final int cellX, final int cellY) {
284 285 286
        item.container = container;
        item.cellX = cellX;
        item.cellY = cellY;
Michael Jurka's avatar
Michael Jurka committed
287

288 289 290 291 292 293 294 295
        // 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;
        }
296

297 298
        final ContentValues values = new ContentValues();
        values.put(LauncherSettings.Favorites.CONTAINER, item.container);
299 300
        values.put(LauncherSettings.Favorites.CELLX, item.cellX);
        values.put(LauncherSettings.Favorites.CELLY, item.cellY);
301
        values.put(LauncherSettings.Favorites.SCREEN, item.screen);
302

Michael Jurka's avatar
Michael Jurka committed
303
        updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
304 305
    }

306
    /**
307
     * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
308
     */
309 310 311
    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;
312 313
        item.cellX = cellX;
        item.cellY = cellY;
314 315 316 317 318 319 320 321 322 323 324
        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;
        }
325 326 327

        final ContentValues values = new ContentValues();
        values.put(LauncherSettings.Favorites.CONTAINER, item.container);
328 329 330 331 332
        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);
333

334
        updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
335
    }
Michael Jurka's avatar
Michael Jurka committed
336 337 338 339 340 341 342 343 344

    /**
     * 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");
345 346
    }

347
    /**
348 349
     * Returns true if the shortcuts already exists in the database.
     * we identify a shortcut by its title and intent.
350
     */
351 352 353 354 355 356 357 358 359 360 361 362
    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;
363 364
    }

365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
    /**
     * 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
387
                ItemInfo item = new ItemInfo();
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406
                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;
    }

407
    /**
408
     * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList.
409
     */
410 411 412 413 414
    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),
415
                        String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)}, null);
416

417 418 419 420 421 422 423 424
        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);
425

426 427
                FolderInfo folderInfo = null;
                switch (c.getInt(itemTypeIndex)) {
428 429
                    case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                        folderInfo = findOrMakeFolder(folderList, id);
430 431
                        break;
                }
432

433 434 435 436
                folderInfo.title = c.getString(titleIndex);
                folderInfo.id = id;
                folderInfo.container = c.getInt(containerIndex);
                folderInfo.screen = c.getInt(screenIndex);
437 438
                folderInfo.cellX = c.getInt(cellXIndex);
                folderInfo.cellY = c.getInt(cellYIndex);
439

440 441 442 443
                return folderInfo;
            }
        } finally {
            c.close();
444
        }
445

446 447
        return null;
    }
448

449 450 451 452
    /**
     * 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.
     */
453 454
    static void addItemToDatabase(Context context, final ItemInfo item, final long container,
            final int screen, final int cellX, final int cellY, final boolean notify) {
455 456 457
        item.container = container;
        item.cellX = cellX;
        item.cellY = cellY;
458 459 460 461 462 463 464 465
        // 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;
        }
466

467 468 469
        final ContentValues values = new ContentValues();
        final ContentResolver cr = context.getContentResolver();
        item.onAddToDatabase(values);
470

471
        LauncherApplication app = (LauncherApplication) context.getApplicationContext();
472 473
        item.id = app.getLauncherProvider().generateNewId();
        values.put(LauncherSettings.Favorites._ID, item.id);
474
        item.updateValuesWithCoordinates(values, item.cellX, item.cellY);
475

Michael Jurka's avatar
Michael Jurka committed
476
        Runnable r = new Runnable() {
477 478 479
            public void run() {
                cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI :
                        LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values);
480

481 482 483 484 485
                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());
                }
486 487 488 489
                sItemsIdMap.put(item.id, item);
                switch (item.itemType) {
                    case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                        sFolders.put(item.id, (FolderInfo) item);
490
                        // Fall through
491 492
                    case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                    case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
493 494
                        if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
                                item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
495
                            sWorkspaceItems.add(item);
496 497 498 499 500 501 502
                        }
                        break;
                    case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
                        sAppWidgets.add((LauncherAppWidgetInfo) item);
                        break;
                }
            }
Michael Jurka's avatar
Michael Jurka committed
503 504 505 506 507 508 509
        };

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

512 513 514
    /**
     * Creates a new unique child id, for a given cell span across all layouts.
     */
515
    static int getCellLayoutChildId(
516 517
            long container, int screen, int localCellX, int localCellY, int spanX, int spanY) {
        return (((int) container & 0xFF) << 24)
518
                | (screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF);
519 520
    }

521 522
    static int getCellCountX() {
        return mCellCountX;
523 524
    }

525 526
    static int getCellCountY() {
        return mCellCountY;
527 528 529 530 531 532 533
    }

    /**
     * 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) {
534 535
        mCellCountX = shortAxisCellCount;
        mCellCountY = longAxisCellCount;
536 537
    }

538
    /**
Michael Jurka's avatar
Michael Jurka committed
539 540 541
     * Removes the specified item from the database
     * @param context
     * @param item
542
     */
Michael Jurka's avatar
Michael Jurka committed
543
    static void deleteItemFromDatabase(Context context, final ItemInfo item) {
544
        final ContentResolver cr = context.getContentResolver();
Michael Jurka's avatar
Michael Jurka committed
545
        final Uri uriToDelete = LauncherSettings.Favorites.getContentUri(item.id, false);
546
        Runnable r = new Runnable() {
547
            public void run() {
Michael Jurka's avatar
Michael Jurka committed
548 549 550 551 552 553 554 555 556 557 558 559 560
                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;
561
                }
Michael Jurka's avatar
Michael Jurka committed
562 563
                sItemsIdMap.remove(item.id);
                sDbIconCache.remove(item);
564
            }
565 566 567 568 569 570
        };
        if (sWorkerThread.getThreadId() == Process.myTid()) {
            r.run();
        } else {
            sWorker.post(r);
        }
571 572
    }

573 574 575
    /**
     * Remove the contents of the specified folder from the database
     */
576
    static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) {
577
        final ContentResolver cr = context.getContentResolver();
578

Michael Jurka's avatar
Michael Jurka committed
579 580 581 582 583 584 585 586 587 588 589 590 591
        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);
592
                }
Michael Jurka's avatar
Michael Jurka committed
593 594 595 596 597 598 599
            }
        };
        if (sWorkerThread.getThreadId() == Process.myTid()) {
            r.run();
        } else {
            sWorker.post(r);
        }
600
    }
601

602 603 604 605 606 607
    /**
     * Set this as the current Launcher activity object for the loader.
     */
    public void initialize(Callbacks callbacks) {
        synchronized (mLock) {
            mCallbacks = new WeakReference<Callbacks>(callbacks);
608 609 610
        }
    }

611 612 613 614
    /**
     * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
     * ACTION_PACKAGE_CHANGED.
     */
615
    @Override
616
    public void onReceive(Context context, Intent intent) {
617
        if (DEBUG_LOADERS) Log.d(TAG, "onReceive intent=" + intent);
618

619
        final String action = intent.getAction();
620

621 622 623 624 625
        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);
626

627
            int op = PackageUpdatedTask.OP_NONE;
628

629 630 631 632
            if (packageName == null || packageName.length() == 0) {
                // they sent us a bad intent
                return;
            }
633

634 635 636 637 638
            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;
639
                }
640 641 642 643 644 645 646
                // 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;
647
                }
648
            }
649

650 651
            if (op != PackageUpdatedTask.OP_NONE) {
                enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName }));
652 653
            }

654
        } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
655 656 657 658
            // 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.
659
            startLoaderFromBackground();
660 661 662 663
        } 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));
664
        } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
665 666 667 668 669 670 671
            // 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();
672
             if (mPreviousConfigMcc != currentConfig.mcc) {
673
                   Log.d(TAG, "Reload apps on config change. curr_mcc:"
674
                       + currentConfig.mcc + " prevmcc:" + mPreviousConfigMcc);
675 676 677
                   forceReload();
             }
             // Update previousConfig
678
             mPreviousConfigMcc = currentConfig.mcc;
679 680
        } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) ||
                   SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) {
681 682 683 684 685
            if (mCallbacks != null) {
                Callbacks callbacks = mCallbacks.get();
                if (callbacks != null) {
                    callbacks.bindSearchablesChanged();
                }
686
            }
687 688 689
        }
    }

690
    private void forceReload() {
691 692 693 694 695 696 697 698 699
        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) {
700 701 702 703
        synchronized (mLock) {
            // Stop any existing loaders first, so they don't set mAllAppsLoaded or
            // mWorkspaceLoaded to true later
            stopLoaderLocked();
704 705
            if (resetAllAppsLoaded) mAllAppsLoaded = false;
            if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
706 707 708
        }
    }

709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726
    /**
     * 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) {
727
            startLoader(false);
728
        }
729
    }
730

731 732 733 734 735 736 737 738 739 740 741 742 743 744
    // 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;
    }

745
    public void startLoader(boolean isLaunching) {
746 747 748 749
        synchronized (mLock) {
            if (DEBUG_LOADERS) {
                Log.d(TAG, "startLoader isLaunching=" + isLaunching);
            }
750

751 752 753
            // 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.
754 755
                // also, don't downgrade isLaunching if we're already running
                isLaunching = isLaunching || stopLoaderLocked();
756
                mLoaderTask = new LoaderTask(mApp, isLaunching);
Winson Chung's avatar
Winson Chung committed
757
                sWorkerThread.setPriority(Thread.NORM_PRIORITY);
758
                sWorker.post(mLoaderTask);
759 760
            }
        }
761
    }
762

763 764 765 766
    public void stopLoader() {
        synchronized (mLock) {
            if (mLoaderTask != null) {
                mLoaderTask.stopLocked();
767
            }
768
        }
769
    }
770

771 772 773 774
    public boolean isAllAppsLoaded() {
        return mAllAppsLoaded;
    }

775 776 777 778 779 780 781 782 783
    boolean isLoadingWorkspace() {
        synchronized (mLock) {
            if (mLoaderTask != null) {
                return mLoaderTask.isLoadingWorkspace();
            }
        }
        return false;
    }

784 785 786 787 788 789 790 791 792 793
    /**
     * 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;
794
        private boolean mIsLoadingAndBindingWorkspace;
795 796
        private boolean mStopped;
        private boolean mLoadAndBindStepFinished;
797
        private HashMap<Object, CharSequence> mLabelCache;
798 799 800 801

        LoaderTask(Context context, boolean isLaunching) {
            mContext = context;
            mIsLaunching = isLaunching;
802
            mLabelCache = new HashMap<Object, CharSequence>();
803
        }
804

805 806 807
        boolean isLaunching() {
            return mIsLaunching;
        }
808

809 810 811 812
        boolean isLoadingWorkspace() {
            return mIsLoadingAndBindingWorkspace;
        }

813
        private void loadAndBindWorkspace() {
814 815
            mIsLoadingAndBindingWorkspace = true;

816 817 818 819
            // Load the workspace
            if (DEBUG_LOADERS) {
                Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
            }
820

821
            if (!mWorkspaceLoaded) {
822
                loadWorkspace();
823 824 825 826 827
                synchronized (LoaderTask.this) {
                    if (mStopped) {
                        return;
                    }
                    mWorkspaceLoaded = true;
828
                }
829
            }
830

831 832 833
            // Bind the workspace
            bindWorkspace();
        }
834

835 836 837 838 839 840
        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;
841

842 843 844 845 846 847
                mHandler.postIdle(new Runnable() {
                        public void run() {
                            synchronized (LoaderTask.this) {
                                mLoadAndBindStepFinished = true;
                                if (DEBUG_LOADERS) {
                                    Log.d(TAG, "done with previous binding step");
848
                                }
849
                                LoaderTask.this.notify();
850 851
                            }
                        }
852 853 854 855 856 857 858
                    });

                while (!mStopped && !mLoadAndBindStepFinished) {
                    try {
                        this.wait();
                    } catch (InterruptedException ex) {
                        // Ignore
859
                    }
860 861 862
                }
                if (DEBUG_LOADERS) {
                    Log.d(TAG, "waited "
863
                            + (SystemClock.uptimeMillis()-workspaceWaitTime)
864
                            + "ms for previous step to finish binding");
865
                }
866
            }
867
        }
868

869 870 871 872 873 874
        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;
875

876
            keep_running: {
877 878
                // Elevate priority when Home launches for the first time to avoid
                // starving at boot time. Staring at a blank home is not cool.
879
                synchronized (mLock) {
880 881
                    if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " +
                            (mIsLaunching ? "DEFAULT" : "BACKGROUND"));
882 883
                    android.os.Process.setThreadPriority(mIsLaunching
                            ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
884
                }
885 886 887 888 889
                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");
890
                    loadAndBindAllApps();
891
                }
892

893 894 895 896 897 898
                if (mStopped) {
                    break keep_running;
                }

                // Whew! Hard work done.  Slow us down, and wait until the UI thread has
                // settled down.
899
                synchronized (mLock) {
900
                    if (mIsLaunching) {
901
                        if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND");
902
                        android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
903 904
                    }
                }
905
                waitForIdle();
906

907 908 909
                // second step
                if (loadWorkspaceFirst) {
                    if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
910
                    loadAndBindAllApps();
911 912 913 914
                } else {
                    if (DEBUG_LOADERS) Log.d(TAG, "step 2: special: loading workspace");
                    loadAndBindWorkspace();
                }
Winson Chung's avatar
Winson Chung committed
915 916 917 918 919

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

922 923 924

            // Update the saved icons if necessary
            if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons");
925 926
            for (Object key : sDbIconCache.keySet()) {
                updateSavedIcon(mContext, (ShortcutInfo) key, sDbIconCache.get(key));
927
            }
928
            sDbIconCache.clear();
929

930 931 932
            // Clear out this reference, otherwise we end up holding it until all of the
            // callback runnables are done.
            mContext = null;
933

934 935 936 937
            synchronized (mLock) {
                // If we are still the last one to be scheduled, remove ourselves.
                if (mLoaderTask == this) {
                    mLoaderTask = null;
938
                }
939 940
            }
        }
941

942 943 944 945
        public void stopLocked() {
            synchronized (LoaderTask.this) {
                mStopped = true;
                this.notify();
946
            }
947
        }
948

949 950 951 952 953 954 955 956 957 958 959 960
        /**
         * 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;
                }
961

962 963 964
                if (mCallbacks == null) {
                    return null;
                }
965

966 967 968 969 970 971 972
                final Callbacks callbacks = mCallbacks.get();
                if (callbacks != oldCallbacks) {
                    return null;
                }
                if (callbacks == null) {
                    Log.w(TAG, "no mCallbacks");
                    return null;
973
                }
974 975

                return callbacks;
976
            }
977
        }
978

979 980
        // 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
981 982 983
            int containerIndex = item.screen;
            if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
                // Return early if we detect that an item is under the hotseat button
984
                if (mCallbacks == null || mCallbacks.get().isAllAppsButtonRank(item.screen)) {
Winson Chung's avatar
Winson Chung committed
985 986
                    return false;
                }
987 988 989 990 991 992 993 994 995 996 997 998

                // 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
999 1000
            } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
                // Skip further checking if it is not the hotseat or workspace container
1001 1002
                return true;
            }
Winson Chung's avatar
Winson Chung committed
1003

1004
            // Check if any workspace icons overlap with each other
1005 1006
            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
1007
                    if (occupied[containerIndex][x][y] != null) {
1008
                        Log.e(TAG, "Error loading shortcut " + item
Winson Chung's avatar
Winson Chung committed
1009
                            + " into cell (" + containerIndex + "-" + item.screen + ":"
1010
                            + x + "," + y
1011
                            + ") occupied by "
Winson Chung's avatar
Winson Chung committed
1012
                            + occupied[containerIndex][x][y]);
1013
                        return false;
1014 1015
                    }
                }
1016 1017 1018
            }
            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
1019
                    occupied[containerIndex][x][y] = item;
1020 1021
                }
            }
Winson Chung's avatar
Winson Chung committed
1022

1023 1024
            return true;
        }
1025

1026 1027
        private void loadWorkspace() {
            final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1028

1029 1030 1031 1032 1033
            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();
1034

1035 1036 1037
            // Make sure the default workspace is loaded, if needed
            mApp.getLauncherProvider().loadDefaultFavoritesIfNecessary();

1038
            sWorkspaceItems.clear();
1039 1040 1041
            sAppWidgets.clear();
            sFolders.clear();
            sItemsIdMap.clear();
1042
            sDbIconCache.clear();
1043

1044
            final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
1045

1046
            final Cursor c = contentResolver.query(
1047
                    LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
1048

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

1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083
            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);
1084 1085 1086
                //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
                //final int displayModeIndex = c.getColumnIndexOrThrow(
                //        LauncherSettings.Favorites.DISPLAY_MODE);
1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110

                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,
1111
                                        titleIndex, mLabelCache);
1112 1113 1114 1115
                            } else {
                                info = getShortcutInfo(c, context, iconTypeIndex,
                                        iconPackageIndex, iconResourceIndex, iconIndex,
                                        titleIndex);
1116 1117 1118

                                // App shortcuts that used to be automatically added to Launcher
                                // didn't always have the correct intent flags set, so do that here
1119 1120 1121
                                if (intent.getAction() != null &&
                                        intent.getCategories() != null &&
                                        intent.getAction().equals(Intent.ACTION_MAIN) &&
1122 1123 1124 1125 1126
                                        intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
                                    intent.addFlags(
                                        Intent.FLAG_ACTIVITY_NEW_TASK |
                                        Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
                                }
1127 1128 1129 1130 1131 1132 1133 1134
                            }

                            if (info != null) {
                                info.intent = intent;
                                info.id = c.getLong(idIndex);
                                container = c.getInt(containerIndex);
                                info.container = container;
                                info.screen = c.getInt(screenIndex);
1135 1136
                                info.cellX = c.getInt(cellXIndex);
                                info.cellY = c.getInt(cellYIndex);
1137 1138 1139 1140

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

1143 1144
                                switch (container) {
                                case LauncherSettings.Favorites.CONTAINER_DESKTOP:
1145
                                case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
1146
                                    sWorkspaceItems.add(info);
1147 1148 1149
                                    break;
                                default:
                                    // Item is in a user folder
1150
                                    FolderInfo folderInfo =
1151
                                            findOrMakeFolder(sFolders, container);
1152 1153
                                    folderInfo.add(info);
                                    break;
1154
                                }
1155
                                sItemsIdMap.put(info.id, info);
1156 1157 1158

                                // now that we've loaded everthing re-save it with the
                                // icon in case it disappears somehow.
1159
                                queueIconToBeChecked(sDbIconCache, info, c, iconIndex);
1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170
                            } 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;
1171

1172
                        case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1173
                            id = c.getLong(idIndex);
1174
                            FolderInfo folderInfo = findOrMakeFolder(sFolders, id);
1175

1176
                            folderInfo.title = c.getString(titleIndex);
1177 1178 1179 1180
                            folderInfo.id = id;
                            container = c.getInt(containerIndex);
                            folderInfo.container = container;
                            folderInfo.screen = c.getInt(screenIndex);
1181 1182
                            folderInfo.cellX = c.getInt(cellXIndex);
                            folderInfo.cellY = c.getInt(cellYIndex);
1183

1184 1185
                            // check & update map of what's occupied
                            if (!checkItemPlacement(occupied, folderInfo)) {
1186
                                break;
1187 1188 1189
                            }
                            switch (container) {
                                case LauncherSettings.Favorites.CONTAINER_DESKTOP:
1190
                                case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
1191
                                    sWorkspaceItems.add(folderInfo);
1192 1193 1194
                                    break;
                            }

1195 1196
                            sItemsIdMap.put(folderInfo.id, folderInfo);
                            sFolders.put(folderInfo.id, folderInfo);
1197 1198 1199 1200 1201 1202 1203 1204 1205
                            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);
1206

1207 1208
                            if (!isSafeMode && (provider == null || provider.provider == null ||
                                    provider.provider.getPackageName() == null)) {
1209 1210 1211 1212
                                String log = "Deleting widget that isn't installed anymore: id="
                                    + id + " appWidgetId=" + appWidgetId;
                                Log.e(TAG, log); 
                                Launcher.sDumpLogs.add(log);
1213 1214
                                itemsToRemove.add(id);
                            } else {
1215 1216
                                appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
                                        provider.provider);
1217 1218
                                appWidgetInfo.id = id;
                                appWidgetInfo.screen = c.getInt(screenIndex);
1219 1220 1221 1222
                                appWidgetInfo.cellX = c.getInt(cellXIndex);
                                appWidgetInfo.cellY = c.getInt(cellYIndex);
                                appWidgetInfo.spanX = c.getInt(spanXIndex);
                                appWidgetInfo.spanY = c.getInt(spanYIndex);
1223 1224 1225
                                int[] minSpan = Launcher.getMinSpanForWidget(context, provider);
                                appWidgetInfo.minSpanX = minSpan[0];
                                appWidgetInfo.minSpanY = minSpan[1];
1226

1227
                                container = c.getInt(containerIndex);
1228 1229
                                if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
                                    container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1230
                                    Log.e(TAG, "Widget found where container "
1231
                                        + "!= CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
1232
                                    continue;
1233
                                }
1234
                                appWidgetInfo.container = c.getInt(containerIndex);
1235

1236 1237 1238
                                // check & update map of what's occupied
                                if (!checkItemPlacement(occupied, appWidgetInfo)) {
                                    break;
1239
                                }
1240 1241
                                sItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
                                sAppWidgets.add(appWidgetInfo);
1242
                            }
1243
                            break;
1244
                        }
1245 1246
                    } catch (Exception e) {
                        Log.w(TAG, "Desktop items loading interrupted:", e);
1247 1248
                    }
                }
1249 1250 1251
            } finally {
                c.close();
            }
1252

1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266
            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);
1267 1268
                    }
                }
1269
            }
1270

1271 1272 1273
            if (DEBUG_LOADERS) {
                Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
                Log.d(TAG, "workspace layout: ");
1274
                for (int y = 0; y < mCellCountY; y++) {
1275 1276 1277 1278 1279
                    String line = "";
                    for (int s = 0; s < Launcher.SCREEN_COUNT; s++) {
                        if (s > 0) {
                            line += " | ";
                        }
1280
                        for (int x = 0; x < mCellCountX; x++) {
1281
                            line += ((occupied[s][x][y] != null) ? "#" : ".");
1282 1283
                        }
                    }
1284
                    Log.d(TAG, "[ " + line + " ]");
1285
                }
1286
            }
1287
        }
1288

1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302
        /**
         * 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;
            }
1303

1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336
            // 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);
                }
            }

1337 1338 1339 1340 1341 1342 1343 1344 1345
            // 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();
                    }
                }
            });
1346

1347
            // Add the items to the workspace.
1348
            int N = workspaceItems.size();
1349 1350 1351
            for (int i=0; i<N; i+=ITEMS_CHUNK) {
                final int start = i;
                final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
1352 1353
                mHandler.post(new Runnable() {
                    public void run() {
1354
                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1355
                        if (callbacks != null) {
1356
                            callbacks.bindItems(workspaceItems, start, start+chunkSize);
1357 1358 1359
                        }
                    }
                });
1360
            }
1361 1362
            // 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);
1363 1364 1365 1366
            mHandler.post(new Runnable() {
                public void run() {
                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
1367
                        callbacks.bindFolders(folders);
1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383
                    }
                }
            });
            // 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.
1384
            N = sAppWidgets.size();
1385 1386
            // once for the current screen
            for (int i=0; i<N; i++) {
1387
                final LauncherAppWidgetInfo widget = sAppWidgets.get(i);
1388
                if (widget.screen == currentScreen) {
1389 1390
                    mHandler.post(new Runnable() {
                        public void run() {
1391
                            Callbacks callbacks = tryGetCallbacks(oldCallbacks);
1392
                            if (callbacks != null) {
1393
                                callbacks.bindAppWidget(widget);
1394 1395 1396 1397
                            }
                        }
                    });
                }
1398 1399 1400
            }
            // once for the other screens
            for (int i=0; i<N; i++) {
1401
                final LauncherAppWidgetInfo widget = sAppWidgets.get(i);
1402 1403 1404 1405 1406 1407
                if (widget.screen != currentScreen) {
                    mHandler.post(new Runnable() {
                        public void run() {
                            Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                            if (callbacks != null) {
                                callbacks.bindAppWidget(widget);
1408
                            }
1409 1410 1411 1412 1413 1414 1415 1416 1417 1418
                        }
                    });
                }
            }
            // Tell the workspace that we're done.
            mHandler.post(new Runnable() {
                public void run() {
                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        callbacks.finishBindingItems();
1419 1420
                    }
                }
1421
            });
Winson Chung's avatar
Winson Chung committed
1422
            // Cleanup
1423 1424
            mHandler.post(new Runnable() {
                public void run() {
Winson Chung's avatar
Winson Chung committed
1425
                    // If we're profiling, ensure this is the last thing in the queue.
1426 1427 1428
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "bound workspace in "
                            + (SystemClock.uptimeMillis()-t) + "ms");
1429
                    }
1430 1431

                    mIsLoadingAndBindingWorkspace = false;
1432
                }
1433 1434 1435 1436 1437 1438 1439 1440 1441
            });
        }

        private void loadAndBindAllApps() {
            if (DEBUG_LOADERS) {
                Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
            }
            if (!mAllAppsLoaded) {
                loadAllAppsByBatch();
1442 1443 1444 1445 1446
                synchronized (LoaderTask.this) {
                    if (mStopped) {
                        return;
                    }
                    mAllAppsLoaded = true;
1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461
                }
            } 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
1462
            @SuppressWarnings("unchecked")
1463
            final ArrayList<ApplicationInfo> list
1464
                    = (ArrayList<ApplicationInfo>) mAllAppsList.data.clone();
1465 1466 1467 1468 1469 1470
            mHandler.post(new Runnable() {
                public void run() {
                    final long t = SystemClock.uptimeMillis();
                    final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        callbacks.bindAllApplications(list);
1471
                    }
1472 1473
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "bound all " + list.size() + " apps from cache in "
1474
                                + (SystemClock.uptimeMillis()-t) + "ms");
1475
                    }
1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490
                }
            });

        }

        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;
1491
            }
1492

1493 1494
            final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
            mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
1495

1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513
            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) {
1514 1515
                        return;
                    }
1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531
                    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,
1532
                            new LauncherModel.ShortcutNameComparator(packageManager, mLabelCache));
1533 1534 1535 1536
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "sort took "
                                + (SystemClock.uptimeMillis()-sortTime) + "ms");
                    }
1537 1538
                }

1539 1540 1541 1542 1543
                final long t2 = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;

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

1549 1550 1551 1552 1553
                final boolean first = i <= batchSize;
                final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                final ArrayList<ApplicationInfo> added = mAllAppsList.added;
                mAllAppsList.added = new ArrayList<ApplicationInfo>();

1554 1555 1556 1557
                mHandler.post(new Runnable() {
                    public void run() {
                        final long t = SystemClock.uptimeMillis();
                        if (callbacks != null) {
1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568
                            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");
1569 1570 1571 1572
                        }
                    }
                });

1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585
                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) { }
                }
1586 1587
            }

1588 1589 1590 1591 1592 1593
            if (DEBUG_LOADERS) {
                Log.d(TAG, "cached all " + N + " apps in "
                        + (SystemClock.uptimeMillis()-t) + "ms"
                        + (mAllAppsLoadDelay > 0 ? " (including delay)" : ""));
            }
        }
1594

1595 1596 1597 1598 1599 1600
        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);
1601
            Log.d(TAG, "mItems size=" + sWorkspaceItems.size());
1602 1603
        }
    }
1604

1605
    void enqueuePackageUpdated(PackageUpdatedTask task) {
1606
        sWorker.post(task);
1607
    }
1608

1609 1610 1611
    private class PackageUpdatedTask implements Runnable {
        int mOp;
        String[] mPackages;
1612

1613 1614 1615 1616 1617
        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
1618

1619

1620 1621 1622 1623
        public PackageUpdatedTask(int op, String[] packages) {
            mOp = op;
            mPackages = packages;
        }
1624

1625 1626
        public void run() {
            final Context context = mApp;
1627

1628 1629 1630 1631 1632 1633 1634
            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]);
1635
                    }
1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650
                    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;
            }
1651

1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662
            ArrayList<ApplicationInfo> added = null;
            ArrayList<ApplicationInfo> modified = null;

            if (mAllAppsList.added.size() > 0) {
                added = mAllAppsList.added;
                mAllAppsList.added = new ArrayList<ApplicationInfo>();
            }
            if (mAllAppsList.modified.size() > 0) {
                modified = mAllAppsList.modified;
                mAllAppsList.modified = new ArrayList<ApplicationInfo>();
            }
1663

1664 1665 1666 1667 1668 1669 1670 1671
            // We may be removing packages that have no associated launcher application, so we
            // pass through the removed package names directly.
            // NOTE: We flush the icon cache aggressively in removePackage() above.
            final ArrayList<String> removedPackageNames = new ArrayList<String>();
            for (int i = 0; i < N; ++i) {
                removedPackageNames.add(packages[i]);
            }

1672 1673 1674 1675
            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;
1676 1677
            }

1678 1679 1680 1681
            if (added != null) {
                final ArrayList<ApplicationInfo> addedFinal = added;
                mHandler.post(new Runnable() {
                    public void run() {
1682 1683
                        Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
                        if (callbacks == cb && cb != null) {
1684 1685 1686 1687 1688 1689 1690 1691 1692
                            callbacks.bindAppsAdded(addedFinal);
                        }
                    }
                });
            }
            if (modified != null) {
                final ArrayList<ApplicationInfo> modifiedFinal = modified;
                mHandler.post(new Runnable() {
                    public void run() {
1693 1694
                        Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
                        if (callbacks == cb && cb != null) {
1695 1696 1697 1698 1699
                            callbacks.bindAppsUpdated(modifiedFinal);
                        }
                    }
                });
            }
1700
            if (!removedPackageNames.isEmpty()) {
1701 1702 1703
                final boolean permanent = mOp != OP_UNAVAILABLE;
                mHandler.post(new Runnable() {
                    public void run() {
1704 1705
                        Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
                        if (callbacks == cb && cb != null) {
1706
                            callbacks.bindAppsRemoved(removedPackageNames, permanent);
1707 1708 1709
                        }
                    }
                });
1710
            }
1711 1712 1713 1714

            mHandler.post(new Runnable() {
                @Override
                public void run() {
1715 1716
                    Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
                    if (callbacks == cb && cb != null) {
1717 1718 1719 1720
                        callbacks.bindPackagesUpdated();
                    }
                }
            });
1721 1722 1723
        }
    }

1724
    /**
1725 1726
     * This is called from the code that adds shortcuts from the intent receiver.  This
     * doesn't have a Cursor, but
1727
     */
1728
    public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context) {
1729
        return getShortcutInfo(manager, intent, context, null, -1, -1, null);
1730 1731 1732 1733 1734 1735 1736 1737
    }

    /**
     * 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,
1738
            Cursor c, int iconIndex, int titleIndex, HashMap<Object, CharSequence> labelCache) {
1739
        Bitmap icon = null;
Michael Jurka's avatar
Michael Jurka committed
1740
        final ShortcutInfo info = new ShortcutInfo();
1741

1742 1743
        ComponentName componentName = intent.getComponent();
        if (componentName == null) {
1744
            return null;
1745 1746
        }

1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757
        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());
        }

1758 1759 1760
        // TODO: See if the PackageManager knows about this case.  If it doesn't
        // then return null & delete this.

1761 1762 1763 1764
        // 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.
1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784

        // 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);
        }
1785
        if (resolveInfo != null) {
1786
            icon = mIconCache.getIcon(componentName, resolveInfo, labelCache);
1787 1788 1789 1790
        }
        // the db
        if (icon == null) {
            if (c != null) {
1791
                icon = getIconFromCursor(c, iconIndex, context);
1792
            }
1793
        }
1794 1795 1796 1797 1798 1799 1800 1801 1802
        // the fallback icon
        if (icon == null) {
            icon = getFallbackIcon();
            info.usingFallbackIcon = true;
        }
        info.setIcon(icon);

        // from the resource
        if (resolveInfo != null) {
1803 1804 1805
            ComponentName key = LauncherModel.getComponentNameFromResolveInfo(resolveInfo);
            if (labelCache != null && labelCache.containsKey(key)) {
                info.title = labelCache.get(key);
1806 1807 1808
            } else {
                info.title = resolveInfo.activityInfo.loadLabel(manager);
                if (labelCache != null) {
1809
                    labelCache.put(key, info.title);
1810 1811
                }
            }
1812 1813 1814 1815 1816 1817 1818 1819
        }
        // from the db
        if (info.title == null) {
            if (c != null) {
                info.title =  c.getString(titleIndex);
            }
        }
        // fall back to the class name of the activity
1820
        if (info.title == null) {
1821
            info.title = componentName.getClassName();
1822 1823 1824 1825
        }
        info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
        return info;
    }
1826

1827
    /**
1828
     * Make an ShortcutInfo object for a shortcut that isn't an application.
1829
     */
1830
    private ShortcutInfo getShortcutInfo(Cursor c, Context context,
1831 1832
            int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex,
            int titleIndex) {
1833

1834
        Bitmap icon = null;
Michael Jurka's avatar
Michael Jurka committed
1835
        final ShortcutInfo info = new ShortcutInfo();
1836
        info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
1837

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

1840 1841
        info.title = c.getString(titleIndex);

1842 1843 1844 1845 1846 1847
        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();
1848 1849
            info.customIcon = false;
            // the resource
1850 1851
            try {
                Resources resources = packageManager.getResourcesForApplication(packageName);
1852 1853
                if (resources != null) {
                    final int id = resources.getIdentifier(resourceName, null, null);
Michael Jurka's avatar
Michael Jurka committed
1854 1855
                    icon = Utilities.createIconBitmap(
                            mIconCache.getFullResIcon(resources, id), context);
1856
                }
1857
            } catch (Exception e) {
1858 1859 1860 1861
                // drop this.  we have other places to look for icons
            }
            // the db
            if (icon == null) {
1862
                icon = getIconFromCursor(c, iconIndex, context);
1863 1864 1865 1866 1867
            }
            // the fallback icon
            if (icon == null) {
                icon = getFallbackIcon();
                info.usingFallbackIcon = true;
1868
            }
1869 1870
            break;
        case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
1871
            icon = getIconFromCursor(c, iconIndex, context);
1872 1873 1874 1875 1876 1877
            if (icon == null) {
                icon = getFallbackIcon();
                info.customIcon = false;
                info.usingFallbackIcon = true;
            } else {
                info.customIcon = true;
1878 1879 1880
            }
            break;
        default:
1881
            icon = getFallbackIcon();
1882
            info.usingFallbackIcon = true;
1883 1884
            info.customIcon = false;
            break;
1885
        }
1886
        info.setIcon(icon);
1887
        return info;
1888
    }
1889

1890
    Bitmap getIconFromCursor(Cursor c, int iconIndex, Context context) {
1891 1892 1893
        @SuppressWarnings("all") // suppress dead code warning
        final boolean debug = false;
        if (debug) {
1894 1895 1896 1897 1898
            Log.d(TAG, "getIconFromCursor app="
                    + c.getString(c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE)));
        }
        byte[] data = c.getBlob(iconIndex);
        try {
1899 1900
            return Utilities.createIconBitmap(
                    BitmapFactory.decodeByteArray(data, 0, data.length), context);
1901 1902 1903 1904 1905
        } catch (Exception e) {
            return null;
        }
    }

1906 1907
    ShortcutInfo addShortcut(Context context, Intent data, long container, int screen,
            int cellX, int cellY, boolean notify) {
1908
        final ShortcutInfo info = infoFromShortcutIntent(context, data, null);
1909 1910 1911
        if (info == null) {
            return null;
        }
1912
        addItemToDatabase(context, info, container, screen, cellX, cellY, notify);
1913 1914 1915 1916

        return info;
    }

1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929
    /**
     * 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;
1930 1931
    }

1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969
    /**
     * 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;
    }

1970
    ShortcutInfo infoFromShortcutIntent(Context context, Intent data, Bitmap fallbackIcon) {
1971 1972 1973 1974
        Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
        String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
        Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);

1975 1976 1977 1978 1979 1980
        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;
        }

1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996
        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
1997 1998
                    icon = Utilities.createIconBitmap(
                            mIconCache.getFullResIcon(resources, id), context);
1999 2000 2001 2002 2003 2004
                } catch (Exception e) {
                    Log.w(TAG, "Could not load shortcut icon: " + extra);
                }
            }
        }

Michael Jurka's avatar
Michael Jurka committed
2005
        final ShortcutInfo info = new ShortcutInfo();
2006

2007
        if (icon == null) {
2008 2009 2010 2011 2012 2013
            if (fallbackIcon != null) {
                icon = fallbackIcon;
            } else {
                icon = getFallbackIcon();
                info.usingFallbackIcon = true;
            }
2014 2015
        }
        info.setIcon(icon);
2016

2017 2018 2019 2020 2021 2022 2023 2024
        info.title = name;
        info.intent = intent;
        info.customIcon = customIcon;
        info.iconResource = iconResource;

        return info;
    }

2025 2026
    boolean queueIconToBeChecked(HashMap<Object, byte[]> cache, ShortcutInfo info, Cursor c,
            int iconIndex) {
2027 2028
        // If apps can't be on SD, don't even bother.
        if (!mAppsCanBeOnExternalStorage) {
2029
            return false;
2030
        }
2031 2032 2033 2034 2035 2036
        // 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.
2037
        if (!info.customIcon && !info.usingFallbackIcon) {
2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050
            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 {
2051 2052
                needSave = true;
            }
2053 2054 2055 2056 2057 2058 2059 2060
        } 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);
2061 2062 2063
        }
    }

2064
    /**
2065
     * Return an existing FolderInfo object if we have encountered this ID previously,
2066
     * or make a new one.
2067
     */
2068
    private static FolderInfo findOrMakeFolder(HashMap<Long, FolderInfo> folders, long id) {
2069 2070
        // See if a placeholder was created for us already
        FolderInfo folderInfo = folders.get(id);
2071
        if (folderInfo == null) {
2072
            // No placeholder -- create a new instance
Michael Jurka's avatar
Michael Jurka committed
2073
            folderInfo = new FolderInfo();
2074
            folders.put(id, folderInfo);
2075
        }
2076
        return folderInfo;
2077 2078
    }

2079
    private static final Collator sCollator = Collator.getInstance();
2080
    public static final Comparator<ApplicationInfo> APP_NAME_COMPARATOR
2081 2082
            = new Comparator<ApplicationInfo>() {
        public final int compare(ApplicationInfo a, ApplicationInfo b) {
2083 2084 2085 2086 2087
            int result = sCollator.compare(a.title.toString(), b.title.toString());
            if (result == 0) {
                result = a.componentName.compareTo(b.componentName);
            }
            return result;
2088
        }
2089
    };
2090 2091 2092 2093 2094 2095 2096 2097
    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;
        }
    };
2098 2099 2100 2101 2102 2103
    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());
        }
    };
2104 2105 2106 2107 2108 2109 2110
    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);
        }
    }
2111 2112
    public static class ShortcutNameComparator implements Comparator<ResolveInfo> {
        private PackageManager mPackageManager;
2113
        private HashMap<Object, CharSequence> mLabelCache;
2114 2115
        ShortcutNameComparator(PackageManager pm) {
            mPackageManager = pm;
2116 2117 2118 2119 2120
            mLabelCache = new HashMap<Object, CharSequence>();
        }
        ShortcutNameComparator(PackageManager pm, HashMap<Object, CharSequence> labelCache) {
            mPackageManager = pm;
            mLabelCache = labelCache;
2121 2122
        }
        public final int compare(ResolveInfo a, ResolveInfo b) {
2123
            CharSequence labelA, labelB;
2124 2125 2126 2127
            ComponentName keyA = LauncherModel.getComponentNameFromResolveInfo(a);
            ComponentName keyB = LauncherModel.getComponentNameFromResolveInfo(b);
            if (mLabelCache.containsKey(keyA)) {
                labelA = mLabelCache.get(keyA);
2128 2129 2130
            } else {
                labelA = a.loadLabel(mPackageManager).toString();

2131
                mLabelCache.put(keyA, labelA);
2132
            }
2133 2134
            if (mLabelCache.containsKey(keyB)) {
                labelB = mLabelCache.get(keyB);
2135 2136 2137
            } else {
                labelB = b.loadLabel(mPackageManager).toString();

2138
                mLabelCache.put(keyB, labelB);
2139
            }
2140 2141 2142
            return sCollator.compare(labelA, labelB);
        }
    };
2143 2144 2145 2146 2147 2148 2149 2150 2151
    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;
2152 2153 2154 2155
            if (mLabelCache.containsKey(a)) {
                labelA = mLabelCache.get(a);
            } else {
                labelA = (a instanceof AppWidgetProviderInfo) ?
2156 2157
                    ((AppWidgetProviderInfo) a).label :
                    ((ResolveInfo) a).loadLabel(mPackageManager).toString();
2158 2159 2160 2161 2162 2163
                mLabelCache.put(a, labelA);
            }
            if (mLabelCache.containsKey(b)) {
                labelB = mLabelCache.get(b);
            } else {
                labelB = (b instanceof AppWidgetProviderInfo) ?
2164 2165
                    ((AppWidgetProviderInfo) b).label :
                    ((ResolveInfo) b).loadLabel(mPackageManager).toString();
2166 2167
                mLabelCache.put(b, labelB);
            }
2168 2169 2170
            return sCollator.compare(labelA, labelB);
        }
    };
2171 2172 2173 2174 2175 2176 2177

    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);
2178 2179 2180 2181 2182
        if (mLoaderTask != null) {
            mLoaderTask.dumpState();
        } else {
            Log.d(TAG, "mLoaderTask=null");
        }
2183
    }
2184
}