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