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