LauncherModel.java revision 3994035966f2d402d5faf355522424cadadb3aa3
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, 944 LauncherSettings.Favorites._ID + " DESC"); 945 946 // +1 for the hotseat (it can be larger than the workspace) 947 // Load workspace in reverse order to ensure that latest items are loaded first (and 948 // before any earlier duplicates) 949 final ItemInfo occupied[][][] = 950 new ItemInfo[Launcher.SCREEN_COUNT + 1][mCellCountX + 1][mCellCountY + 1]; 951 952 try { 953 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); 954 final int intentIndex = c.getColumnIndexOrThrow 955 (LauncherSettings.Favorites.INTENT); 956 final int titleIndex = c.getColumnIndexOrThrow 957 (LauncherSettings.Favorites.TITLE); 958 final int iconTypeIndex = c.getColumnIndexOrThrow( 959 LauncherSettings.Favorites.ICON_TYPE); 960 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); 961 final int iconPackageIndex = c.getColumnIndexOrThrow( 962 LauncherSettings.Favorites.ICON_PACKAGE); 963 final int iconResourceIndex = c.getColumnIndexOrThrow( 964 LauncherSettings.Favorites.ICON_RESOURCE); 965 final int containerIndex = c.getColumnIndexOrThrow( 966 LauncherSettings.Favorites.CONTAINER); 967 final int itemTypeIndex = c.getColumnIndexOrThrow( 968 LauncherSettings.Favorites.ITEM_TYPE); 969 final int appWidgetIdIndex = c.getColumnIndexOrThrow( 970 LauncherSettings.Favorites.APPWIDGET_ID); 971 final int screenIndex = c.getColumnIndexOrThrow( 972 LauncherSettings.Favorites.SCREEN); 973 final int cellXIndex = c.getColumnIndexOrThrow 974 (LauncherSettings.Favorites.CELLX); 975 final int cellYIndex = c.getColumnIndexOrThrow 976 (LauncherSettings.Favorites.CELLY); 977 final int spanXIndex = c.getColumnIndexOrThrow 978 (LauncherSettings.Favorites.SPANX); 979 final int spanYIndex = c.getColumnIndexOrThrow( 980 LauncherSettings.Favorites.SPANY); 981 final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); 982 final int displayModeIndex = c.getColumnIndexOrThrow( 983 LauncherSettings.Favorites.DISPLAY_MODE); 984 985 ShortcutInfo info; 986 String intentDescription; 987 LauncherAppWidgetInfo appWidgetInfo; 988 int container; 989 long id; 990 Intent intent; 991 992 while (!mStopped && c.moveToNext()) { 993 try { 994 int itemType = c.getInt(itemTypeIndex); 995 996 switch (itemType) { 997 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 998 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 999 intentDescription = c.getString(intentIndex); 1000 try { 1001 intent = Intent.parseUri(intentDescription, 0); 1002 } catch (URISyntaxException e) { 1003 continue; 1004 } 1005 1006 if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { 1007 info = getShortcutInfo(manager, intent, context, c, iconIndex, 1008 titleIndex, mLabelCache); 1009 } else { 1010 info = getShortcutInfo(c, context, iconTypeIndex, 1011 iconPackageIndex, iconResourceIndex, iconIndex, 1012 titleIndex); 1013 } 1014 1015 if (info != null) { 1016 info.intent = intent; 1017 info.id = c.getLong(idIndex); 1018 container = c.getInt(containerIndex); 1019 info.container = container; 1020 info.screen = c.getInt(screenIndex); 1021 info.cellX = c.getInt(cellXIndex); 1022 info.cellY = c.getInt(cellYIndex); 1023 1024 // check & update map of what's occupied 1025 if (!checkItemPlacement(occupied, info)) { 1026 break; 1027 } 1028 1029 switch (container) { 1030 case LauncherSettings.Favorites.CONTAINER_DESKTOP: 1031 case LauncherSettings.Favorites.CONTAINER_HOTSEAT: 1032 sWorkspaceItems.add(info); 1033 break; 1034 default: 1035 // Item is in a user folder 1036 FolderInfo folderInfo = 1037 findOrMakeFolder(sFolders, container); 1038 folderInfo.add(info); 1039 break; 1040 } 1041 sItemsIdMap.put(info.id, info); 1042 1043 // now that we've loaded everthing re-save it with the 1044 // icon in case it disappears somehow. 1045 queueIconToBeChecked(sDbIconCache, info, c, iconIndex); 1046 } else { 1047 // Failed to load the shortcut, probably because the 1048 // activity manager couldn't resolve it (maybe the app 1049 // was uninstalled), or the db row was somehow screwed up. 1050 // Delete it. 1051 id = c.getLong(idIndex); 1052 Log.e(TAG, "Error loading shortcut " + id + ", removing it"); 1053 contentResolver.delete(LauncherSettings.Favorites.getContentUri( 1054 id, false), null, null); 1055 } 1056 break; 1057 1058 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 1059 id = c.getLong(idIndex); 1060 FolderInfo folderInfo = findOrMakeFolder(sFolders, id); 1061 1062 folderInfo.title = c.getString(titleIndex); 1063 folderInfo.id = id; 1064 container = c.getInt(containerIndex); 1065 folderInfo.container = container; 1066 folderInfo.screen = c.getInt(screenIndex); 1067 folderInfo.cellX = c.getInt(cellXIndex); 1068 folderInfo.cellY = c.getInt(cellYIndex); 1069 1070 // check & update map of what's occupied 1071 if (!checkItemPlacement(occupied, folderInfo)) { 1072 break; 1073 } 1074 switch (container) { 1075 case LauncherSettings.Favorites.CONTAINER_DESKTOP: 1076 case LauncherSettings.Favorites.CONTAINER_HOTSEAT: 1077 sWorkspaceItems.add(folderInfo); 1078 break; 1079 } 1080 1081 sItemsIdMap.put(folderInfo.id, folderInfo); 1082 sFolders.put(folderInfo.id, folderInfo); 1083 break; 1084 1085 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 1086 // Read all Launcher-specific widget details 1087 int appWidgetId = c.getInt(appWidgetIdIndex); 1088 id = c.getLong(idIndex); 1089 1090 final AppWidgetProviderInfo provider = 1091 widgets.getAppWidgetInfo(appWidgetId); 1092 1093 if (!isSafeMode && (provider == null || provider.provider == null || 1094 provider.provider.getPackageName() == null)) { 1095 Log.e(TAG, "Deleting widget that isn't installed anymore: id=" 1096 + id + " appWidgetId=" + appWidgetId); 1097 itemsToRemove.add(id); 1098 } else { 1099 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId); 1100 appWidgetInfo.id = id; 1101 appWidgetInfo.screen = c.getInt(screenIndex); 1102 appWidgetInfo.cellX = c.getInt(cellXIndex); 1103 appWidgetInfo.cellY = c.getInt(cellYIndex); 1104 appWidgetInfo.spanX = c.getInt(spanXIndex); 1105 appWidgetInfo.spanY = c.getInt(spanYIndex); 1106 1107 container = c.getInt(containerIndex); 1108 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP && 1109 container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 1110 Log.e(TAG, "Widget found where container " 1111 + "!= CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!"); 1112 continue; 1113 } 1114 appWidgetInfo.container = c.getInt(containerIndex); 1115 1116 // check & update map of what's occupied 1117 if (!checkItemPlacement(occupied, appWidgetInfo)) { 1118 break; 1119 } 1120 sItemsIdMap.put(appWidgetInfo.id, appWidgetInfo); 1121 sAppWidgets.add(appWidgetInfo); 1122 } 1123 break; 1124 } 1125 } catch (Exception e) { 1126 Log.w(TAG, "Desktop items loading interrupted:", e); 1127 } 1128 } 1129 } finally { 1130 c.close(); 1131 } 1132 1133 if (itemsToRemove.size() > 0) { 1134 ContentProviderClient client = contentResolver.acquireContentProviderClient( 1135 LauncherSettings.Favorites.CONTENT_URI); 1136 // Remove dead items 1137 for (long id : itemsToRemove) { 1138 if (DEBUG_LOADERS) { 1139 Log.d(TAG, "Removed id = " + id); 1140 } 1141 // Don't notify content observers 1142 try { 1143 client.delete(LauncherSettings.Favorites.getContentUri(id, false), 1144 null, null); 1145 } catch (RemoteException e) { 1146 Log.w(TAG, "Could not remove id = " + id); 1147 } 1148 } 1149 } 1150 1151 if (DEBUG_LOADERS) { 1152 Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms"); 1153 Log.d(TAG, "workspace layout: "); 1154 for (int y = 0; y < mCellCountY; y++) { 1155 String line = ""; 1156 for (int s = 0; s < Launcher.SCREEN_COUNT; s++) { 1157 if (s > 0) { 1158 line += " | "; 1159 } 1160 for (int x = 0; x < mCellCountX; x++) { 1161 line += ((occupied[s][x][y] != null) ? "#" : "."); 1162 } 1163 } 1164 Log.d(TAG, "[ " + line + " ]"); 1165 } 1166 } 1167 } 1168 1169 /** 1170 * Read everything out of our database. 1171 */ 1172 private void bindWorkspace() { 1173 final long t = SystemClock.uptimeMillis(); 1174 1175 // Don't use these two variables in any of the callback runnables. 1176 // Otherwise we hold a reference to them. 1177 final Callbacks oldCallbacks = mCallbacks.get(); 1178 if (oldCallbacks == null) { 1179 // This launcher has exited and nobody bothered to tell us. Just bail. 1180 Log.w(TAG, "LoaderTask running with no launcher"); 1181 return; 1182 } 1183 1184 int N; 1185 // Tell the workspace that we're about to start firing items at it 1186 mHandler.post(new Runnable() { 1187 public void run() { 1188 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1189 if (callbacks != null) { 1190 callbacks.startBinding(); 1191 } 1192 } 1193 }); 1194 1195 // Unbind previously bound workspace items to prevent a leak of AppWidgetHostViews. 1196 final ArrayList<ItemInfo> workspaceItems = unbindWorkspaceItemsOnMainThread(); 1197 1198 // Add the items to the workspace. 1199 N = workspaceItems.size(); 1200 for (int i=0; i<N; i+=ITEMS_CHUNK) { 1201 final int start = i; 1202 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i); 1203 mHandler.post(new Runnable() { 1204 public void run() { 1205 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1206 if (callbacks != null) { 1207 callbacks.bindItems(workspaceItems, start, start+chunkSize); 1208 } 1209 } 1210 }); 1211 } 1212 // Ensure that we don't use the same folders data structure on the main thread 1213 final HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>(sFolders); 1214 mHandler.post(new Runnable() { 1215 public void run() { 1216 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1217 if (callbacks != null) { 1218 callbacks.bindFolders(folders); 1219 } 1220 } 1221 }); 1222 // Wait until the queue goes empty. 1223 mHandler.post(new Runnable() { 1224 public void run() { 1225 if (DEBUG_LOADERS) { 1226 Log.d(TAG, "Going to start binding widgets soon."); 1227 } 1228 } 1229 }); 1230 // Bind the widgets, one at a time. 1231 // WARNING: this is calling into the workspace from the background thread, 1232 // but since getCurrentScreen() just returns the int, we should be okay. This 1233 // is just a hint for the order, and if it's wrong, we'll be okay. 1234 // TODO: instead, we should have that push the current screen into here. 1235 final int currentScreen = oldCallbacks.getCurrentWorkspaceScreen(); 1236 N = sAppWidgets.size(); 1237 // once for the current screen 1238 for (int i=0; i<N; i++) { 1239 final LauncherAppWidgetInfo widget = sAppWidgets.get(i); 1240 if (widget.screen == currentScreen) { 1241 mHandler.post(new Runnable() { 1242 public void run() { 1243 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1244 if (callbacks != null) { 1245 callbacks.bindAppWidget(widget); 1246 } 1247 } 1248 }); 1249 } 1250 } 1251 // once for the other screens 1252 for (int i=0; i<N; i++) { 1253 final LauncherAppWidgetInfo widget = sAppWidgets.get(i); 1254 if (widget.screen != currentScreen) { 1255 mHandler.post(new Runnable() { 1256 public void run() { 1257 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1258 if (callbacks != null) { 1259 callbacks.bindAppWidget(widget); 1260 } 1261 } 1262 }); 1263 } 1264 } 1265 // Tell the workspace that we're done. 1266 mHandler.post(new Runnable() { 1267 public void run() { 1268 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1269 if (callbacks != null) { 1270 callbacks.finishBindingItems(); 1271 } 1272 } 1273 }); 1274 // If we're profiling, this is the last thing in the queue. 1275 mHandler.post(new Runnable() { 1276 public void run() { 1277 if (DEBUG_LOADERS) { 1278 Log.d(TAG, "bound workspace in " 1279 + (SystemClock.uptimeMillis()-t) + "ms"); 1280 } 1281 } 1282 }); 1283 } 1284 1285 private void loadAndBindAllApps() { 1286 if (DEBUG_LOADERS) { 1287 Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded); 1288 } 1289 if (!mAllAppsLoaded) { 1290 loadAllAppsByBatch(); 1291 if (mStopped) { 1292 return; 1293 } 1294 mAllAppsLoaded = true; 1295 } else { 1296 onlyBindAllApps(); 1297 } 1298 } 1299 1300 private void onlyBindAllApps() { 1301 final Callbacks oldCallbacks = mCallbacks.get(); 1302 if (oldCallbacks == null) { 1303 // This launcher has exited and nobody bothered to tell us. Just bail. 1304 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)"); 1305 return; 1306 } 1307 1308 // shallow copy 1309 final ArrayList<ApplicationInfo> list 1310 = (ArrayList<ApplicationInfo>)mAllAppsList.data.clone(); 1311 mHandler.post(new Runnable() { 1312 public void run() { 1313 final long t = SystemClock.uptimeMillis(); 1314 final Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1315 if (callbacks != null) { 1316 callbacks.bindAllApplications(list); 1317 } 1318 if (DEBUG_LOADERS) { 1319 Log.d(TAG, "bound all " + list.size() + " apps from cache in " 1320 + (SystemClock.uptimeMillis()-t) + "ms"); 1321 } 1322 } 1323 }); 1324 1325 } 1326 1327 private void loadAllAppsByBatch() { 1328 final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 1329 1330 // Don't use these two variables in any of the callback runnables. 1331 // Otherwise we hold a reference to them. 1332 final Callbacks oldCallbacks = mCallbacks.get(); 1333 if (oldCallbacks == null) { 1334 // This launcher has exited and nobody bothered to tell us. Just bail. 1335 Log.w(TAG, "LoaderTask running with no launcher (loadAllAppsByBatch)"); 1336 return; 1337 } 1338 1339 final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); 1340 mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); 1341 1342 final PackageManager packageManager = mContext.getPackageManager(); 1343 List<ResolveInfo> apps = null; 1344 1345 int N = Integer.MAX_VALUE; 1346 1347 int startIndex; 1348 int i=0; 1349 int batchSize = -1; 1350 while (i < N && !mStopped) { 1351 if (i == 0) { 1352 mAllAppsList.clear(); 1353 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 1354 apps = packageManager.queryIntentActivities(mainIntent, 0); 1355 if (DEBUG_LOADERS) { 1356 Log.d(TAG, "queryIntentActivities took " 1357 + (SystemClock.uptimeMillis()-qiaTime) + "ms"); 1358 } 1359 if (apps == null) { 1360 return; 1361 } 1362 N = apps.size(); 1363 if (DEBUG_LOADERS) { 1364 Log.d(TAG, "queryIntentActivities got " + N + " apps"); 1365 } 1366 if (N == 0) { 1367 // There are no apps?!? 1368 return; 1369 } 1370 if (mBatchSize == 0) { 1371 batchSize = N; 1372 } else { 1373 batchSize = mBatchSize; 1374 } 1375 1376 final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 1377 Collections.sort(apps, 1378 new LauncherModel.ShortcutNameComparator(packageManager, mLabelCache)); 1379 if (DEBUG_LOADERS) { 1380 Log.d(TAG, "sort took " 1381 + (SystemClock.uptimeMillis()-sortTime) + "ms"); 1382 } 1383 } 1384 1385 final long t2 = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 1386 1387 startIndex = i; 1388 for (int j=0; i<N && j<batchSize; j++) { 1389 // This builds the icon bitmaps. 1390 mAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i), 1391 mIconCache, mLabelCache)); 1392 i++; 1393 } 1394 1395 final boolean first = i <= batchSize; 1396 final Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1397 final ArrayList<ApplicationInfo> added = mAllAppsList.added; 1398 mAllAppsList.added = new ArrayList<ApplicationInfo>(); 1399 1400 mHandler.post(new Runnable() { 1401 public void run() { 1402 final long t = SystemClock.uptimeMillis(); 1403 if (callbacks != null) { 1404 if (first) { 1405 callbacks.bindAllApplications(added); 1406 } else { 1407 callbacks.bindAppsAdded(added); 1408 } 1409 if (DEBUG_LOADERS) { 1410 Log.d(TAG, "bound " + added.size() + " apps in " 1411 + (SystemClock.uptimeMillis() - t) + "ms"); 1412 } 1413 } else { 1414 Log.i(TAG, "not binding apps: no Launcher activity"); 1415 } 1416 } 1417 }); 1418 1419 if (DEBUG_LOADERS) { 1420 Log.d(TAG, "batch of " + (i-startIndex) + " icons processed in " 1421 + (SystemClock.uptimeMillis()-t2) + "ms"); 1422 } 1423 1424 if (mAllAppsLoadDelay > 0 && i < N) { 1425 try { 1426 if (DEBUG_LOADERS) { 1427 Log.d(TAG, "sleeping for " + mAllAppsLoadDelay + "ms"); 1428 } 1429 Thread.sleep(mAllAppsLoadDelay); 1430 } catch (InterruptedException exc) { } 1431 } 1432 } 1433 1434 if (DEBUG_LOADERS) { 1435 Log.d(TAG, "cached all " + N + " apps in " 1436 + (SystemClock.uptimeMillis()-t) + "ms" 1437 + (mAllAppsLoadDelay > 0 ? " (including delay)" : "")); 1438 } 1439 } 1440 1441 public void dumpState() { 1442 Log.d(TAG, "mLoaderTask.mContext=" + mContext); 1443 Log.d(TAG, "mLoaderTask.mWaitThread=" + mWaitThread); 1444 Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching); 1445 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped); 1446 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished); 1447 Log.d(TAG, "mItems size=" + sWorkspaceItems.size()); 1448 } 1449 } 1450 1451 void enqueuePackageUpdated(PackageUpdatedTask task) { 1452 sWorker.post(task); 1453 } 1454 1455 private class PackageUpdatedTask implements Runnable { 1456 int mOp; 1457 String[] mPackages; 1458 1459 public static final int OP_NONE = 0; 1460 public static final int OP_ADD = 1; 1461 public static final int OP_UPDATE = 2; 1462 public static final int OP_REMOVE = 3; // uninstlled 1463 public static final int OP_UNAVAILABLE = 4; // external media unmounted 1464 1465 1466 public PackageUpdatedTask(int op, String[] packages) { 1467 mOp = op; 1468 mPackages = packages; 1469 } 1470 1471 public void run() { 1472 final Context context = mApp; 1473 1474 final String[] packages = mPackages; 1475 final int N = packages.length; 1476 switch (mOp) { 1477 case OP_ADD: 1478 for (int i=0; i<N; i++) { 1479 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]); 1480 mAllAppsList.addPackage(context, packages[i]); 1481 } 1482 break; 1483 case OP_UPDATE: 1484 for (int i=0; i<N; i++) { 1485 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]); 1486 mAllAppsList.updatePackage(context, packages[i]); 1487 } 1488 break; 1489 case OP_REMOVE: 1490 case OP_UNAVAILABLE: 1491 for (int i=0; i<N; i++) { 1492 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]); 1493 mAllAppsList.removePackage(packages[i]); 1494 } 1495 break; 1496 } 1497 1498 ArrayList<ApplicationInfo> added = null; 1499 ArrayList<ApplicationInfo> removed = null; 1500 ArrayList<ApplicationInfo> modified = null; 1501 1502 if (mAllAppsList.added.size() > 0) { 1503 added = mAllAppsList.added; 1504 mAllAppsList.added = new ArrayList<ApplicationInfo>(); 1505 } 1506 if (mAllAppsList.removed.size() > 0) { 1507 removed = mAllAppsList.removed; 1508 mAllAppsList.removed = new ArrayList<ApplicationInfo>(); 1509 for (ApplicationInfo info: removed) { 1510 mIconCache.remove(info.intent.getComponent()); 1511 } 1512 } 1513 if (mAllAppsList.modified.size() > 0) { 1514 modified = mAllAppsList.modified; 1515 mAllAppsList.modified = new ArrayList<ApplicationInfo>(); 1516 } 1517 1518 final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null; 1519 if (callbacks == null) { 1520 Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading."); 1521 return; 1522 } 1523 1524 if (added != null) { 1525 final ArrayList<ApplicationInfo> addedFinal = added; 1526 mHandler.post(new Runnable() { 1527 public void run() { 1528 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; 1529 if (callbacks == cb && cb != null) { 1530 callbacks.bindAppsAdded(addedFinal); 1531 } 1532 } 1533 }); 1534 } 1535 if (modified != null) { 1536 final ArrayList<ApplicationInfo> modifiedFinal = modified; 1537 mHandler.post(new Runnable() { 1538 public void run() { 1539 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; 1540 if (callbacks == cb && cb != null) { 1541 callbacks.bindAppsUpdated(modifiedFinal); 1542 } 1543 } 1544 }); 1545 } 1546 if (removed != null) { 1547 final boolean permanent = mOp != OP_UNAVAILABLE; 1548 final ArrayList<ApplicationInfo> removedFinal = removed; 1549 mHandler.post(new Runnable() { 1550 public void run() { 1551 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; 1552 if (callbacks == cb && cb != null) { 1553 callbacks.bindAppsRemoved(removedFinal, permanent); 1554 } 1555 } 1556 }); 1557 } 1558 1559 mHandler.post(new Runnable() { 1560 @Override 1561 public void run() { 1562 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; 1563 if (callbacks == cb && cb != null) { 1564 callbacks.bindPackagesUpdated(); 1565 } 1566 } 1567 }); 1568 } 1569 } 1570 1571 /** 1572 * This is called from the code that adds shortcuts from the intent receiver. This 1573 * doesn't have a Cursor, but 1574 */ 1575 public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context) { 1576 return getShortcutInfo(manager, intent, context, null, -1, -1, null); 1577 } 1578 1579 /** 1580 * Make an ShortcutInfo object for a shortcut that is an application. 1581 * 1582 * If c is not null, then it will be used to fill in missing data like the title and icon. 1583 */ 1584 public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context, 1585 Cursor c, int iconIndex, int titleIndex, HashMap<Object, CharSequence> labelCache) { 1586 Bitmap icon = null; 1587 final ShortcutInfo info = new ShortcutInfo(); 1588 1589 ComponentName componentName = intent.getComponent(); 1590 if (componentName == null) { 1591 return null; 1592 } 1593 1594 // TODO: See if the PackageManager knows about this case. If it doesn't 1595 // then return null & delete this. 1596 1597 // the resource -- This may implicitly give us back the fallback icon, 1598 // but don't worry about that. All we're doing with usingFallbackIcon is 1599 // to avoid saving lots of copies of that in the database, and most apps 1600 // have icons anyway. 1601 final ResolveInfo resolveInfo = manager.resolveActivity(intent, 0); 1602 if (resolveInfo != null) { 1603 icon = mIconCache.getIcon(componentName, resolveInfo, labelCache); 1604 } 1605 // the db 1606 if (icon == null) { 1607 if (c != null) { 1608 icon = getIconFromCursor(c, iconIndex, context); 1609 } 1610 } 1611 // the fallback icon 1612 if (icon == null) { 1613 icon = getFallbackIcon(); 1614 info.usingFallbackIcon = true; 1615 } 1616 info.setIcon(icon); 1617 1618 // from the resource 1619 if (resolveInfo != null) { 1620 ComponentName key = LauncherModel.getComponentNameFromResolveInfo(resolveInfo); 1621 if (labelCache != null && labelCache.containsKey(key)) { 1622 info.title = labelCache.get(key); 1623 } else { 1624 info.title = resolveInfo.activityInfo.loadLabel(manager); 1625 if (labelCache != null) { 1626 labelCache.put(key, info.title); 1627 } 1628 } 1629 } 1630 // from the db 1631 if (info.title == null) { 1632 if (c != null) { 1633 info.title = c.getString(titleIndex); 1634 } 1635 } 1636 // fall back to the class name of the activity 1637 if (info.title == null) { 1638 info.title = componentName.getClassName(); 1639 } 1640 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; 1641 return info; 1642 } 1643 1644 /** 1645 * Make an ShortcutInfo object for a shortcut that isn't an application. 1646 */ 1647 private ShortcutInfo getShortcutInfo(Cursor c, Context context, 1648 int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex, 1649 int titleIndex) { 1650 1651 Bitmap icon = null; 1652 final ShortcutInfo info = new ShortcutInfo(); 1653 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; 1654 1655 // TODO: If there's an explicit component and we can't install that, delete it. 1656 1657 info.title = c.getString(titleIndex); 1658 1659 int iconType = c.getInt(iconTypeIndex); 1660 switch (iconType) { 1661 case LauncherSettings.Favorites.ICON_TYPE_RESOURCE: 1662 String packageName = c.getString(iconPackageIndex); 1663 String resourceName = c.getString(iconResourceIndex); 1664 PackageManager packageManager = context.getPackageManager(); 1665 info.customIcon = false; 1666 // the resource 1667 try { 1668 Resources resources = packageManager.getResourcesForApplication(packageName); 1669 if (resources != null) { 1670 final int id = resources.getIdentifier(resourceName, null, null); 1671 icon = Utilities.createIconBitmap( 1672 mIconCache.getFullResIcon(resources, id), context); 1673 } 1674 } catch (Exception e) { 1675 // drop this. we have other places to look for icons 1676 } 1677 // the db 1678 if (icon == null) { 1679 icon = getIconFromCursor(c, iconIndex, context); 1680 } 1681 // the fallback icon 1682 if (icon == null) { 1683 icon = getFallbackIcon(); 1684 info.usingFallbackIcon = true; 1685 } 1686 break; 1687 case LauncherSettings.Favorites.ICON_TYPE_BITMAP: 1688 icon = getIconFromCursor(c, iconIndex, context); 1689 if (icon == null) { 1690 icon = getFallbackIcon(); 1691 info.customIcon = false; 1692 info.usingFallbackIcon = true; 1693 } else { 1694 info.customIcon = true; 1695 } 1696 break; 1697 default: 1698 icon = getFallbackIcon(); 1699 info.usingFallbackIcon = true; 1700 info.customIcon = false; 1701 break; 1702 } 1703 info.setIcon(icon); 1704 return info; 1705 } 1706 1707 Bitmap getIconFromCursor(Cursor c, int iconIndex, Context context) { 1708 if (false) { 1709 Log.d(TAG, "getIconFromCursor app=" 1710 + c.getString(c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE))); 1711 } 1712 byte[] data = c.getBlob(iconIndex); 1713 try { 1714 return Utilities.createIconBitmap( 1715 BitmapFactory.decodeByteArray(data, 0, data.length), context); 1716 } catch (Exception e) { 1717 return null; 1718 } 1719 } 1720 1721 ShortcutInfo addShortcut(Context context, Intent data, long container, int screen, 1722 int cellX, int cellY, boolean notify) { 1723 final ShortcutInfo info = infoFromShortcutIntent(context, data, null); 1724 addItemToDatabase(context, info, container, screen, cellX, cellY, notify); 1725 1726 return info; 1727 } 1728 1729 /** 1730 * Attempts to find an AppWidgetProviderInfo that matches the given component. 1731 */ 1732 AppWidgetProviderInfo findAppWidgetProviderInfoWithComponent(Context context, 1733 ComponentName component) { 1734 List<AppWidgetProviderInfo> widgets = 1735 AppWidgetManager.getInstance(context).getInstalledProviders(); 1736 for (AppWidgetProviderInfo info : widgets) { 1737 if (info.provider.equals(component)) { 1738 return info; 1739 } 1740 } 1741 return null; 1742 } 1743 1744 /** 1745 * Returns a list of all the widgets that can handle configuration with a particular mimeType. 1746 */ 1747 List<WidgetMimeTypeHandlerData> resolveWidgetsForMimeType(Context context, String mimeType) { 1748 final PackageManager packageManager = context.getPackageManager(); 1749 final List<WidgetMimeTypeHandlerData> supportedConfigurationActivities = 1750 new ArrayList<WidgetMimeTypeHandlerData>(); 1751 1752 final Intent supportsIntent = 1753 new Intent(InstallWidgetReceiver.ACTION_SUPPORTS_CLIPDATA_MIMETYPE); 1754 supportsIntent.setType(mimeType); 1755 1756 // Create a set of widget configuration components that we can test against 1757 final List<AppWidgetProviderInfo> widgets = 1758 AppWidgetManager.getInstance(context).getInstalledProviders(); 1759 final HashMap<ComponentName, AppWidgetProviderInfo> configurationComponentToWidget = 1760 new HashMap<ComponentName, AppWidgetProviderInfo>(); 1761 for (AppWidgetProviderInfo info : widgets) { 1762 configurationComponentToWidget.put(info.configure, info); 1763 } 1764 1765 // Run through each of the intents that can handle this type of clip data, and cross 1766 // reference them with the components that are actual configuration components 1767 final List<ResolveInfo> activities = packageManager.queryIntentActivities(supportsIntent, 1768 PackageManager.MATCH_DEFAULT_ONLY); 1769 for (ResolveInfo info : activities) { 1770 final ActivityInfo activityInfo = info.activityInfo; 1771 final ComponentName infoComponent = new ComponentName(activityInfo.packageName, 1772 activityInfo.name); 1773 if (configurationComponentToWidget.containsKey(infoComponent)) { 1774 supportedConfigurationActivities.add( 1775 new InstallWidgetReceiver.WidgetMimeTypeHandlerData(info, 1776 configurationComponentToWidget.get(infoComponent))); 1777 } 1778 } 1779 return supportedConfigurationActivities; 1780 } 1781 1782 ShortcutInfo infoFromShortcutIntent(Context context, Intent data, Bitmap fallbackIcon) { 1783 Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); 1784 String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); 1785 Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON); 1786 1787 Bitmap icon = null; 1788 boolean customIcon = false; 1789 ShortcutIconResource iconResource = null; 1790 1791 if (bitmap != null && bitmap instanceof Bitmap) { 1792 icon = Utilities.createIconBitmap(new FastBitmapDrawable((Bitmap)bitmap), context); 1793 customIcon = true; 1794 } else { 1795 Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); 1796 if (extra != null && extra instanceof ShortcutIconResource) { 1797 try { 1798 iconResource = (ShortcutIconResource) extra; 1799 final PackageManager packageManager = context.getPackageManager(); 1800 Resources resources = packageManager.getResourcesForApplication( 1801 iconResource.packageName); 1802 final int id = resources.getIdentifier(iconResource.resourceName, null, null); 1803 icon = Utilities.createIconBitmap( 1804 mIconCache.getFullResIcon(resources, id), context); 1805 } catch (Exception e) { 1806 Log.w(TAG, "Could not load shortcut icon: " + extra); 1807 } 1808 } 1809 } 1810 1811 final ShortcutInfo info = new ShortcutInfo(); 1812 1813 if (icon == null) { 1814 if (fallbackIcon != null) { 1815 icon = fallbackIcon; 1816 } else { 1817 icon = getFallbackIcon(); 1818 info.usingFallbackIcon = true; 1819 } 1820 } 1821 info.setIcon(icon); 1822 1823 info.title = name; 1824 info.intent = intent; 1825 info.customIcon = customIcon; 1826 info.iconResource = iconResource; 1827 1828 return info; 1829 } 1830 1831 boolean queueIconToBeChecked(HashMap<Object, byte[]> cache, ShortcutInfo info, Cursor c, 1832 int iconIndex) { 1833 // If apps can't be on SD, don't even bother. 1834 if (!mAppsCanBeOnExternalStorage) { 1835 return false; 1836 } 1837 // If this icon doesn't have a custom icon, check to see 1838 // what's stored in the DB, and if it doesn't match what 1839 // we're going to show, store what we are going to show back 1840 // into the DB. We do this so when we're loading, if the 1841 // package manager can't find an icon (for example because 1842 // the app is on SD) then we can use that instead. 1843 if (!info.customIcon && !info.usingFallbackIcon) { 1844 cache.put(info, c.getBlob(iconIndex)); 1845 return true; 1846 } 1847 return false; 1848 } 1849 void updateSavedIcon(Context context, ShortcutInfo info, byte[] data) { 1850 boolean needSave = false; 1851 try { 1852 if (data != null) { 1853 Bitmap saved = BitmapFactory.decodeByteArray(data, 0, data.length); 1854 Bitmap loaded = info.getIcon(mIconCache); 1855 needSave = !saved.sameAs(loaded); 1856 } else { 1857 needSave = true; 1858 } 1859 } catch (Exception e) { 1860 needSave = true; 1861 } 1862 if (needSave) { 1863 Log.d(TAG, "going to save icon bitmap for info=" + info); 1864 // This is slower than is ideal, but this only happens once 1865 // or when the app is updated with a new icon. 1866 updateItemInDatabase(context, info); 1867 } 1868 } 1869 1870 /** 1871 * Return an existing FolderInfo object if we have encountered this ID previously, 1872 * or make a new one. 1873 */ 1874 private static FolderInfo findOrMakeFolder(HashMap<Long, FolderInfo> folders, long id) { 1875 // See if a placeholder was created for us already 1876 FolderInfo folderInfo = folders.get(id); 1877 if (folderInfo == null) { 1878 // No placeholder -- create a new instance 1879 folderInfo = new FolderInfo(); 1880 folders.put(id, folderInfo); 1881 } 1882 return folderInfo; 1883 } 1884 1885 private static final Collator sCollator = Collator.getInstance(); 1886 public static final Comparator<ApplicationInfo> APP_NAME_COMPARATOR 1887 = new Comparator<ApplicationInfo>() { 1888 public final int compare(ApplicationInfo a, ApplicationInfo b) { 1889 int result = sCollator.compare(a.title.toString(), b.title.toString()); 1890 if (result == 0) { 1891 result = a.componentName.compareTo(b.componentName); 1892 } 1893 return result; 1894 } 1895 }; 1896 public static final Comparator<ApplicationInfo> APP_INSTALL_TIME_COMPARATOR 1897 = new Comparator<ApplicationInfo>() { 1898 public final int compare(ApplicationInfo a, ApplicationInfo b) { 1899 if (a.firstInstallTime < b.firstInstallTime) return 1; 1900 if (a.firstInstallTime > b.firstInstallTime) return -1; 1901 return 0; 1902 } 1903 }; 1904 public static final Comparator<AppWidgetProviderInfo> WIDGET_NAME_COMPARATOR 1905 = new Comparator<AppWidgetProviderInfo>() { 1906 public final int compare(AppWidgetProviderInfo a, AppWidgetProviderInfo b) { 1907 return sCollator.compare(a.label.toString(), b.label.toString()); 1908 } 1909 }; 1910 static ComponentName getComponentNameFromResolveInfo(ResolveInfo info) { 1911 if (info.activityInfo != null) { 1912 return new ComponentName(info.activityInfo.packageName, info.activityInfo.name); 1913 } else { 1914 return new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name); 1915 } 1916 } 1917 public static class ShortcutNameComparator implements Comparator<ResolveInfo> { 1918 private PackageManager mPackageManager; 1919 private HashMap<Object, CharSequence> mLabelCache; 1920 ShortcutNameComparator(PackageManager pm) { 1921 mPackageManager = pm; 1922 mLabelCache = new HashMap<Object, CharSequence>(); 1923 } 1924 ShortcutNameComparator(PackageManager pm, HashMap<Object, CharSequence> labelCache) { 1925 mPackageManager = pm; 1926 mLabelCache = labelCache; 1927 } 1928 public final int compare(ResolveInfo a, ResolveInfo b) { 1929 CharSequence labelA, labelB; 1930 ComponentName keyA = LauncherModel.getComponentNameFromResolveInfo(a); 1931 ComponentName keyB = LauncherModel.getComponentNameFromResolveInfo(b); 1932 if (mLabelCache.containsKey(keyA)) { 1933 labelA = mLabelCache.get(keyA); 1934 } else { 1935 labelA = a.loadLabel(mPackageManager).toString(); 1936 1937 mLabelCache.put(keyA, labelA); 1938 } 1939 if (mLabelCache.containsKey(keyB)) { 1940 labelB = mLabelCache.get(keyB); 1941 } else { 1942 labelB = b.loadLabel(mPackageManager).toString(); 1943 1944 mLabelCache.put(keyB, labelB); 1945 } 1946 return sCollator.compare(labelA, labelB); 1947 } 1948 }; 1949 public static class WidgetAndShortcutNameComparator implements Comparator<Object> { 1950 private PackageManager mPackageManager; 1951 private HashMap<Object, String> mLabelCache; 1952 WidgetAndShortcutNameComparator(PackageManager pm) { 1953 mPackageManager = pm; 1954 mLabelCache = new HashMap<Object, String>(); 1955 } 1956 public final int compare(Object a, Object b) { 1957 String labelA, labelB; 1958 if (mLabelCache.containsKey(a)) { 1959 labelA = mLabelCache.get(a); 1960 } else { 1961 labelA = (a instanceof AppWidgetProviderInfo) ? 1962 ((AppWidgetProviderInfo) a).label : 1963 ((ResolveInfo) a).loadLabel(mPackageManager).toString(); 1964 mLabelCache.put(a, labelA); 1965 } 1966 if (mLabelCache.containsKey(b)) { 1967 labelB = mLabelCache.get(b); 1968 } else { 1969 labelB = (b instanceof AppWidgetProviderInfo) ? 1970 ((AppWidgetProviderInfo) b).label : 1971 ((ResolveInfo) b).loadLabel(mPackageManager).toString(); 1972 mLabelCache.put(b, labelB); 1973 } 1974 return sCollator.compare(labelA, labelB); 1975 } 1976 }; 1977 1978 public void dumpState() { 1979 Log.d(TAG, "mCallbacks=" + mCallbacks); 1980 ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mAllAppsList.data); 1981 ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mAllAppsList.added); 1982 ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mAllAppsList.removed); 1983 ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mAllAppsList.modified); 1984 if (mLoaderTask != null) { 1985 mLoaderTask.dumpState(); 1986 } else { 1987 Log.d(TAG, "mLoaderTask=null"); 1988 } 1989 } 1990} 1991