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