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