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