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