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