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 17 package com.android.launcher2; 18 19 import android.app.SearchManager; 20 import android.appwidget.AppWidgetManager; 21 import android.appwidget.AppWidgetProviderInfo; 22 import android.content.BroadcastReceiver; 23 import android.content.ComponentName; 24 import android.content.ContentProviderClient; 25 import android.content.ContentResolver; 26 import android.content.ContentValues; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.Intent.ShortcutIconResource; 30 import android.content.pm.ActivityInfo; 31 import android.content.pm.PackageInfo; 32 import android.content.pm.PackageManager; 33 import android.content.pm.PackageManager.NameNotFoundException; 34 import android.content.pm.ResolveInfo; 35 import android.content.res.Configuration; 36 import android.content.res.Resources; 37 import android.database.Cursor; 38 import android.graphics.Bitmap; 39 import android.graphics.BitmapFactory; 40 import android.net.Uri; 41 import android.os.Environment; 42 import android.os.Handler; 43 import android.os.HandlerThread; 44 import android.os.Parcelable; 45 import android.os.Process; 46 import android.os.RemoteException; 47 import android.os.SystemClock; 48 import android.util.Log; 49 50 import com.android.launcher.R; 51 import com.android.launcher2.InstallWidgetReceiver.WidgetMimeTypeHandlerData; 52 53 import java.lang.ref.WeakReference; 54 import java.net.URISyntaxException; 55 import java.text.Collator; 56 import java.util.ArrayList; 57 import java.util.Collections; 58 import java.util.Comparator; 59 import java.util.HashMap; 60 import java.util.HashSet; 61 import java.util.Iterator; 62 import java.util.List; 63 import java.util.Set; 64 65 /** 66 * Maintains in-memory state of the Launcher. It is expected that there should be only one 67 * LauncherModel object held in a static. Also provide APIs for updating the database state 68 * for the Launcher. 69 */ 70 public class LauncherModel extends BroadcastReceiver { 71 static final boolean DEBUG_LOADERS = false; 72 static final String TAG = "Launcher.Model"; 73 74 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons 75 private final boolean mAppsCanBeOnExternalStorage; 76 private int mBatchSize; // 0 is all apps at once 77 private int mAllAppsLoadDelay; // milliseconds between batches 78 79 private final LauncherApplication mApp; 80 private final Object mLock = new Object(); 81 private DeferredHandler mHandler = new DeferredHandler(); 82 private LoaderTask mLoaderTask; 83 private boolean mIsLoaderTaskRunning; 84 85 private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader"); 86 static { 87 sWorkerThread.start(); 88 } 89 private static final Handler sWorker = new Handler(sWorkerThread.getLooper()); 90 91 // We start off with everything not loaded. After that, we assume that 92 // our monitoring of the package manager provides all updates and we never 93 // need to do a requery. These are only ever touched from the loader thread. 94 private boolean mWorkspaceLoaded; 95 private boolean mAllAppsLoaded; 96 97 // When we are loading pages synchronously, we can't just post the binding of items on the side 98 // pages as this delays the rotation process. Instead, we wait for a callback from the first 99 // draw (in Workspace) to initiate the binding of the remaining side pages. Any time we start 100 // a normal load, we also clear this set of Runnables. 101 static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>(); 102 103 private WeakReference<Callbacks> mCallbacks; 104 105 // < only access in worker thread > 106 private AllAppsList mAllAppsList; 107 108 // The lock that must be acquired before referencing any static bg data structures. Unlike 109 // other locks, this one can generally be held long-term because we never expect any of these 110 // static data structures to be referenced outside of the worker thread except on the first 111 // load after configuration change. 112 static final Object sBgLock = new Object(); 113 114 // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by 115 // LauncherModel to their ids 116 static final HashMap<Long, ItemInfo> sBgItemsIdMap = new HashMap<Long, ItemInfo>(); 117 118 // sBgItems is passed to bindItems, which expects a list of all folders and shortcuts created by 119 // LauncherModel that are directly on the home screen (however, no widgets or shortcuts 120 // within folders). 121 static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>(); 122 123 // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget() 124 static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets = 125 new ArrayList<LauncherAppWidgetInfo>(); 126 127 // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders() 128 static final HashMap<Long, FolderInfo> sBgFolders = new HashMap<Long, FolderInfo>(); 129 130 // sBgDbIconCache is the set of ItemInfos that need to have their icons updated in the database 131 static final HashMap<Object, byte[]> sBgDbIconCache = new HashMap<Object, byte[]>(); 132 133 // </ only access in worker thread > 134 135 private IconCache mIconCache; 136 private Bitmap mDefaultIcon; 137 138 private static int mCellCountX; 139 private static int mCellCountY; 140 141 protected int mPreviousConfigMcc; 142 143 public interface Callbacks { 144 public boolean setLoadOnResume(); 145 public int getCurrentWorkspaceScreen(); 146 public void startBinding(); 147 public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end); 148 public void bindFolders(HashMap<Long,FolderInfo> folders); 149 public void finishBindingItems(); 150 public void bindAppWidget(LauncherAppWidgetInfo info); 151 public void bindAllApplications(ArrayList<ApplicationInfo> apps); 152 public void bindAppsAdded(ArrayList<ApplicationInfo> apps); 153 public void bindAppsUpdated(ArrayList<ApplicationInfo> apps); 154 public void bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent); 155 public void bindPackagesUpdated(); 156 public boolean isAllAppsVisible(); 157 public boolean isAllAppsButtonRank(int rank); 158 public void bindSearchablesChanged(); 159 public void onPageBoundSynchronously(int page); 160 } 161 162 LauncherModel(LauncherApplication app, IconCache iconCache) { 163 mAppsCanBeOnExternalStorage = !Environment.isExternalStorageEmulated(); 164 mApp = app; 165 mAllAppsList = new AllAppsList(iconCache); 166 mIconCache = iconCache; 167 168 mDefaultIcon = Utilities.createIconBitmap( 169 mIconCache.getFullResDefaultActivityIcon(), app); 170 171 final Resources res = app.getResources(); 172 mAllAppsLoadDelay = res.getInteger(R.integer.config_allAppsBatchLoadDelay); 173 mBatchSize = res.getInteger(R.integer.config_allAppsBatchSize); 174 Configuration config = res.getConfiguration(); 175 mPreviousConfigMcc = config.mcc; 176 } 177 178 /** Runs the specified runnable immediately if called from the main thread, otherwise it is 179 * posted on the main thread handler. */ 180 private void runOnMainThread(Runnable r) { 181 if (sWorkerThread.getThreadId() == Process.myTid()) { 182 // If we are on the worker thread, post onto the main handler 183 mHandler.post(r); 184 } else { 185 r.run(); 186 } 187 } 188 189 /** Runs the specified runnable immediately if called from the worker thread, otherwise it is 190 * posted on the worker thread handler. */ 191 private static void runOnWorkerThread(Runnable r) { 192 if (sWorkerThread.getThreadId() == Process.myTid()) { 193 r.run(); 194 } else { 195 // If we are not on the worker thread, then post to the worker handler 196 sWorker.post(r); 197 } 198 } 199 200 public Bitmap getFallbackIcon() { 201 return Bitmap.createBitmap(mDefaultIcon); 202 } 203 204 public void unbindWorkspaceItems() { 205 sWorker.post(new Runnable() { 206 @Override 207 public void run() { 208 unbindWorkspaceItemsOnMainThread(); 209 } 210 }); 211 } 212 213 /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */ 214 private void unbindWorkspaceItemsOnMainThread() { 215 // Ensure that we don't use the same workspace items data structure on the main thread 216 // by making a copy of workspace items first. 217 final ArrayList<ItemInfo> tmpWorkspaceItems = new ArrayList<ItemInfo>(); 218 final ArrayList<ItemInfo> tmpAppWidgets = new ArrayList<ItemInfo>(); 219 synchronized (sBgLock) { 220 tmpWorkspaceItems.addAll(sBgWorkspaceItems); 221 tmpAppWidgets.addAll(sBgAppWidgets); 222 } 223 Runnable r = new Runnable() { 224 @Override 225 public void run() { 226 for (ItemInfo item : tmpWorkspaceItems) { 227 item.unbind(); 228 } 229 for (ItemInfo item : tmpAppWidgets) { 230 item.unbind(); 231 } 232 } 233 }; 234 runOnMainThread(r); 235 } 236 237 /** 238 * Adds an item to the DB if it was not created previously, or move it to a new 239 * <container, screen, cellX, cellY> 240 */ 241 static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container, 242 int screen, int cellX, int cellY) { 243 if (item.container == ItemInfo.NO_ID) { 244 // From all apps 245 addItemToDatabase(context, item, container, screen, cellX, cellY, false); 246 } else { 247 // From somewhere else 248 moveItemInDatabase(context, item, container, screen, cellX, cellY); 249 } 250 } 251 252 static void updateItemInDatabaseHelper(Context context, final ContentValues values, 253 final ItemInfo item, final String callingFunction) { 254 final long itemId = item.id; 255 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false); 256 final ContentResolver cr = context.getContentResolver(); 257 258 Runnable r = new Runnable() { 259 public void run() { 260 cr.update(uri, values, null, null); 261 262 // Lock on mBgLock *after* the db operation 263 synchronized (sBgLock) { 264 ItemInfo modelItem = sBgItemsIdMap.get(itemId); 265 if (item != modelItem) { 266 // the modelItem needs to match up perfectly with item if our model is to be 267 // consistent with the database-- for now, just require modelItem == item 268 String msg = "item: " + ((item != null) ? item.toString() : "null") + 269 "modelItem: " + ((modelItem != null) ? modelItem.toString() : "null") + 270 "Error: ItemInfo passed to " + callingFunction + " doesn't match " + 271 "original"; 272 throw new RuntimeException(msg); 273 } 274 275 // Items are added/removed from the corresponding FolderInfo elsewhere, such 276 // as in Workspace.onDrop. Here, we just add/remove them from the list of items 277 // that are on the desktop, as appropriate 278 if (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || 279 modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 280 if (!sBgWorkspaceItems.contains(modelItem)) { 281 sBgWorkspaceItems.add(modelItem); 282 283 } 284 } else { 285 sBgWorkspaceItems.remove(modelItem); 286 } 287 } 288 } 289 }; 290 runOnWorkerThread(r); 291 } 292 293 /** 294 * Move an item in the DB to a new <container, screen, cellX, cellY> 295 */ 296 static void moveItemInDatabase(Context context, final ItemInfo item, final long container, 297 final int screen, final int cellX, final int cellY) { 298 item.container = container; 299 item.cellX = cellX; 300 item.cellY = cellY; 301 302 // We store hotseat items in canonical form which is this orientation invariant position 303 // in the hotseat 304 if (context instanceof Launcher && screen < 0 && 305 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 306 item.screen = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY); 307 } else { 308 item.screen = screen; 309 } 310 311 final ContentValues values = new ContentValues(); 312 values.put(LauncherSettings.Favorites.CONTAINER, item.container); 313 values.put(LauncherSettings.Favorites.CELLX, item.cellX); 314 values.put(LauncherSettings.Favorites.CELLY, item.cellY); 315 values.put(LauncherSettings.Favorites.SCREEN, item.screen); 316 317 updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase"); 318 } 319 320 /** 321 * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY> 322 */ 323 static void modifyItemInDatabase(Context context, final ItemInfo item, final long container, 324 final int screen, final int cellX, final int cellY, final int spanX, final int spanY) { 325 item.container = container; 326 item.cellX = cellX; 327 item.cellY = cellY; 328 item.spanX = spanX; 329 item.spanY = spanY; 330 331 // We store hotseat items in canonical form which is this orientation invariant position 332 // in the hotseat 333 if (context instanceof Launcher && screen < 0 && 334 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 335 item.screen = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY); 336 } else { 337 item.screen = screen; 338 } 339 340 final ContentValues values = new ContentValues(); 341 values.put(LauncherSettings.Favorites.CONTAINER, item.container); 342 values.put(LauncherSettings.Favorites.CELLX, item.cellX); 343 values.put(LauncherSettings.Favorites.CELLY, item.cellY); 344 values.put(LauncherSettings.Favorites.SPANX, item.spanX); 345 values.put(LauncherSettings.Favorites.SPANY, item.spanY); 346 values.put(LauncherSettings.Favorites.SCREEN, item.screen); 347 348 updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase"); 349 } 350 351 /** 352 * Update an item to the database in a specified container. 353 */ 354 static void updateItemInDatabase(Context context, final ItemInfo item) { 355 final ContentValues values = new ContentValues(); 356 item.onAddToDatabase(values); 357 item.updateValuesWithCoordinates(values, item.cellX, item.cellY); 358 updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase"); 359 } 360 361 /** 362 * Returns true if the shortcuts already exists in the database. 363 * we identify a shortcut by its title and intent. 364 */ 365 static boolean shortcutExists(Context context, String title, Intent intent) { 366 final ContentResolver cr = context.getContentResolver(); 367 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, 368 new String[] { "title", "intent" }, "title=? and intent=?", 369 new String[] { title, intent.toUri(0) }, null); 370 boolean result = false; 371 try { 372 result = c.moveToFirst(); 373 } finally { 374 c.close(); 375 } 376 return result; 377 } 378 379 /** 380 * Returns an ItemInfo array containing all the items in the LauncherModel. 381 * The ItemInfo.id is not set through this function. 382 */ 383 static ArrayList<ItemInfo> getItemsInLocalCoordinates(Context context) { 384 ArrayList<ItemInfo> items = new ArrayList<ItemInfo>(); 385 final ContentResolver cr = context.getContentResolver(); 386 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, new String[] { 387 LauncherSettings.Favorites.ITEM_TYPE, LauncherSettings.Favorites.CONTAINER, 388 LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.CELLX, LauncherSettings.Favorites.CELLY, 389 LauncherSettings.Favorites.SPANX, LauncherSettings.Favorites.SPANY }, null, null, null); 390 391 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); 392 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); 393 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); 394 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); 395 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); 396 final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX); 397 final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY); 398 399 try { 400 while (c.moveToNext()) { 401 ItemInfo item = new ItemInfo(); 402 item.cellX = c.getInt(cellXIndex); 403 item.cellY = c.getInt(cellYIndex); 404 item.spanX = c.getInt(spanXIndex); 405 item.spanY = c.getInt(spanYIndex); 406 item.container = c.getInt(containerIndex); 407 item.itemType = c.getInt(itemTypeIndex); 408 item.screen = c.getInt(screenIndex); 409 410 items.add(item); 411 } 412 } catch (Exception e) { 413 items.clear(); 414 } finally { 415 c.close(); 416 } 417 418 return items; 419 } 420 421 /** 422 * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList. 423 */ 424 FolderInfo getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id) { 425 final ContentResolver cr = context.getContentResolver(); 426 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null, 427 "_id=? and (itemType=? or itemType=?)", 428 new String[] { String.valueOf(id), 429 String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)}, null); 430 431 try { 432 if (c.moveToFirst()) { 433 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); 434 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); 435 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); 436 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); 437 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); 438 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); 439 440 FolderInfo folderInfo = null; 441 switch (c.getInt(itemTypeIndex)) { 442 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 443 folderInfo = findOrMakeFolder(folderList, id); 444 break; 445 } 446 447 folderInfo.title = c.getString(titleIndex); 448 folderInfo.id = id; 449 folderInfo.container = c.getInt(containerIndex); 450 folderInfo.screen = c.getInt(screenIndex); 451 folderInfo.cellX = c.getInt(cellXIndex); 452 folderInfo.cellY = c.getInt(cellYIndex); 453 454 return folderInfo; 455 } 456 } finally { 457 c.close(); 458 } 459 460 return null; 461 } 462 463 /** 464 * Add an item to the database in a specified container. Sets the container, screen, cellX and 465 * cellY fields of the item. Also assigns an ID to the item. 466 */ 467 static void addItemToDatabase(Context context, final ItemInfo item, final long container, 468 final int screen, final int cellX, final int cellY, final boolean notify) { 469 item.container = container; 470 item.cellX = cellX; 471 item.cellY = cellY; 472 // We store hotseat items in canonical form which is this orientation invariant position 473 // in the hotseat 474 if (context instanceof Launcher && screen < 0 && 475 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 476 item.screen = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY); 477 } else { 478 item.screen = screen; 479 } 480 481 final ContentValues values = new ContentValues(); 482 final ContentResolver cr = context.getContentResolver(); 483 item.onAddToDatabase(values); 484 485 LauncherApplication app = (LauncherApplication) context.getApplicationContext(); 486 item.id = app.getLauncherProvider().generateNewId(); 487 values.put(LauncherSettings.Favorites._ID, item.id); 488 item.updateValuesWithCoordinates(values, item.cellX, item.cellY); 489 490 Runnable r = new Runnable() { 491 public void run() { 492 cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI : 493 LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values); 494 // Lock on mBgLock *after* the db operation 495 synchronized (sBgLock) { 496 if (sBgItemsIdMap.containsKey(item.id)) { 497 // we should not be adding new items in the db with the same id 498 throw new RuntimeException("Error: ItemInfo id (" + item.id + ") passed to " + 499 "addItemToDatabase already exists." + item.toString()); 500 } 501 sBgItemsIdMap.put(item.id, item); 502 switch (item.itemType) { 503 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 504 sBgFolders.put(item.id, (FolderInfo) item); 505 // Fall through 506 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 507 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 508 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || 509 item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 510 sBgWorkspaceItems.add(item); 511 } 512 break; 513 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 514 sBgAppWidgets.add((LauncherAppWidgetInfo) item); 515 break; 516 } 517 } 518 } 519 }; 520 runOnWorkerThread(r); 521 } 522 523 /** 524 * Creates a new unique child id, for a given cell span across all layouts. 525 */ 526 static int getCellLayoutChildId( 527 long container, int screen, int localCellX, int localCellY, int spanX, int spanY) { 528 return (((int) container & 0xFF) << 24) 529 | (screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF); 530 } 531 532 static int getCellCountX() { 533 return mCellCountX; 534 } 535 536 static int getCellCountY() { 537 return mCellCountY; 538 } 539 540 /** 541 * Updates the model orientation helper to take into account the current layout dimensions 542 * when performing local/canonical coordinate transformations. 543 */ 544 static void updateWorkspaceLayoutCells(int shortAxisCellCount, int longAxisCellCount) { 545 mCellCountX = shortAxisCellCount; 546 mCellCountY = longAxisCellCount; 547 } 548 549 /** 550 * Removes the specified item from the database 551 * @param context 552 * @param item 553 */ 554 static void deleteItemFromDatabase(Context context, final ItemInfo item) { 555 final ContentResolver cr = context.getContentResolver(); 556 final Uri uriToDelete = LauncherSettings.Favorites.getContentUri(item.id, false); 557 Runnable r = new Runnable() { 558 public void run() { 559 cr.delete(uriToDelete, null, null); 560 // Lock on mBgLock *after* the db operation 561 synchronized (sBgLock) { 562 switch (item.itemType) { 563 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 564 sBgFolders.remove(item.id); 565 sBgWorkspaceItems.remove(item); 566 break; 567 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 568 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 569 sBgWorkspaceItems.remove(item); 570 break; 571 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 572 sBgAppWidgets.remove((LauncherAppWidgetInfo) item); 573 break; 574 } 575 sBgItemsIdMap.remove(item.id); 576 sBgDbIconCache.remove(item); 577 } 578 } 579 }; 580 runOnWorkerThread(r); 581 } 582 583 /** 584 * Remove the contents of the specified folder from the database 585 */ 586 static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) { 587 final ContentResolver cr = context.getContentResolver(); 588 589 Runnable r = new Runnable() { 590 public void run() { 591 cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null); 592 // Lock on mBgLock *after* the db operation 593 synchronized (sBgLock) { 594 sBgItemsIdMap.remove(info.id); 595 sBgFolders.remove(info.id); 596 sBgDbIconCache.remove(info); 597 sBgWorkspaceItems.remove(info); 598 } 599 600 cr.delete(LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, 601 LauncherSettings.Favorites.CONTAINER + "=" + info.id, null); 602 // Lock on mBgLock *after* the db operation 603 synchronized (sBgLock) { 604 for (ItemInfo childInfo : info.contents) { 605 sBgItemsIdMap.remove(childInfo.id); 606 sBgDbIconCache.remove(childInfo); 607 } 608 } 609 } 610 }; 611 runOnWorkerThread(r); 612 } 613 614 /** 615 * Set this as the current Launcher activity object for the loader. 616 */ 617 public void initialize(Callbacks callbacks) { 618 synchronized (mLock) { 619 mCallbacks = new WeakReference<Callbacks>(callbacks); 620 } 621 } 622 623 /** 624 * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and 625 * ACTION_PACKAGE_CHANGED. 626 */ 627 @Override 628 public void onReceive(Context context, Intent intent) { 629 if (DEBUG_LOADERS) Log.d(TAG, "onReceive intent=" + intent); 630 631 final String action = intent.getAction(); 632 633 if (Intent.ACTION_PACKAGE_CHANGED.equals(action) 634 || Intent.ACTION_PACKAGE_REMOVED.equals(action) 635 || Intent.ACTION_PACKAGE_ADDED.equals(action)) { 636 final String packageName = intent.getData().getSchemeSpecificPart(); 637 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); 638 639 int op = PackageUpdatedTask.OP_NONE; 640 641 if (packageName == null || packageName.length() == 0) { 642 // they sent us a bad intent 643 return; 644 } 645 646 if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) { 647 op = PackageUpdatedTask.OP_UPDATE; 648 } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { 649 if (!replacing) { 650 op = PackageUpdatedTask.OP_REMOVE; 651 } 652 // else, we are replacing the package, so a PACKAGE_ADDED will be sent 653 // later, we will update the package at this time 654 } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { 655 if (!replacing) { 656 op = PackageUpdatedTask.OP_ADD; 657 } else { 658 op = PackageUpdatedTask.OP_UPDATE; 659 } 660 } 661 662 if (op != PackageUpdatedTask.OP_NONE) { 663 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName })); 664 } 665 666 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { 667 // First, schedule to add these apps back in. 668 String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); 669 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packages)); 670 // Then, rebind everything. 671 startLoaderFromBackground(); 672 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { 673 String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); 674 enqueuePackageUpdated(new PackageUpdatedTask( 675 PackageUpdatedTask.OP_UNAVAILABLE, packages)); 676 } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) { 677 // If we have changed locale we need to clear out the labels in all apps/workspace. 678 forceReload(); 679 } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { 680 // Check if configuration change was an mcc/mnc change which would affect app resources 681 // and we would need to clear out the labels in all apps/workspace. Same handling as 682 // above for ACTION_LOCALE_CHANGED 683 Configuration currentConfig = context.getResources().getConfiguration(); 684 if (mPreviousConfigMcc != currentConfig.mcc) { 685 Log.d(TAG, "Reload apps on config change. curr_mcc:" 686 + currentConfig.mcc + " prevmcc:" + mPreviousConfigMcc); 687 forceReload(); 688 } 689 // Update previousConfig 690 mPreviousConfigMcc = currentConfig.mcc; 691 } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) || 692 SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) { 693 if (mCallbacks != null) { 694 Callbacks callbacks = mCallbacks.get(); 695 if (callbacks != null) { 696 callbacks.bindSearchablesChanged(); 697 } 698 } 699 } 700 } 701 702 private void forceReload() { 703 resetLoadedState(true, true); 704 705 // Do this here because if the launcher activity is running it will be restarted. 706 // If it's not running startLoaderFromBackground will merely tell it that it needs 707 // to reload. 708 startLoaderFromBackground(); 709 } 710 711 public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) { 712 synchronized (mLock) { 713 // Stop any existing loaders first, so they don't set mAllAppsLoaded or 714 // mWorkspaceLoaded to true later 715 stopLoaderLocked(); 716 if (resetAllAppsLoaded) mAllAppsLoaded = false; 717 if (resetWorkspaceLoaded) mWorkspaceLoaded = false; 718 } 719 } 720 721 /** 722 * When the launcher is in the background, it's possible for it to miss paired 723 * configuration changes. So whenever we trigger the loader from the background 724 * tell the launcher that it needs to re-run the loader when it comes back instead 725 * of doing it now. 726 */ 727 public void startLoaderFromBackground() { 728 boolean runLoader = false; 729 if (mCallbacks != null) { 730 Callbacks callbacks = mCallbacks.get(); 731 if (callbacks != null) { 732 // Only actually run the loader if they're not paused. 733 if (!callbacks.setLoadOnResume()) { 734 runLoader = true; 735 } 736 } 737 } 738 if (runLoader) { 739 startLoader(false, -1); 740 } 741 } 742 743 // If there is already a loader task running, tell it to stop. 744 // returns true if isLaunching() was true on the old task 745 private boolean stopLoaderLocked() { 746 boolean isLaunching = false; 747 LoaderTask oldTask = mLoaderTask; 748 if (oldTask != null) { 749 if (oldTask.isLaunching()) { 750 isLaunching = true; 751 } 752 oldTask.stopLocked(); 753 } 754 return isLaunching; 755 } 756 757 public void startLoader(boolean isLaunching, int synchronousBindPage) { 758 synchronized (mLock) { 759 if (DEBUG_LOADERS) { 760 Log.d(TAG, "startLoader isLaunching=" + isLaunching); 761 } 762 763 // Clear any deferred bind-runnables from the synchronized load process 764 // We must do this before any loading/binding is scheduled below. 765 mDeferredBindRunnables.clear(); 766 767 // Don't bother to start the thread if we know it's not going to do anything 768 if (mCallbacks != null && mCallbacks.get() != null) { 769 // If there is already one running, tell it to stop. 770 // also, don't downgrade isLaunching if we're already running 771 isLaunching = isLaunching || stopLoaderLocked(); 772 mLoaderTask = new LoaderTask(mApp, isLaunching); 773 if (synchronousBindPage > -1 && mAllAppsLoaded && mWorkspaceLoaded) { 774 mLoaderTask.runBindSynchronousPage(synchronousBindPage); 775 } else { 776 sWorkerThread.setPriority(Thread.NORM_PRIORITY); 777 sWorker.post(mLoaderTask); 778 } 779 } 780 } 781 } 782 783 void bindRemainingSynchronousPages() { 784 // Post the remaining side pages to be loaded 785 if (!mDeferredBindRunnables.isEmpty()) { 786 for (final Runnable r : mDeferredBindRunnables) { 787 mHandler.post(r); 788 } 789 mDeferredBindRunnables.clear(); 790 } 791 } 792 793 public void stopLoader() { 794 synchronized (mLock) { 795 if (mLoaderTask != null) { 796 mLoaderTask.stopLocked(); 797 } 798 } 799 } 800 801 public boolean isAllAppsLoaded() { 802 return mAllAppsLoaded; 803 } 804 805 boolean isLoadingWorkspace() { 806 synchronized (mLock) { 807 if (mLoaderTask != null) { 808 return mLoaderTask.isLoadingWorkspace(); 809 } 810 } 811 return false; 812 } 813 814 /** 815 * Runnable for the thread that loads the contents of the launcher: 816 * - workspace icons 817 * - widgets 818 * - all apps icons 819 */ 820 private class LoaderTask implements Runnable { 821 private Context mContext; 822 private boolean mIsLaunching; 823 private boolean mIsLoadingAndBindingWorkspace; 824 private boolean mStopped; 825 private boolean mLoadAndBindStepFinished; 826 827 private HashMap<Object, CharSequence> mLabelCache; 828 829 LoaderTask(Context context, boolean isLaunching) { 830 mContext = context; 831 mIsLaunching = isLaunching; 832 mLabelCache = new HashMap<Object, CharSequence>(); 833 } 834 835 boolean isLaunching() { 836 return mIsLaunching; 837 } 838 839 boolean isLoadingWorkspace() { 840 return mIsLoadingAndBindingWorkspace; 841 } 842 843 private void loadAndBindWorkspace() { 844 mIsLoadingAndBindingWorkspace = true; 845 846 // Load the workspace 847 if (DEBUG_LOADERS) { 848 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded); 849 } 850 851 if (!mWorkspaceLoaded) { 852 loadWorkspace(); 853 synchronized (LoaderTask.this) { 854 if (mStopped) { 855 return; 856 } 857 mWorkspaceLoaded = true; 858 } 859 } 860 861 // Bind the workspace 862 bindWorkspace(-1); 863 } 864 865 private void waitForIdle() { 866 // Wait until the either we're stopped or the other threads are done. 867 // This way we don't start loading all apps until the workspace has settled 868 // down. 869 synchronized (LoaderTask.this) { 870 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 871 872 mHandler.postIdle(new Runnable() { 873 public void run() { 874 synchronized (LoaderTask.this) { 875 mLoadAndBindStepFinished = true; 876 if (DEBUG_LOADERS) { 877 Log.d(TAG, "done with previous binding step"); 878 } 879 LoaderTask.this.notify(); 880 } 881 } 882 }); 883 884 while (!mStopped && !mLoadAndBindStepFinished) { 885 try { 886 this.wait(); 887 } catch (InterruptedException ex) { 888 // Ignore 889 } 890 } 891 if (DEBUG_LOADERS) { 892 Log.d(TAG, "waited " 893 + (SystemClock.uptimeMillis()-workspaceWaitTime) 894 + "ms for previous step to finish binding"); 895 } 896 } 897 } 898 899 void runBindSynchronousPage(int synchronousBindPage) { 900 if (synchronousBindPage < 0) { 901 // Ensure that we have a valid page index to load synchronously 902 throw new RuntimeException("Should not call runBindSynchronousPage() without " + 903 "valid page index"); 904 } 905 if (!mAllAppsLoaded || !mWorkspaceLoaded) { 906 // Ensure that we don't try and bind a specified page when the pages have not been 907 // loaded already (we should load everything asynchronously in that case) 908 throw new RuntimeException("Expecting AllApps and Workspace to be loaded"); 909 } 910 synchronized (mLock) { 911 if (mIsLoaderTaskRunning) { 912 // Ensure that we are never running the background loading at this point since 913 // we also touch the background collections 914 throw new RuntimeException("Error! Background loading is already running"); 915 } 916 } 917 918 // XXX: Throw an exception if we are already loading (since we touch the worker thread 919 // data structures, we can't allow any other thread to touch that data, but because 920 // this call is synchronous, we can get away with not locking). 921 922 // The LauncherModel is static in the LauncherApplication and mHandler may have queued 923 // operations from the previous activity. We need to ensure that all queued operations 924 // are executed before any synchronous binding work is done. 925 mHandler.flush(); 926 927 // Divide the set of loaded items into those that we are binding synchronously, and 928 // everything else that is to be bound normally (asynchronously). 929 bindWorkspace(synchronousBindPage); 930 // XXX: For now, continue posting the binding of AllApps as there are other issues that 931 // arise from that. 932 onlyBindAllApps(); 933 } 934 935 public void run() { 936 synchronized (mLock) { 937 mIsLoaderTaskRunning = true; 938 } 939 // Optimize for end-user experience: if the Launcher is up and // running with the 940 // All Apps interface in the foreground, load All Apps first. Otherwise, load the 941 // workspace first (default). 942 final Callbacks cbk = mCallbacks.get(); 943 final boolean loadWorkspaceFirst = cbk != null ? (!cbk.isAllAppsVisible()) : true; 944 945 keep_running: { 946 // Elevate priority when Home launches for the first time to avoid 947 // starving at boot time. Staring at a blank home is not cool. 948 synchronized (mLock) { 949 if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " + 950 (mIsLaunching ? "DEFAULT" : "BACKGROUND")); 951 android.os.Process.setThreadPriority(mIsLaunching 952 ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND); 953 } 954 if (loadWorkspaceFirst) { 955 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace"); 956 loadAndBindWorkspace(); 957 } else { 958 if (DEBUG_LOADERS) Log.d(TAG, "step 1: special: loading all apps"); 959 loadAndBindAllApps(); 960 } 961 962 if (mStopped) { 963 break keep_running; 964 } 965 966 // Whew! Hard work done. Slow us down, and wait until the UI thread has 967 // settled down. 968 synchronized (mLock) { 969 if (mIsLaunching) { 970 if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND"); 971 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 972 } 973 } 974 waitForIdle(); 975 976 // second step 977 if (loadWorkspaceFirst) { 978 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps"); 979 loadAndBindAllApps(); 980 } else { 981 if (DEBUG_LOADERS) Log.d(TAG, "step 2: special: loading workspace"); 982 loadAndBindWorkspace(); 983 } 984 985 // Restore the default thread priority after we are done loading items 986 synchronized (mLock) { 987 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); 988 } 989 } 990 991 992 // Update the saved icons if necessary 993 if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons"); 994 synchronized (sBgLock) { 995 for (Object key : sBgDbIconCache.keySet()) { 996 updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key)); 997 } 998 sBgDbIconCache.clear(); 999 } 1000 1001 // Clear out this reference, otherwise we end up holding it until all of the 1002 // callback runnables are done. 1003 mContext = null; 1004 1005 synchronized (mLock) { 1006 // If we are still the last one to be scheduled, remove ourselves. 1007 if (mLoaderTask == this) { 1008 mLoaderTask = null; 1009 } 1010 mIsLoaderTaskRunning = false; 1011 } 1012 } 1013 1014 public void stopLocked() { 1015 synchronized (LoaderTask.this) { 1016 mStopped = true; 1017 this.notify(); 1018 } 1019 } 1020 1021 /** 1022 * Gets the callbacks object. If we've been stopped, or if the launcher object 1023 * has somehow been garbage collected, return null instead. Pass in the Callbacks 1024 * object that was around when the deferred message was scheduled, and if there's 1025 * a new Callbacks object around then also return null. This will save us from 1026 * calling onto it with data that will be ignored. 1027 */ 1028 Callbacks tryGetCallbacks(Callbacks oldCallbacks) { 1029 synchronized (mLock) { 1030 if (mStopped) { 1031 return null; 1032 } 1033 1034 if (mCallbacks == null) { 1035 return null; 1036 } 1037 1038 final Callbacks callbacks = mCallbacks.get(); 1039 if (callbacks != oldCallbacks) { 1040 return null; 1041 } 1042 if (callbacks == null) { 1043 Log.w(TAG, "no mCallbacks"); 1044 return null; 1045 } 1046 1047 return callbacks; 1048 } 1049 } 1050 1051 // check & update map of what's occupied; used to discard overlapping/invalid items 1052 private boolean checkItemPlacement(ItemInfo occupied[][][], ItemInfo item) { 1053 int containerIndex = item.screen; 1054 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 1055 // Return early if we detect that an item is under the hotseat button 1056 if (mCallbacks == null || mCallbacks.get().isAllAppsButtonRank(item.screen)) { 1057 return false; 1058 } 1059 1060 // We use the last index to refer to the hotseat and the screen as the rank, so 1061 // test and update the occupied state accordingly 1062 if (occupied[Launcher.SCREEN_COUNT][item.screen][0] != null) { 1063 Log.e(TAG, "Error loading shortcut into hotseat " + item 1064 + " into position (" + item.screen + ":" + item.cellX + "," + item.cellY 1065 + ") occupied by " + occupied[Launcher.SCREEN_COUNT][item.screen][0]); 1066 return false; 1067 } else { 1068 occupied[Launcher.SCREEN_COUNT][item.screen][0] = item; 1069 return true; 1070 } 1071 } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { 1072 // Skip further checking if it is not the hotseat or workspace container 1073 return true; 1074 } 1075 1076 // Check if any workspace icons overlap with each other 1077 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) { 1078 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) { 1079 if (occupied[containerIndex][x][y] != null) { 1080 Log.e(TAG, "Error loading shortcut " + item 1081 + " into cell (" + containerIndex + "-" + item.screen + ":" 1082 + x + "," + y 1083 + ") occupied by " 1084 + occupied[containerIndex][x][y]); 1085 return false; 1086 } 1087 } 1088 } 1089 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) { 1090 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) { 1091 occupied[containerIndex][x][y] = item; 1092 } 1093 } 1094 1095 return true; 1096 } 1097 1098 private void loadWorkspace() { 1099 final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 1100 1101 final Context context = mContext; 1102 final ContentResolver contentResolver = context.getContentResolver(); 1103 final PackageManager manager = context.getPackageManager(); 1104 final AppWidgetManager widgets = AppWidgetManager.getInstance(context); 1105 final boolean isSafeMode = manager.isSafeMode(); 1106 1107 // Make sure the default workspace is loaded, if needed 1108 mApp.getLauncherProvider().loadDefaultFavoritesIfNecessary(); 1109 1110 synchronized (sBgLock) { 1111 sBgWorkspaceItems.clear(); 1112 sBgAppWidgets.clear(); 1113 sBgFolders.clear(); 1114 sBgItemsIdMap.clear(); 1115 sBgDbIconCache.clear(); 1116 1117 final ArrayList<Long> itemsToRemove = new ArrayList<Long>(); 1118 1119 final Cursor c = contentResolver.query( 1120 LauncherSettings.Favorites.CONTENT_URI, null, null, null, null); 1121 1122 // +1 for the hotseat (it can be larger than the workspace) 1123 // Load workspace in reverse order to ensure that latest items are loaded first (and 1124 // before any earlier duplicates) 1125 final ItemInfo occupied[][][] = 1126 new ItemInfo[Launcher.SCREEN_COUNT + 1][mCellCountX + 1][mCellCountY + 1]; 1127 1128 try { 1129 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); 1130 final int intentIndex = c.getColumnIndexOrThrow 1131 (LauncherSettings.Favorites.INTENT); 1132 final int titleIndex = c.getColumnIndexOrThrow 1133 (LauncherSettings.Favorites.TITLE); 1134 final int iconTypeIndex = c.getColumnIndexOrThrow( 1135 LauncherSettings.Favorites.ICON_TYPE); 1136 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); 1137 final int iconPackageIndex = c.getColumnIndexOrThrow( 1138 LauncherSettings.Favorites.ICON_PACKAGE); 1139 final int iconResourceIndex = c.getColumnIndexOrThrow( 1140 LauncherSettings.Favorites.ICON_RESOURCE); 1141 final int containerIndex = c.getColumnIndexOrThrow( 1142 LauncherSettings.Favorites.CONTAINER); 1143 final int itemTypeIndex = c.getColumnIndexOrThrow( 1144 LauncherSettings.Favorites.ITEM_TYPE); 1145 final int appWidgetIdIndex = c.getColumnIndexOrThrow( 1146 LauncherSettings.Favorites.APPWIDGET_ID); 1147 final int screenIndex = c.getColumnIndexOrThrow( 1148 LauncherSettings.Favorites.SCREEN); 1149 final int cellXIndex = c.getColumnIndexOrThrow 1150 (LauncherSettings.Favorites.CELLX); 1151 final int cellYIndex = c.getColumnIndexOrThrow 1152 (LauncherSettings.Favorites.CELLY); 1153 final int spanXIndex = c.getColumnIndexOrThrow 1154 (LauncherSettings.Favorites.SPANX); 1155 final int spanYIndex = c.getColumnIndexOrThrow( 1156 LauncherSettings.Favorites.SPANY); 1157 //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); 1158 //final int displayModeIndex = c.getColumnIndexOrThrow( 1159 // LauncherSettings.Favorites.DISPLAY_MODE); 1160 1161 ShortcutInfo info; 1162 String intentDescription; 1163 LauncherAppWidgetInfo appWidgetInfo; 1164 int container; 1165 long id; 1166 Intent intent; 1167 1168 while (!mStopped && c.moveToNext()) { 1169 try { 1170 int itemType = c.getInt(itemTypeIndex); 1171 1172 switch (itemType) { 1173 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 1174 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 1175 intentDescription = c.getString(intentIndex); 1176 try { 1177 intent = Intent.parseUri(intentDescription, 0); 1178 } catch (URISyntaxException e) { 1179 continue; 1180 } 1181 1182 if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { 1183 info = getShortcutInfo(manager, intent, context, c, iconIndex, 1184 titleIndex, mLabelCache); 1185 } else { 1186 info = getShortcutInfo(c, context, iconTypeIndex, 1187 iconPackageIndex, iconResourceIndex, iconIndex, 1188 titleIndex); 1189 1190 // App shortcuts that used to be automatically added to Launcher 1191 // didn't always have the correct intent flags set, so do that 1192 // here 1193 if (intent.getAction() != null && 1194 intent.getCategories() != null && 1195 intent.getAction().equals(Intent.ACTION_MAIN) && 1196 intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { 1197 intent.addFlags( 1198 Intent.FLAG_ACTIVITY_NEW_TASK | 1199 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 1200 } 1201 } 1202 1203 if (info != null) { 1204 info.intent = intent; 1205 info.id = c.getLong(idIndex); 1206 container = c.getInt(containerIndex); 1207 info.container = container; 1208 info.screen = c.getInt(screenIndex); 1209 info.cellX = c.getInt(cellXIndex); 1210 info.cellY = c.getInt(cellYIndex); 1211 1212 // check & update map of what's occupied 1213 if (!checkItemPlacement(occupied, info)) { 1214 break; 1215 } 1216 1217 switch (container) { 1218 case LauncherSettings.Favorites.CONTAINER_DESKTOP: 1219 case LauncherSettings.Favorites.CONTAINER_HOTSEAT: 1220 sBgWorkspaceItems.add(info); 1221 break; 1222 default: 1223 // Item is in a user folder 1224 FolderInfo folderInfo = 1225 findOrMakeFolder(sBgFolders, container); 1226 folderInfo.add(info); 1227 break; 1228 } 1229 sBgItemsIdMap.put(info.id, info); 1230 1231 // now that we've loaded everthing re-save it with the 1232 // icon in case it disappears somehow. 1233 queueIconToBeChecked(sBgDbIconCache, info, c, iconIndex); 1234 } else { 1235 // Failed to load the shortcut, probably because the 1236 // activity manager couldn't resolve it (maybe the app 1237 // was uninstalled), or the db row was somehow screwed up. 1238 // Delete it. 1239 id = c.getLong(idIndex); 1240 Log.e(TAG, "Error loading shortcut " + id + ", removing it"); 1241 contentResolver.delete(LauncherSettings.Favorites.getContentUri( 1242 id, false), null, null); 1243 } 1244 break; 1245 1246 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 1247 id = c.getLong(idIndex); 1248 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id); 1249 1250 folderInfo.title = c.getString(titleIndex); 1251 folderInfo.id = id; 1252 container = c.getInt(containerIndex); 1253 folderInfo.container = container; 1254 folderInfo.screen = c.getInt(screenIndex); 1255 folderInfo.cellX = c.getInt(cellXIndex); 1256 folderInfo.cellY = c.getInt(cellYIndex); 1257 1258 // check & update map of what's occupied 1259 if (!checkItemPlacement(occupied, folderInfo)) { 1260 break; 1261 } 1262 switch (container) { 1263 case LauncherSettings.Favorites.CONTAINER_DESKTOP: 1264 case LauncherSettings.Favorites.CONTAINER_HOTSEAT: 1265 sBgWorkspaceItems.add(folderInfo); 1266 break; 1267 } 1268 1269 sBgItemsIdMap.put(folderInfo.id, folderInfo); 1270 sBgFolders.put(folderInfo.id, folderInfo); 1271 break; 1272 1273 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 1274 // Read all Launcher-specific widget details 1275 int appWidgetId = c.getInt(appWidgetIdIndex); 1276 id = c.getLong(idIndex); 1277 1278 final AppWidgetProviderInfo provider = 1279 widgets.getAppWidgetInfo(appWidgetId); 1280 1281 if (!isSafeMode && (provider == null || provider.provider == null || 1282 provider.provider.getPackageName() == null)) { 1283 String log = "Deleting widget that isn't installed anymore: id=" 1284 + id + " appWidgetId=" + appWidgetId; 1285 Log.e(TAG, log); 1286 Launcher.sDumpLogs.add(log); 1287 itemsToRemove.add(id); 1288 } else { 1289 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, 1290 provider.provider); 1291 appWidgetInfo.id = id; 1292 appWidgetInfo.screen = c.getInt(screenIndex); 1293 appWidgetInfo.cellX = c.getInt(cellXIndex); 1294 appWidgetInfo.cellY = c.getInt(cellYIndex); 1295 appWidgetInfo.spanX = c.getInt(spanXIndex); 1296 appWidgetInfo.spanY = c.getInt(spanYIndex); 1297 int[] minSpan = Launcher.getMinSpanForWidget(context, provider); 1298 appWidgetInfo.minSpanX = minSpan[0]; 1299 appWidgetInfo.minSpanY = minSpan[1]; 1300 1301 container = c.getInt(containerIndex); 1302 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP && 1303 container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 1304 Log.e(TAG, "Widget found where container != " + 1305 "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!"); 1306 continue; 1307 } 1308 appWidgetInfo.container = c.getInt(containerIndex); 1309 1310 // check & update map of what's occupied 1311 if (!checkItemPlacement(occupied, appWidgetInfo)) { 1312 break; 1313 } 1314 sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo); 1315 sBgAppWidgets.add(appWidgetInfo); 1316 } 1317 break; 1318 } 1319 } catch (Exception e) { 1320 Log.w(TAG, "Desktop items loading interrupted:", e); 1321 } 1322 } 1323 } finally { 1324 c.close(); 1325 } 1326 1327 if (itemsToRemove.size() > 0) { 1328 ContentProviderClient client = contentResolver.acquireContentProviderClient( 1329 LauncherSettings.Favorites.CONTENT_URI); 1330 // Remove dead items 1331 for (long id : itemsToRemove) { 1332 if (DEBUG_LOADERS) { 1333 Log.d(TAG, "Removed id = " + id); 1334 } 1335 // Don't notify content observers 1336 try { 1337 client.delete(LauncherSettings.Favorites.getContentUri(id, false), 1338 null, null); 1339 } catch (RemoteException e) { 1340 Log.w(TAG, "Could not remove id = " + id); 1341 } 1342 } 1343 } 1344 1345 if (DEBUG_LOADERS) { 1346 Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms"); 1347 Log.d(TAG, "workspace layout: "); 1348 for (int y = 0; y < mCellCountY; y++) { 1349 String line = ""; 1350 for (int s = 0; s < Launcher.SCREEN_COUNT; s++) { 1351 if (s > 0) { 1352 line += " | "; 1353 } 1354 for (int x = 0; x < mCellCountX; x++) { 1355 line += ((occupied[s][x][y] != null) ? "#" : "."); 1356 } 1357 } 1358 Log.d(TAG, "[ " + line + " ]"); 1359 } 1360 } 1361 } 1362 } 1363 1364 /** Filters the set of items who are directly or indirectly (via another container) on the 1365 * specified screen. */ 1366 private void filterCurrentWorkspaceItems(int currentScreen, 1367 ArrayList<ItemInfo> allWorkspaceItems, 1368 ArrayList<ItemInfo> currentScreenItems, 1369 ArrayList<ItemInfo> otherScreenItems) { 1370 // Purge any null ItemInfos 1371 Iterator<ItemInfo> iter = allWorkspaceItems.iterator(); 1372 while (iter.hasNext()) { 1373 ItemInfo i = iter.next(); 1374 if (i == null) { 1375 iter.remove(); 1376 } 1377 } 1378 1379 // If we aren't filtering on a screen, then the set of items to load is the full set of 1380 // items given. 1381 if (currentScreen < 0) { 1382 currentScreenItems.addAll(allWorkspaceItems); 1383 } 1384 1385 // Order the set of items by their containers first, this allows use to walk through the 1386 // list sequentially, build up a list of containers that are in the specified screen, 1387 // as well as all items in those containers. 1388 Set<Long> itemsOnScreen = new HashSet<Long>(); 1389 Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() { 1390 @Override 1391 public int compare(ItemInfo lhs, ItemInfo rhs) { 1392 return (int) (lhs.container - rhs.container); 1393 } 1394 }); 1395 for (ItemInfo info : allWorkspaceItems) { 1396 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 1397 if (info.screen == currentScreen) { 1398 currentScreenItems.add(info); 1399 itemsOnScreen.add(info.id); 1400 } else { 1401 otherScreenItems.add(info); 1402 } 1403 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 1404 currentScreenItems.add(info); 1405 itemsOnScreen.add(info.id); 1406 } else { 1407 if (itemsOnScreen.contains(info.container)) { 1408 currentScreenItems.add(info); 1409 itemsOnScreen.add(info.id); 1410 } else { 1411 otherScreenItems.add(info); 1412 } 1413 } 1414 } 1415 } 1416 1417 /** Filters the set of widgets which are on the specified screen. */ 1418 private void filterCurrentAppWidgets(int currentScreen, 1419 ArrayList<LauncherAppWidgetInfo> appWidgets, 1420 ArrayList<LauncherAppWidgetInfo> currentScreenWidgets, 1421 ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) { 1422 // If we aren't filtering on a screen, then the set of items to load is the full set of 1423 // widgets given. 1424 if (currentScreen < 0) { 1425 currentScreenWidgets.addAll(appWidgets); 1426 } 1427 1428 for (LauncherAppWidgetInfo widget : appWidgets) { 1429 if (widget == null) continue; 1430 if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && 1431 widget.screen == currentScreen) { 1432 currentScreenWidgets.add(widget); 1433 } else { 1434 otherScreenWidgets.add(widget); 1435 } 1436 } 1437 } 1438 1439 /** Filters the set of folders which are on the specified screen. */ 1440 private void filterCurrentFolders(int currentScreen, 1441 HashMap<Long, ItemInfo> itemsIdMap, 1442 HashMap<Long, FolderInfo> folders, 1443 HashMap<Long, FolderInfo> currentScreenFolders, 1444 HashMap<Long, FolderInfo> otherScreenFolders) { 1445 // If we aren't filtering on a screen, then the set of items to load is the full set of 1446 // widgets given. 1447 if (currentScreen < 0) { 1448 currentScreenFolders.putAll(folders); 1449 } 1450 1451 for (long id : folders.keySet()) { 1452 ItemInfo info = itemsIdMap.get(id); 1453 FolderInfo folder = folders.get(id); 1454 if (info == null || folder == null) continue; 1455 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && 1456 info.screen == currentScreen) { 1457 currentScreenFolders.put(id, folder); 1458 } else { 1459 otherScreenFolders.put(id, folder); 1460 } 1461 } 1462 } 1463 1464 /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to 1465 * right) */ 1466 private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) { 1467 // XXX: review this 1468 Collections.sort(workspaceItems, new Comparator<ItemInfo>() { 1469 @Override 1470 public int compare(ItemInfo lhs, ItemInfo rhs) { 1471 int cellCountX = LauncherModel.getCellCountX(); 1472 int cellCountY = LauncherModel.getCellCountY(); 1473 int screenOffset = cellCountX * cellCountY; 1474 int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat 1475 long lr = (lhs.container * containerOffset + lhs.screen * screenOffset + 1476 lhs.cellY * cellCountX + lhs.cellX); 1477 long rr = (rhs.container * containerOffset + rhs.screen * screenOffset + 1478 rhs.cellY * cellCountX + rhs.cellX); 1479 return (int) (lr - rr); 1480 } 1481 }); 1482 } 1483 1484 private void bindWorkspaceItems(final Callbacks oldCallbacks, 1485 final ArrayList<ItemInfo> workspaceItems, 1486 final ArrayList<LauncherAppWidgetInfo> appWidgets, 1487 final HashMap<Long, FolderInfo> folders, 1488 ArrayList<Runnable> deferredBindRunnables) { 1489 1490 final boolean postOnMainThread = (deferredBindRunnables != null); 1491 1492 // Bind the workspace items 1493 int N = workspaceItems.size(); 1494 for (int i = 0; i < N; i += ITEMS_CHUNK) { 1495 final int start = i; 1496 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i); 1497 final Runnable r = new Runnable() { 1498 @Override 1499 public void run() { 1500 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1501 if (callbacks != null) { 1502 callbacks.bindItems(workspaceItems, start, start+chunkSize); 1503 } 1504 } 1505 }; 1506 if (postOnMainThread) { 1507 deferredBindRunnables.add(r); 1508 } else { 1509 runOnMainThread(r); 1510 } 1511 } 1512 1513 // Bind the folders 1514 if (!folders.isEmpty()) { 1515 final Runnable r = new Runnable() { 1516 public void run() { 1517 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1518 if (callbacks != null) { 1519 callbacks.bindFolders(folders); 1520 } 1521 } 1522 }; 1523 if (postOnMainThread) { 1524 deferredBindRunnables.add(r); 1525 } else { 1526 runOnMainThread(r); 1527 } 1528 } 1529 1530 // Bind the widgets, one at a time 1531 N = appWidgets.size(); 1532 for (int i = 0; i < N; i++) { 1533 final LauncherAppWidgetInfo widget = appWidgets.get(i); 1534 final Runnable r = new Runnable() { 1535 public void run() { 1536 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1537 if (callbacks != null) { 1538 callbacks.bindAppWidget(widget); 1539 } 1540 } 1541 }; 1542 if (postOnMainThread) { 1543 deferredBindRunnables.add(r); 1544 } else { 1545 runOnMainThread(r); 1546 } 1547 } 1548 } 1549 1550 /** 1551 * Binds all loaded data to actual views on the main thread. 1552 */ 1553 private void bindWorkspace(int synchronizeBindPage) { 1554 final long t = SystemClock.uptimeMillis(); 1555 Runnable r; 1556 1557 // Don't use these two variables in any of the callback runnables. 1558 // Otherwise we hold a reference to them. 1559 final Callbacks oldCallbacks = mCallbacks.get(); 1560 if (oldCallbacks == null) { 1561 // This launcher has exited and nobody bothered to tell us. Just bail. 1562 Log.w(TAG, "LoaderTask running with no launcher"); 1563 return; 1564 } 1565 1566 final boolean isLoadingSynchronously = (synchronizeBindPage > -1); 1567 final int currentScreen = isLoadingSynchronously ? synchronizeBindPage : 1568 oldCallbacks.getCurrentWorkspaceScreen(); 1569 1570 // Load all the items that are on the current page first (and in the process, unbind 1571 // all the existing workspace items before we call startBinding() below. 1572 unbindWorkspaceItemsOnMainThread(); 1573 ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>(); 1574 ArrayList<LauncherAppWidgetInfo> appWidgets = 1575 new ArrayList<LauncherAppWidgetInfo>(); 1576 HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>(); 1577 HashMap<Long, ItemInfo> itemsIdMap = new HashMap<Long, ItemInfo>(); 1578 synchronized (sBgLock) { 1579 workspaceItems.addAll(sBgWorkspaceItems); 1580 appWidgets.addAll(sBgAppWidgets); 1581 folders.putAll(sBgFolders); 1582 itemsIdMap.putAll(sBgItemsIdMap); 1583 } 1584 1585 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>(); 1586 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>(); 1587 ArrayList<LauncherAppWidgetInfo> currentAppWidgets = 1588 new ArrayList<LauncherAppWidgetInfo>(); 1589 ArrayList<LauncherAppWidgetInfo> otherAppWidgets = 1590 new ArrayList<LauncherAppWidgetInfo>(); 1591 HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>(); 1592 HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>(); 1593 1594 // Separate the items that are on the current screen, and all the other remaining items 1595 filterCurrentWorkspaceItems(currentScreen, workspaceItems, currentWorkspaceItems, 1596 otherWorkspaceItems); 1597 filterCurrentAppWidgets(currentScreen, appWidgets, currentAppWidgets, 1598 otherAppWidgets); 1599 filterCurrentFolders(currentScreen, itemsIdMap, folders, currentFolders, 1600 otherFolders); 1601 sortWorkspaceItemsSpatially(currentWorkspaceItems); 1602 sortWorkspaceItemsSpatially(otherWorkspaceItems); 1603 1604 // Tell the workspace that we're about to start binding items 1605 r = new Runnable() { 1606 public void run() { 1607 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1608 if (callbacks != null) { 1609 callbacks.startBinding(); 1610 } 1611 } 1612 }; 1613 runOnMainThread(r); 1614 1615 // Load items on the current page 1616 bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, 1617 currentFolders, null); 1618 if (isLoadingSynchronously) { 1619 r = new Runnable() { 1620 public void run() { 1621 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1622 if (callbacks != null) { 1623 callbacks.onPageBoundSynchronously(currentScreen); 1624 } 1625 } 1626 }; 1627 runOnMainThread(r); 1628 } 1629 1630 // Load all the remaining pages (if we are loading synchronously, we want to defer this 1631 // work until after the first render) 1632 mDeferredBindRunnables.clear(); 1633 bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders, 1634 (isLoadingSynchronously ? mDeferredBindRunnables : null)); 1635 1636 // Tell the workspace that we're done binding items 1637 r = new Runnable() { 1638 public void run() { 1639 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1640 if (callbacks != null) { 1641 callbacks.finishBindingItems(); 1642 } 1643 1644 // If we're profiling, ensure this is the last thing in the queue. 1645 if (DEBUG_LOADERS) { 1646 Log.d(TAG, "bound workspace in " 1647 + (SystemClock.uptimeMillis()-t) + "ms"); 1648 } 1649 1650 mIsLoadingAndBindingWorkspace = false; 1651 } 1652 }; 1653 if (isLoadingSynchronously) { 1654 mDeferredBindRunnables.add(r); 1655 } else { 1656 runOnMainThread(r); 1657 } 1658 } 1659 1660 private void loadAndBindAllApps() { 1661 if (DEBUG_LOADERS) { 1662 Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded); 1663 } 1664 if (!mAllAppsLoaded) { 1665 loadAllAppsByBatch(); 1666 synchronized (LoaderTask.this) { 1667 if (mStopped) { 1668 return; 1669 } 1670 mAllAppsLoaded = true; 1671 } 1672 } else { 1673 onlyBindAllApps(); 1674 } 1675 } 1676 1677 private void onlyBindAllApps() { 1678 final Callbacks oldCallbacks = mCallbacks.get(); 1679 if (oldCallbacks == null) { 1680 // This launcher has exited and nobody bothered to tell us. Just bail. 1681 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)"); 1682 return; 1683 } 1684 1685 // shallow copy 1686 @SuppressWarnings("unchecked") 1687 final ArrayList<ApplicationInfo> list 1688 = (ArrayList<ApplicationInfo>) mAllAppsList.data.clone(); 1689 Runnable r = new Runnable() { 1690 public void run() { 1691 final long t = SystemClock.uptimeMillis(); 1692 final Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1693 if (callbacks != null) { 1694 callbacks.bindAllApplications(list); 1695 } 1696 if (DEBUG_LOADERS) { 1697 Log.d(TAG, "bound all " + list.size() + " apps from cache in " 1698 + (SystemClock.uptimeMillis()-t) + "ms"); 1699 } 1700 } 1701 }; 1702 boolean isRunningOnMainThread = !(sWorkerThread.getThreadId() == Process.myTid()); 1703 if (oldCallbacks.isAllAppsVisible() && isRunningOnMainThread) { 1704 r.run(); 1705 } else { 1706 mHandler.post(r); 1707 } 1708 } 1709 1710 private void loadAllAppsByBatch() { 1711 final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 1712 1713 // Don't use these two variables in any of the callback runnables. 1714 // Otherwise we hold a reference to them. 1715 final Callbacks oldCallbacks = mCallbacks.get(); 1716 if (oldCallbacks == null) { 1717 // This launcher has exited and nobody bothered to tell us. Just bail. 1718 Log.w(TAG, "LoaderTask running with no launcher (loadAllAppsByBatch)"); 1719 return; 1720 } 1721 1722 final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); 1723 mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); 1724 1725 final PackageManager packageManager = mContext.getPackageManager(); 1726 List<ResolveInfo> apps = null; 1727 1728 int N = Integer.MAX_VALUE; 1729 1730 int startIndex; 1731 int i=0; 1732 int batchSize = -1; 1733 while (i < N && !mStopped) { 1734 if (i == 0) { 1735 mAllAppsList.clear(); 1736 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 1737 apps = packageManager.queryIntentActivities(mainIntent, 0); 1738 if (DEBUG_LOADERS) { 1739 Log.d(TAG, "queryIntentActivities took " 1740 + (SystemClock.uptimeMillis()-qiaTime) + "ms"); 1741 } 1742 if (apps == null) { 1743 return; 1744 } 1745 N = apps.size(); 1746 if (DEBUG_LOADERS) { 1747 Log.d(TAG, "queryIntentActivities got " + N + " apps"); 1748 } 1749 if (N == 0) { 1750 // There are no apps?!? 1751 return; 1752 } 1753 if (mBatchSize == 0) { 1754 batchSize = N; 1755 } else { 1756 batchSize = mBatchSize; 1757 } 1758 1759 final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 1760 Collections.sort(apps, 1761 new LauncherModel.ShortcutNameComparator(packageManager, mLabelCache)); 1762 if (DEBUG_LOADERS) { 1763 Log.d(TAG, "sort took " 1764 + (SystemClock.uptimeMillis()-sortTime) + "ms"); 1765 } 1766 } 1767 1768 final long t2 = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 1769 1770 startIndex = i; 1771 for (int j=0; i<N && j<batchSize; j++) { 1772 // This builds the icon bitmaps. 1773 mAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i), 1774 mIconCache, mLabelCache)); 1775 i++; 1776 } 1777 1778 final boolean first = i <= batchSize; 1779 final Callbacks callbacks = tryGetCallbacks(oldCallbacks); 1780 final ArrayList<ApplicationInfo> added = mAllAppsList.added; 1781 mAllAppsList.added = new ArrayList<ApplicationInfo>(); 1782 1783 mHandler.post(new Runnable() { 1784 public void run() { 1785 final long t = SystemClock.uptimeMillis(); 1786 if (callbacks != null) { 1787 if (first) { 1788 callbacks.bindAllApplications(added); 1789 } else { 1790 callbacks.bindAppsAdded(added); 1791 } 1792 if (DEBUG_LOADERS) { 1793 Log.d(TAG, "bound " + added.size() + " apps in " 1794 + (SystemClock.uptimeMillis() - t) + "ms"); 1795 } 1796 } else { 1797 Log.i(TAG, "not binding apps: no Launcher activity"); 1798 } 1799 } 1800 }); 1801 1802 if (DEBUG_LOADERS) { 1803 Log.d(TAG, "batch of " + (i-startIndex) + " icons processed in " 1804 + (SystemClock.uptimeMillis()-t2) + "ms"); 1805 } 1806 1807 if (mAllAppsLoadDelay > 0 && i < N) { 1808 try { 1809 if (DEBUG_LOADERS) { 1810 Log.d(TAG, "sleeping for " + mAllAppsLoadDelay + "ms"); 1811 } 1812 Thread.sleep(mAllAppsLoadDelay); 1813 } catch (InterruptedException exc) { } 1814 } 1815 } 1816 1817 if (DEBUG_LOADERS) { 1818 Log.d(TAG, "cached all " + N + " apps in " 1819 + (SystemClock.uptimeMillis()-t) + "ms" 1820 + (mAllAppsLoadDelay > 0 ? " (including delay)" : "")); 1821 } 1822 } 1823 1824 public void dumpState() { 1825 synchronized (sBgLock) { 1826 Log.d(TAG, "mLoaderTask.mContext=" + mContext); 1827 Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching); 1828 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped); 1829 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished); 1830 Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size()); 1831 } 1832 } 1833 } 1834 1835 void enqueuePackageUpdated(PackageUpdatedTask task) { 1836 sWorker.post(task); 1837 } 1838 1839 private class PackageUpdatedTask implements Runnable { 1840 int mOp; 1841 String[] mPackages; 1842 1843 public static final int OP_NONE = 0; 1844 public static final int OP_ADD = 1; 1845 public static final int OP_UPDATE = 2; 1846 public static final int OP_REMOVE = 3; // uninstlled 1847 public static final int OP_UNAVAILABLE = 4; // external media unmounted 1848 1849 1850 public PackageUpdatedTask(int op, String[] packages) { 1851 mOp = op; 1852 mPackages = packages; 1853 } 1854 1855 public void run() { 1856 final Context context = mApp; 1857 1858 final String[] packages = mPackages; 1859 final int N = packages.length; 1860 switch (mOp) { 1861 case OP_ADD: 1862 for (int i=0; i<N; i++) { 1863 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]); 1864 mAllAppsList.addPackage(context, packages[i]); 1865 } 1866 break; 1867 case OP_UPDATE: 1868 for (int i=0; i<N; i++) { 1869 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]); 1870 mAllAppsList.updatePackage(context, packages[i]); 1871 } 1872 break; 1873 case OP_REMOVE: 1874 case OP_UNAVAILABLE: 1875 for (int i=0; i<N; i++) { 1876 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]); 1877 mAllAppsList.removePackage(packages[i]); 1878 } 1879 break; 1880 } 1881 1882 ArrayList<ApplicationInfo> added = null; 1883 ArrayList<ApplicationInfo> removed = null; 1884 ArrayList<ApplicationInfo> modified = null; 1885 1886 if (mAllAppsList.added.size() > 0) { 1887 added = mAllAppsList.added; 1888 mAllAppsList.added = new ArrayList<ApplicationInfo>(); 1889 } 1890 if (mAllAppsList.removed.size() > 0) { 1891 removed = mAllAppsList.removed; 1892 mAllAppsList.removed = new ArrayList<ApplicationInfo>(); 1893 for (ApplicationInfo info: removed) { 1894 mIconCache.remove(info.intent.getComponent()); 1895 } 1896 } 1897 if (mAllAppsList.modified.size() > 0) { 1898 modified = mAllAppsList.modified; 1899 mAllAppsList.modified = new ArrayList<ApplicationInfo>(); 1900 } 1901 1902 final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null; 1903 if (callbacks == null) { 1904 Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading."); 1905 return; 1906 } 1907 1908 if (added != null) { 1909 final ArrayList<ApplicationInfo> addedFinal = added; 1910 mHandler.post(new Runnable() { 1911 public void run() { 1912 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; 1913 if (callbacks == cb && cb != null) { 1914 callbacks.bindAppsAdded(addedFinal); 1915 } 1916 } 1917 }); 1918 } 1919 if (modified != null) { 1920 final ArrayList<ApplicationInfo> modifiedFinal = modified; 1921 mHandler.post(new Runnable() { 1922 public void run() { 1923 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; 1924 if (callbacks == cb && cb != null) { 1925 callbacks.bindAppsUpdated(modifiedFinal); 1926 } 1927 } 1928 }); 1929 } 1930 if (removed != null) { 1931 final boolean permanent = mOp != OP_UNAVAILABLE; 1932 final ArrayList<ApplicationInfo> removedFinal = removed; 1933 mHandler.post(new Runnable() { 1934 public void run() { 1935 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; 1936 if (callbacks == cb && cb != null) { 1937 callbacks.bindAppsRemoved(removedFinal, permanent); 1938 } 1939 } 1940 }); 1941 } 1942 1943 mHandler.post(new Runnable() { 1944 @Override 1945 public void run() { 1946 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; 1947 if (callbacks == cb && cb != null) { 1948 callbacks.bindPackagesUpdated(); 1949 } 1950 } 1951 }); 1952 } 1953 } 1954 1955 /** 1956 * Returns all the Workspace ShortcutInfos associated with a particular package. 1957 * @param intent 1958 * @return 1959 */ 1960 ArrayList<ShortcutInfo> getShortcutInfosForPackage(String packageName) { 1961 ArrayList<ShortcutInfo> infos = new ArrayList<ShortcutInfo>(); 1962 synchronized (sBgLock) { 1963 for (ItemInfo i : sBgWorkspaceItems) { 1964 if (i instanceof ShortcutInfo) { 1965 ShortcutInfo info = (ShortcutInfo) i; 1966 if (packageName.equals(info.getPackageName())) { 1967 infos.add(info); 1968 } 1969 } 1970 } 1971 } 1972 return infos; 1973 } 1974 1975 /** 1976 * This is called from the code that adds shortcuts from the intent receiver. This 1977 * doesn't have a Cursor, but 1978 */ 1979 public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context) { 1980 return getShortcutInfo(manager, intent, context, null, -1, -1, null); 1981 } 1982 1983 /** 1984 * Make an ShortcutInfo object for a shortcut that is an application. 1985 * 1986 * If c is not null, then it will be used to fill in missing data like the title and icon. 1987 */ 1988 public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context, 1989 Cursor c, int iconIndex, int titleIndex, HashMap<Object, CharSequence> labelCache) { 1990 Bitmap icon = null; 1991 final ShortcutInfo info = new ShortcutInfo(); 1992 1993 ComponentName componentName = intent.getComponent(); 1994 if (componentName == null) { 1995 return null; 1996 } 1997 1998 try { 1999 PackageInfo pi = manager.getPackageInfo(componentName.getPackageName(), 0); 2000 if (!pi.applicationInfo.enabled) { 2001 // If we return null here, the corresponding item will be removed from the launcher 2002 // db and will not appear in the workspace. 2003 return null; 2004 } 2005 } catch (NameNotFoundException e) { 2006 Log.d(TAG, "getPackInfo failed for package " + componentName.getPackageName()); 2007 } 2008 2009 // TODO: See if the PackageManager knows about this case. If it doesn't 2010 // then return null & delete this. 2011 2012 // the resource -- This may implicitly give us back the fallback icon, 2013 // but don't worry about that. All we're doing with usingFallbackIcon is 2014 // to avoid saving lots of copies of that in the database, and most apps 2015 // have icons anyway. 2016 2017 // Attempt to use queryIntentActivities to get the ResolveInfo (with IntentFilter info) and 2018 // if that fails, or is ambiguious, fallback to the standard way of getting the resolve info 2019 // via resolveActivity(). 2020 ResolveInfo resolveInfo = null; 2021 ComponentName oldComponent = intent.getComponent(); 2022 Intent newIntent = new Intent(intent.getAction(), null); 2023 newIntent.addCategory(Intent.CATEGORY_LAUNCHER); 2024 newIntent.setPackage(oldComponent.getPackageName()); 2025 List<ResolveInfo> infos = manager.queryIntentActivities(newIntent, 0); 2026 for (ResolveInfo i : infos) { 2027 ComponentName cn = new ComponentName(i.activityInfo.packageName, 2028 i.activityInfo.name); 2029 if (cn.equals(oldComponent)) { 2030 resolveInfo = i; 2031 } 2032 } 2033 if (resolveInfo == null) { 2034 resolveInfo = manager.resolveActivity(intent, 0); 2035 } 2036 if (resolveInfo != null) { 2037 icon = mIconCache.getIcon(componentName, resolveInfo, labelCache); 2038 } 2039 // the db 2040 if (icon == null) { 2041 if (c != null) { 2042 icon = getIconFromCursor(c, iconIndex, context); 2043 } 2044 } 2045 // the fallback icon 2046 if (icon == null) { 2047 icon = getFallbackIcon(); 2048 info.usingFallbackIcon = true; 2049 } 2050 info.setIcon(icon); 2051 2052 // from the resource 2053 if (resolveInfo != null) { 2054 ComponentName key = LauncherModel.getComponentNameFromResolveInfo(resolveInfo); 2055 if (labelCache != null && labelCache.containsKey(key)) { 2056 info.title = labelCache.get(key); 2057 } else { 2058 info.title = resolveInfo.activityInfo.loadLabel(manager); 2059 if (labelCache != null) { 2060 labelCache.put(key, info.title); 2061 } 2062 } 2063 } 2064 // from the db 2065 if (info.title == null) { 2066 if (c != null) { 2067 info.title = c.getString(titleIndex); 2068 } 2069 } 2070 // fall back to the class name of the activity 2071 if (info.title == null) { 2072 info.title = componentName.getClassName(); 2073 } 2074 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; 2075 return info; 2076 } 2077 2078 /** 2079 * Make an ShortcutInfo object for a shortcut that isn't an application. 2080 */ 2081 private ShortcutInfo getShortcutInfo(Cursor c, Context context, 2082 int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex, 2083 int titleIndex) { 2084 2085 Bitmap icon = null; 2086 final ShortcutInfo info = new ShortcutInfo(); 2087 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; 2088 2089 // TODO: If there's an explicit component and we can't install that, delete it. 2090 2091 info.title = c.getString(titleIndex); 2092 2093 int iconType = c.getInt(iconTypeIndex); 2094 switch (iconType) { 2095 case LauncherSettings.Favorites.ICON_TYPE_RESOURCE: 2096 String packageName = c.getString(iconPackageIndex); 2097 String resourceName = c.getString(iconResourceIndex); 2098 PackageManager packageManager = context.getPackageManager(); 2099 info.customIcon = false; 2100 // the resource 2101 try { 2102 Resources resources = packageManager.getResourcesForApplication(packageName); 2103 if (resources != null) { 2104 final int id = resources.getIdentifier(resourceName, null, null); 2105 icon = Utilities.createIconBitmap( 2106 mIconCache.getFullResIcon(resources, id), context); 2107 } 2108 } catch (Exception e) { 2109 // drop this. we have other places to look for icons 2110 } 2111 // the db 2112 if (icon == null) { 2113 icon = getIconFromCursor(c, iconIndex, context); 2114 } 2115 // the fallback icon 2116 if (icon == null) { 2117 icon = getFallbackIcon(); 2118 info.usingFallbackIcon = true; 2119 } 2120 break; 2121 case LauncherSettings.Favorites.ICON_TYPE_BITMAP: 2122 icon = getIconFromCursor(c, iconIndex, context); 2123 if (icon == null) { 2124 icon = getFallbackIcon(); 2125 info.customIcon = false; 2126 info.usingFallbackIcon = true; 2127 } else { 2128 info.customIcon = true; 2129 } 2130 break; 2131 default: 2132 icon = getFallbackIcon(); 2133 info.usingFallbackIcon = true; 2134 info.customIcon = false; 2135 break; 2136 } 2137 info.setIcon(icon); 2138 return info; 2139 } 2140 2141 Bitmap getIconFromCursor(Cursor c, int iconIndex, Context context) { 2142 @SuppressWarnings("all") // suppress dead code warning 2143 final boolean debug = false; 2144 if (debug) { 2145 Log.d(TAG, "getIconFromCursor app=" 2146 + c.getString(c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE))); 2147 } 2148 byte[] data = c.getBlob(iconIndex); 2149 try { 2150 return Utilities.createIconBitmap( 2151 BitmapFactory.decodeByteArray(data, 0, data.length), context); 2152 } catch (Exception e) { 2153 return null; 2154 } 2155 } 2156 2157 ShortcutInfo addShortcut(Context context, Intent data, long container, int screen, 2158 int cellX, int cellY, boolean notify) { 2159 final ShortcutInfo info = infoFromShortcutIntent(context, data, null); 2160 if (info == null) { 2161 return null; 2162 } 2163 addItemToDatabase(context, info, container, screen, cellX, cellY, notify); 2164 2165 return info; 2166 } 2167 2168 /** 2169 * Attempts to find an AppWidgetProviderInfo that matches the given component. 2170 */ 2171 AppWidgetProviderInfo findAppWidgetProviderInfoWithComponent(Context context, 2172 ComponentName component) { 2173 List<AppWidgetProviderInfo> widgets = 2174 AppWidgetManager.getInstance(context).getInstalledProviders(); 2175 for (AppWidgetProviderInfo info : widgets) { 2176 if (info.provider.equals(component)) { 2177 return info; 2178 } 2179 } 2180 return null; 2181 } 2182 2183 /** 2184 * Returns a list of all the widgets that can handle configuration with a particular mimeType. 2185 */ 2186 List<WidgetMimeTypeHandlerData> resolveWidgetsForMimeType(Context context, String mimeType) { 2187 final PackageManager packageManager = context.getPackageManager(); 2188 final List<WidgetMimeTypeHandlerData> supportedConfigurationActivities = 2189 new ArrayList<WidgetMimeTypeHandlerData>(); 2190 2191 final Intent supportsIntent = 2192 new Intent(InstallWidgetReceiver.ACTION_SUPPORTS_CLIPDATA_MIMETYPE); 2193 supportsIntent.setType(mimeType); 2194 2195 // Create a set of widget configuration components that we can test against 2196 final List<AppWidgetProviderInfo> widgets = 2197 AppWidgetManager.getInstance(context).getInstalledProviders(); 2198 final HashMap<ComponentName, AppWidgetProviderInfo> configurationComponentToWidget = 2199 new HashMap<ComponentName, AppWidgetProviderInfo>(); 2200 for (AppWidgetProviderInfo info : widgets) { 2201 configurationComponentToWidget.put(info.configure, info); 2202 } 2203 2204 // Run through each of the intents that can handle this type of clip data, and cross 2205 // reference them with the components that are actual configuration components 2206 final List<ResolveInfo> activities = packageManager.queryIntentActivities(supportsIntent, 2207 PackageManager.MATCH_DEFAULT_ONLY); 2208 for (ResolveInfo info : activities) { 2209 final ActivityInfo activityInfo = info.activityInfo; 2210 final ComponentName infoComponent = new ComponentName(activityInfo.packageName, 2211 activityInfo.name); 2212 if (configurationComponentToWidget.containsKey(infoComponent)) { 2213 supportedConfigurationActivities.add( 2214 new InstallWidgetReceiver.WidgetMimeTypeHandlerData(info, 2215 configurationComponentToWidget.get(infoComponent))); 2216 } 2217 } 2218 return supportedConfigurationActivities; 2219 } 2220 2221 ShortcutInfo infoFromShortcutIntent(Context context, Intent data, Bitmap fallbackIcon) { 2222 Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); 2223 String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); 2224 Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON); 2225 2226 if (intent == null) { 2227 // If the intent is null, we can't construct a valid ShortcutInfo, so we return null 2228 Log.e(TAG, "Can't construct ShorcutInfo with null intent"); 2229 return null; 2230 } 2231 2232 Bitmap icon = null; 2233 boolean customIcon = false; 2234 ShortcutIconResource iconResource = null; 2235 2236 if (bitmap != null && bitmap instanceof Bitmap) { 2237 icon = Utilities.createIconBitmap(new FastBitmapDrawable((Bitmap)bitmap), context); 2238 customIcon = true; 2239 } else { 2240 Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); 2241 if (extra != null && extra instanceof ShortcutIconResource) { 2242 try { 2243 iconResource = (ShortcutIconResource) extra; 2244 final PackageManager packageManager = context.getPackageManager(); 2245 Resources resources = packageManager.getResourcesForApplication( 2246 iconResource.packageName); 2247 final int id = resources.getIdentifier(iconResource.resourceName, null, null); 2248 icon = Utilities.createIconBitmap( 2249 mIconCache.getFullResIcon(resources, id), context); 2250 } catch (Exception e) { 2251 Log.w(TAG, "Could not load shortcut icon: " + extra); 2252 } 2253 } 2254 } 2255 2256 final ShortcutInfo info = new ShortcutInfo(); 2257 2258 if (icon == null) { 2259 if (fallbackIcon != null) { 2260 icon = fallbackIcon; 2261 } else { 2262 icon = getFallbackIcon(); 2263 info.usingFallbackIcon = true; 2264 } 2265 } 2266 info.setIcon(icon); 2267 2268 info.title = name; 2269 info.intent = intent; 2270 info.customIcon = customIcon; 2271 info.iconResource = iconResource; 2272 2273 return info; 2274 } 2275 2276 boolean queueIconToBeChecked(HashMap<Object, byte[]> cache, ShortcutInfo info, Cursor c, 2277 int iconIndex) { 2278 // If apps can't be on SD, don't even bother. 2279 if (!mAppsCanBeOnExternalStorage) { 2280 return false; 2281 } 2282 // If this icon doesn't have a custom icon, check to see 2283 // what's stored in the DB, and if it doesn't match what 2284 // we're going to show, store what we are going to show back 2285 // into the DB. We do this so when we're loading, if the 2286 // package manager can't find an icon (for example because 2287 // the app is on SD) then we can use that instead. 2288 if (!info.customIcon && !info.usingFallbackIcon) { 2289 cache.put(info, c.getBlob(iconIndex)); 2290 return true; 2291 } 2292 return false; 2293 } 2294 void updateSavedIcon(Context context, ShortcutInfo info, byte[] data) { 2295 boolean needSave = false; 2296 try { 2297 if (data != null) { 2298 Bitmap saved = BitmapFactory.decodeByteArray(data, 0, data.length); 2299 Bitmap loaded = info.getIcon(mIconCache); 2300 needSave = !saved.sameAs(loaded); 2301 } else { 2302 needSave = true; 2303 } 2304 } catch (Exception e) { 2305 needSave = true; 2306 } 2307 if (needSave) { 2308 Log.d(TAG, "going to save icon bitmap for info=" + info); 2309 // This is slower than is ideal, but this only happens once 2310 // or when the app is updated with a new icon. 2311 updateItemInDatabase(context, info); 2312 } 2313 } 2314 2315 /** 2316 * Return an existing FolderInfo object if we have encountered this ID previously, 2317 * or make a new one. 2318 */ 2319 private static FolderInfo findOrMakeFolder(HashMap<Long, FolderInfo> folders, long id) { 2320 // See if a placeholder was created for us already 2321 FolderInfo folderInfo = folders.get(id); 2322 if (folderInfo == null) { 2323 // No placeholder -- create a new instance 2324 folderInfo = new FolderInfo(); 2325 folders.put(id, folderInfo); 2326 } 2327 return folderInfo; 2328 } 2329 2330 private static final Collator sCollator = Collator.getInstance(); 2331 public static final Comparator<ApplicationInfo> APP_NAME_COMPARATOR 2332 = new Comparator<ApplicationInfo>() { 2333 public final int compare(ApplicationInfo a, ApplicationInfo b) { 2334 int result = sCollator.compare(a.title.toString(), b.title.toString()); 2335 if (result == 0) { 2336 result = a.componentName.compareTo(b.componentName); 2337 } 2338 return result; 2339 } 2340 }; 2341 public static final Comparator<ApplicationInfo> APP_INSTALL_TIME_COMPARATOR 2342 = new Comparator<ApplicationInfo>() { 2343 public final int compare(ApplicationInfo a, ApplicationInfo b) { 2344 if (a.firstInstallTime < b.firstInstallTime) return 1; 2345 if (a.firstInstallTime > b.firstInstallTime) return -1; 2346 return 0; 2347 } 2348 }; 2349 public static final Comparator<AppWidgetProviderInfo> WIDGET_NAME_COMPARATOR 2350 = new Comparator<AppWidgetProviderInfo>() { 2351 public final int compare(AppWidgetProviderInfo a, AppWidgetProviderInfo b) { 2352 return sCollator.compare(a.label.toString(), b.label.toString()); 2353 } 2354 }; 2355 static ComponentName getComponentNameFromResolveInfo(ResolveInfo info) { 2356 if (info.activityInfo != null) { 2357 return new ComponentName(info.activityInfo.packageName, info.activityInfo.name); 2358 } else { 2359 return new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name); 2360 } 2361 } 2362 public static class ShortcutNameComparator implements Comparator<ResolveInfo> { 2363 private PackageManager mPackageManager; 2364 private HashMap<Object, CharSequence> mLabelCache; 2365 ShortcutNameComparator(PackageManager pm) { 2366 mPackageManager = pm; 2367 mLabelCache = new HashMap<Object, CharSequence>(); 2368 } 2369 ShortcutNameComparator(PackageManager pm, HashMap<Object, CharSequence> labelCache) { 2370 mPackageManager = pm; 2371 mLabelCache = labelCache; 2372 } 2373 public final int compare(ResolveInfo a, ResolveInfo b) { 2374 CharSequence labelA, labelB; 2375 ComponentName keyA = LauncherModel.getComponentNameFromResolveInfo(a); 2376 ComponentName keyB = LauncherModel.getComponentNameFromResolveInfo(b); 2377 if (mLabelCache.containsKey(keyA)) { 2378 labelA = mLabelCache.get(keyA); 2379 } else { 2380 labelA = a.loadLabel(mPackageManager).toString(); 2381 2382 mLabelCache.put(keyA, labelA); 2383 } 2384 if (mLabelCache.containsKey(keyB)) { 2385 labelB = mLabelCache.get(keyB); 2386 } else { 2387 labelB = b.loadLabel(mPackageManager).toString(); 2388 2389 mLabelCache.put(keyB, labelB); 2390 } 2391 return sCollator.compare(labelA, labelB); 2392 } 2393 }; 2394 public static class WidgetAndShortcutNameComparator implements Comparator<Object> { 2395 private PackageManager mPackageManager; 2396 private HashMap<Object, String> mLabelCache; 2397 WidgetAndShortcutNameComparator(PackageManager pm) { 2398 mPackageManager = pm; 2399 mLabelCache = new HashMap<Object, String>(); 2400 } 2401 public final int compare(Object a, Object b) { 2402 String labelA, labelB; 2403 if (mLabelCache.containsKey(a)) { 2404 labelA = mLabelCache.get(a); 2405 } else { 2406 labelA = (a instanceof AppWidgetProviderInfo) ? 2407 ((AppWidgetProviderInfo) a).label : 2408 ((ResolveInfo) a).loadLabel(mPackageManager).toString(); 2409 mLabelCache.put(a, labelA); 2410 } 2411 if (mLabelCache.containsKey(b)) { 2412 labelB = mLabelCache.get(b); 2413 } else { 2414 labelB = (b instanceof AppWidgetProviderInfo) ? 2415 ((AppWidgetProviderInfo) b).label : 2416 ((ResolveInfo) b).loadLabel(mPackageManager).toString(); 2417 mLabelCache.put(b, labelB); 2418 } 2419 return sCollator.compare(labelA, labelB); 2420 } 2421 }; 2422 2423 public void dumpState() { 2424 Log.d(TAG, "mCallbacks=" + mCallbacks); 2425 ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mAllAppsList.data); 2426 ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mAllAppsList.added); 2427 ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mAllAppsList.removed); 2428 ApplicationInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mAllAppsList.modified); 2429 if (mLoaderTask != null) { 2430 mLoaderTask.dumpState(); 2431 } else { 2432 Log.d(TAG, "mLoaderTask=null"); 2433 } 2434 } 2435 } 2436