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