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