LauncherModel.java revision b358f813c93ac0d6c4f31058947efd16b188cd1e
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.*; 23import android.content.Intent.ShortcutIconResource; 24import android.content.pm.ActivityInfo; 25import android.content.pm.PackageInfo; 26import android.content.pm.PackageManager; 27import android.content.pm.PackageManager.NameNotFoundException; 28import android.content.pm.ResolveInfo; 29import android.content.res.Configuration; 30import android.content.res.Resources; 31import android.database.Cursor; 32import android.graphics.Bitmap; 33import android.graphics.BitmapFactory; 34import android.net.Uri; 35import android.os.Environment; 36import android.os.Handler; 37import android.os.HandlerThread; 38import android.os.Parcelable; 39import android.os.Process; 40import android.os.RemoteException; 41import android.os.SystemClock; 42import android.provider.BaseColumns; 43import android.text.TextUtils; 44import android.util.Log; 45import android.util.Pair; 46 47import com.android.launcher3.InstallWidgetReceiver.WidgetMimeTypeHandlerData; 48 49import java.lang.ref.WeakReference; 50import java.net.URISyntaxException; 51import java.text.Collator; 52import java.util.ArrayList; 53import java.util.Arrays; 54import java.util.Collection; 55import java.util.Collections; 56import java.util.Comparator; 57import java.util.HashMap; 58import java.util.HashSet; 59import java.util.Iterator; 60import java.util.List; 61import java.util.Set; 62import java.util.TreeMap; 63import java.util.concurrent.atomic.AtomicBoolean; 64 65/** 66 * Maintains in-memory state of the Launcher. It is expected that there should be only one 67 * LauncherModel object held in a static. Also provide APIs for updating the database state 68 * for the Launcher. 69 */ 70public class LauncherModel extends BroadcastReceiver { 71 static final boolean DEBUG_LOADERS = false; 72 private static final boolean DEBUG_RECEIVER = true; // STOPSHIP(cwren) temporary for debugging 73 74 static final String TAG = "Launcher.Model"; 75 76 // true = use a "More Apps" folder for non-workspace apps on upgrade 77 // false = strew non-workspace apps across the workspace on upgrade 78 public static final boolean UPGRADE_USE_MORE_APPS_FOLDER = false; 79 public static final int LOADER_FLAG_NONE = 0; 80 public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0; 81 public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1; 82 83 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons 84 private static final long INVALID_SCREEN_ID = -1L; 85 86 private final boolean mAppsCanBeOnRemoveableStorage; 87 private final boolean mOldContentProviderExists; 88 89 private final LauncherAppState mApp; 90 private final Object mLock = new Object(); 91 private DeferredHandler mHandler = new DeferredHandler(); 92 private LoaderTask mLoaderTask; 93 private boolean mIsLoaderTaskRunning; 94 private volatile boolean mFlushingWorkerThread; 95 96 // Specific runnable types that are run on the main thread deferred handler, this allows us to 97 // clear all queued binding runnables when the Launcher activity is destroyed. 98 private static final int MAIN_THREAD_NORMAL_RUNNABLE = 0; 99 private static final int MAIN_THREAD_BINDING_RUNNABLE = 1; 100 101 102 private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader"); 103 static { 104 sWorkerThread.start(); 105 } 106 private static final Handler sWorker = new Handler(sWorkerThread.getLooper()); 107 108 // We start off with everything not loaded. After that, we assume that 109 // our monitoring of the package manager provides all updates and we never 110 // need to do a requery. These are only ever touched from the loader thread. 111 private boolean mWorkspaceLoaded; 112 private boolean mAllAppsLoaded; 113 114 // When we are loading pages synchronously, we can't just post the binding of items on the side 115 // pages as this delays the rotation process. Instead, we wait for a callback from the first 116 // draw (in Workspace) to initiate the binding of the remaining side pages. Any time we start 117 // a normal load, we also clear this set of Runnables. 118 static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>(); 119 120 private WeakReference<Callbacks> mCallbacks; 121 122 // < only access in worker thread > 123 AllAppsList mBgAllAppsList; 124 125 // The lock that must be acquired before referencing any static bg data structures. Unlike 126 // other locks, this one can generally be held long-term because we never expect any of these 127 // static data structures to be referenced outside of the worker thread except on the first 128 // load after configuration change. 129 static final Object sBgLock = new Object(); 130 131 // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by 132 // LauncherModel to their ids 133 static final HashMap<Long, ItemInfo> sBgItemsIdMap = new HashMap<Long, ItemInfo>(); 134 135 // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts 136 // created by LauncherModel that are directly on the home screen (however, no widgets or 137 // shortcuts within folders). 138 static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>(); 139 140 // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget() 141 static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets = 142 new ArrayList<LauncherAppWidgetInfo>(); 143 144 // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders() 145 static final HashMap<Long, FolderInfo> sBgFolders = new HashMap<Long, FolderInfo>(); 146 147 // sBgDbIconCache is the set of ItemInfos that need to have their icons updated in the database 148 static final HashMap<Object, byte[]> sBgDbIconCache = new HashMap<Object, byte[]>(); 149 150 // sBgWorkspaceScreens is the ordered set of workspace screens. 151 static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>(); 152 153 // </ only access in worker thread > 154 155 private IconCache mIconCache; 156 private Bitmap mDefaultIcon; 157 158 protected int mPreviousConfigMcc; 159 160 public interface Callbacks { 161 public boolean setLoadOnResume(); 162 public int getCurrentWorkspaceScreen(); 163 public void startBinding(); 164 public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end, 165 boolean forceAnimateIcons); 166 public void bindScreens(ArrayList<Long> orderedScreenIds); 167 public void bindAddScreens(ArrayList<Long> orderedScreenIds); 168 public void bindFolders(HashMap<Long,FolderInfo> folders); 169 public void finishBindingItems(boolean upgradePath); 170 public void bindAppWidget(LauncherAppWidgetInfo info); 171 public void bindAllApplications(ArrayList<AppInfo> apps); 172 public void bindAppsAdded(ArrayList<Long> newScreens, 173 ArrayList<ItemInfo> addNotAnimated, 174 ArrayList<ItemInfo> addAnimated, 175 ArrayList<AppInfo> addedApps); 176 public void bindAppsUpdated(ArrayList<AppInfo> apps); 177 public void updatePackageState(String pkgName, int state); 178 public void bindComponentsRemoved(ArrayList<String> packageNames, 179 ArrayList<AppInfo> appInfos); 180 public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts); 181 public void bindSearchablesChanged(); 182 public boolean isAllAppsButtonRank(int rank); 183 public void onPageBoundSynchronously(int page); 184 public void dumpLogsToLocalData(); 185 } 186 187 public interface ItemInfoFilter { 188 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn); 189 } 190 191 LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) { 192 Context context = app.getContext(); 193 ContentResolver contentResolver = context.getContentResolver(); 194 195 mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable(); 196 ContentProviderClient client = contentResolver.acquireContentProviderClient( 197 LauncherSettings.Favorites.OLD_CONTENT_URI); 198 mOldContentProviderExists = (client != null); 199 if (client != null) { 200 client.release(); 201 } 202 mApp = app; 203 mBgAllAppsList = new AllAppsList(iconCache, appFilter); 204 mIconCache = iconCache; 205 206 final Resources res = context.getResources(); 207 Configuration config = res.getConfiguration(); 208 mPreviousConfigMcc = config.mcc; 209 } 210 211 /** Runs the specified runnable immediately if called from the main thread, otherwise it is 212 * posted on the main thread handler. */ 213 private void runOnMainThread(Runnable r) { 214 runOnMainThread(r, 0); 215 } 216 private void runOnMainThread(Runnable r, int type) { 217 if (sWorkerThread.getThreadId() == Process.myTid()) { 218 // If we are on the worker thread, post onto the main handler 219 mHandler.post(r); 220 } else { 221 r.run(); 222 } 223 } 224 225 /** Runs the specified runnable immediately if called from the worker thread, otherwise it is 226 * posted on the worker thread handler. */ 227 private static void runOnWorkerThread(Runnable r) { 228 if (sWorkerThread.getThreadId() == Process.myTid()) { 229 r.run(); 230 } else { 231 // If we are not on the worker thread, then post to the worker handler 232 sWorker.post(r); 233 } 234 } 235 236 boolean canMigrateFromOldLauncherDb(Launcher launcher) { 237 return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ; 238 } 239 240 static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> items, int[] xy, 241 long screen) { 242 LauncherAppState app = LauncherAppState.getInstance(); 243 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 244 final int xCount = (int) grid.numColumns; 245 final int yCount = (int) grid.numRows; 246 boolean[][] occupied = new boolean[xCount][yCount]; 247 248 int cellX, cellY, spanX, spanY; 249 for (int i = 0; i < items.size(); ++i) { 250 final ItemInfo item = items.get(i); 251 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 252 if (item.screenId == screen) { 253 cellX = item.cellX; 254 cellY = item.cellY; 255 spanX = item.spanX; 256 spanY = item.spanY; 257 for (int x = cellX; 0 <= x && x < cellX + spanX && x < xCount; x++) { 258 for (int y = cellY; 0 <= y && y < cellY + spanY && y < yCount; y++) { 259 occupied[x][y] = true; 260 } 261 } 262 } 263 } 264 } 265 266 return CellLayout.findVacantCell(xy, 1, 1, xCount, yCount, occupied); 267 } 268 static Pair<Long, int[]> findNextAvailableIconSpace(Context context, String name, 269 Intent launchIntent, 270 int firstScreenIndex, 271 ArrayList<Long> workspaceScreens) { 272 // Lock on the app so that we don't try and get the items while apps are being added 273 LauncherAppState app = LauncherAppState.getInstance(); 274 LauncherModel model = app.getModel(); 275 boolean found = false; 276 synchronized (app) { 277 if (sWorkerThread.getThreadId() != Process.myTid()) { 278 // Flush the LauncherModel worker thread, so that if we just did another 279 // processInstallShortcut, we give it time for its shortcut to get added to the 280 // database (getItemsInLocalCoordinates reads the database) 281 model.flushWorkerThread(); 282 } 283 final ArrayList<ItemInfo> items = LauncherModel.getItemsInLocalCoordinates(context); 284 285 // Try adding to the workspace screens incrementally, starting at the default or center 286 // screen and alternating between +1, -1, +2, -2, etc. (using ~ ceil(i/2f)*(-1)^(i-1)) 287 firstScreenIndex = Math.min(firstScreenIndex, workspaceScreens.size()); 288 int count = workspaceScreens.size(); 289 for (int screen = firstScreenIndex; screen < count && !found; screen++) { 290 int[] tmpCoordinates = new int[2]; 291 if (findNextAvailableIconSpaceInScreen(items, tmpCoordinates, 292 workspaceScreens.get(screen))) { 293 // Update the Launcher db 294 return new Pair<Long, int[]>(workspaceScreens.get(screen), tmpCoordinates); 295 } 296 } 297 } 298 return null; 299 } 300 301 public void setPackageState(final String pkgName, final int state) { 302 // Process the updated package state 303 Runnable r = new Runnable() { 304 public void run() { 305 Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null; 306 if (callbacks != null) { 307 callbacks.updatePackageState(pkgName, state); 308 } 309 } 310 }; 311 mHandler.post(r); 312 } 313 314 public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) { 315 final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null; 316 317 if (allAppsApps == null) { 318 throw new RuntimeException("allAppsApps must not be null"); 319 } 320 if (allAppsApps.isEmpty()) { 321 return; 322 } 323 324 final ArrayList<AppInfo> restoredAppsFinal = new ArrayList<AppInfo>(); 325 Iterator<AppInfo> iter = allAppsApps.iterator(); 326 while (iter.hasNext()) { 327 ItemInfo a = iter.next(); 328 if (LauncherModel.appWasRestored(ctx, a.getIntent())) { 329 restoredAppsFinal.add((AppInfo) a); 330 } 331 } 332 333 // Process the newly added applications and add them to the database first 334 Runnable r = new Runnable() { 335 public void run() { 336 runOnMainThread(new Runnable() { 337 public void run() { 338 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; 339 if (callbacks == cb && cb != null) { 340 if (!restoredAppsFinal.isEmpty()) { 341 for (AppInfo info : restoredAppsFinal) { 342 final Intent intent = info.getIntent(); 343 if (intent != null) { 344 mIconCache.deletePreloadedIcon(intent.getComponent()); 345 } 346 } 347 callbacks.bindAppsUpdated(restoredAppsFinal); 348 } 349 callbacks.bindAppsAdded(null, null, null, allAppsApps); 350 } 351 } 352 }); 353 } 354 }; 355 runOnWorkerThread(r); 356 } 357 358 public void addAndBindAddedWorkspaceApps(final Context context, 359 final ArrayList<ItemInfo> workspaceApps) { 360 final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null; 361 362 if (workspaceApps == null) { 363 throw new RuntimeException("workspaceApps and allAppsApps must not be null"); 364 } 365 if (workspaceApps.isEmpty()) { 366 return; 367 } 368 // Process the newly added applications and add them to the database first 369 Runnable r = new Runnable() { 370 public void run() { 371 final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>(); 372 final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>(); 373 final ArrayList<AppInfo> restoredAppsFinal = new ArrayList<AppInfo>(); 374 375 // Get the list of workspace screens. We need to append to this list and 376 // can not use sBgWorkspaceScreens because loadWorkspace() may not have been 377 // called. 378 ArrayList<Long> workspaceScreens = new ArrayList<Long>(); 379 TreeMap<Integer, Long> orderedScreens = loadWorkspaceScreensDb(context); 380 for (Integer i : orderedScreens.keySet()) { 381 long screenId = orderedScreens.get(i); 382 workspaceScreens.add(screenId); 383 } 384 385 synchronized(sBgLock) { 386 Iterator<ItemInfo> iter = workspaceApps.iterator(); 387 while (iter.hasNext()) { 388 ItemInfo a = iter.next(); 389 final String name = a.title.toString(); 390 final Intent launchIntent = a.getIntent(); 391 392 // Short-circuit this logic if the icon exists somewhere on the workspace 393 if (LauncherModel.shortcutExists(context, name, launchIntent)) { 394 // Only InstallShortcutReceiver sends us shortcutInfos, ignore them 395 if (a instanceof AppInfo && 396 LauncherModel.appWasRestored(context, launchIntent)) { 397 restoredAppsFinal.add((AppInfo) a); 398 } 399 continue; 400 } 401 402 // Add this icon to the db, creating a new page if necessary. If there 403 // is only the empty page then we just add items to the first page. 404 // Otherwise, we add them to the next pages. 405 int startSearchPageIndex = workspaceScreens.isEmpty() ? 0 : 1; 406 Pair<Long, int[]> coords = LauncherModel.findNextAvailableIconSpace(context, 407 name, launchIntent, startSearchPageIndex, workspaceScreens); 408 if (coords == null) { 409 LauncherProvider lp = LauncherAppState.getLauncherProvider(); 410 411 // If we can't find a valid position, then just add a new screen. 412 // This takes time so we need to re-queue the add until the new 413 // page is added. Create as many screens as necessary to satisfy 414 // the startSearchPageIndex. 415 int numPagesToAdd = Math.max(1, startSearchPageIndex + 1 - 416 workspaceScreens.size()); 417 while (numPagesToAdd > 0) { 418 long screenId = lp.generateNewScreenId(); 419 // Save the screen id for binding in the workspace 420 workspaceScreens.add(screenId); 421 addedWorkspaceScreensFinal.add(screenId); 422 numPagesToAdd--; 423 } 424 425 // Find the coordinate again 426 coords = LauncherModel.findNextAvailableIconSpace(context, 427 name, launchIntent, startSearchPageIndex, workspaceScreens); 428 } 429 if (coords == null) { 430 throw new RuntimeException("Coordinates should not be null"); 431 } 432 433 ShortcutInfo shortcutInfo; 434 if (a instanceof ShortcutInfo) { 435 shortcutInfo = (ShortcutInfo) a; 436 } else if (a instanceof AppInfo) { 437 shortcutInfo = ((AppInfo) a).makeShortcut(); 438 } else { 439 throw new RuntimeException("Unexpected info type"); 440 } 441 442 // Add the shortcut to the db 443 addItemToDatabase(context, shortcutInfo, 444 LauncherSettings.Favorites.CONTAINER_DESKTOP, 445 coords.first, coords.second[0], coords.second[1], false); 446 // Save the ShortcutInfo for binding in the workspace 447 addedShortcutsFinal.add(shortcutInfo); 448 } 449 } 450 451 // Update the workspace screens 452 updateWorkspaceScreenOrder(context, workspaceScreens); 453 454 if (!addedShortcutsFinal.isEmpty()) { 455 runOnMainThread(new Runnable() { 456 public void run() { 457 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; 458 if (callbacks == cb && cb != null) { 459 final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>(); 460 final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>(); 461 if (!addedShortcutsFinal.isEmpty()) { 462 ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 1); 463 long lastScreenId = info.screenId; 464 for (ItemInfo i : addedShortcutsFinal) { 465 if (i.screenId == lastScreenId) { 466 addAnimated.add(i); 467 } else { 468 addNotAnimated.add(i); 469 } 470 } 471 } 472 callbacks.bindAppsAdded(addedWorkspaceScreensFinal, 473 addNotAnimated, addAnimated, null); 474 if (!restoredAppsFinal.isEmpty()) { 475 callbacks.bindAppsUpdated(restoredAppsFinal); 476 } 477 } 478 } 479 }); 480 } 481 } 482 }; 483 runOnWorkerThread(r); 484 } 485 486 public Bitmap getFallbackIcon() { 487 if (mDefaultIcon == null) { 488 final Context context = LauncherAppState.getInstance().getContext(); 489 mDefaultIcon = Utilities.createIconBitmap( 490 mIconCache.getFullResDefaultActivityIcon(), context); 491 } 492 return Bitmap.createBitmap(mDefaultIcon); 493 } 494 495 public void unbindItemInfosAndClearQueuedBindRunnables() { 496 if (sWorkerThread.getThreadId() == Process.myTid()) { 497 throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " + 498 "main thread"); 499 } 500 501 // Clear any deferred bind runnables 502 mDeferredBindRunnables.clear(); 503 // Remove any queued bind runnables 504 mHandler.cancelAllRunnablesOfType(MAIN_THREAD_BINDING_RUNNABLE); 505 // Unbind all the workspace items 506 unbindWorkspaceItemsOnMainThread(); 507 } 508 509 /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */ 510 void unbindWorkspaceItemsOnMainThread() { 511 // Ensure that we don't use the same workspace items data structure on the main thread 512 // by making a copy of workspace items first. 513 final ArrayList<ItemInfo> tmpWorkspaceItems = new ArrayList<ItemInfo>(); 514 final ArrayList<ItemInfo> tmpAppWidgets = new ArrayList<ItemInfo>(); 515 synchronized (sBgLock) { 516 tmpWorkspaceItems.addAll(sBgWorkspaceItems); 517 tmpAppWidgets.addAll(sBgAppWidgets); 518 } 519 Runnable r = new Runnable() { 520 @Override 521 public void run() { 522 for (ItemInfo item : tmpWorkspaceItems) { 523 item.unbind(); 524 } 525 for (ItemInfo item : tmpAppWidgets) { 526 item.unbind(); 527 } 528 } 529 }; 530 runOnMainThread(r); 531 } 532 533 /** 534 * Adds an item to the DB if it was not created previously, or move it to a new 535 * <container, screen, cellX, cellY> 536 */ 537 static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container, 538 long screenId, int cellX, int cellY) { 539 if (item.container == ItemInfo.NO_ID) { 540 // From all apps 541 addItemToDatabase(context, item, container, screenId, cellX, cellY, false); 542 } else { 543 // From somewhere else 544 moveItemInDatabase(context, item, container, screenId, cellX, cellY); 545 } 546 } 547 548 static void checkItemInfoLocked( 549 final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) { 550 ItemInfo modelItem = sBgItemsIdMap.get(itemId); 551 if (modelItem != null && item != modelItem) { 552 // check all the data is consistent 553 if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) { 554 ShortcutInfo modelShortcut = (ShortcutInfo) modelItem; 555 ShortcutInfo shortcut = (ShortcutInfo) item; 556 if (modelShortcut.title.toString().equals(shortcut.title.toString()) && 557 modelShortcut.intent.filterEquals(shortcut.intent) && 558 modelShortcut.id == shortcut.id && 559 modelShortcut.itemType == shortcut.itemType && 560 modelShortcut.container == shortcut.container && 561 modelShortcut.screenId == shortcut.screenId && 562 modelShortcut.cellX == shortcut.cellX && 563 modelShortcut.cellY == shortcut.cellY && 564 modelShortcut.spanX == shortcut.spanX && 565 modelShortcut.spanY == shortcut.spanY && 566 ((modelShortcut.dropPos == null && shortcut.dropPos == null) || 567 (modelShortcut.dropPos != null && 568 shortcut.dropPos != null && 569 modelShortcut.dropPos[0] == shortcut.dropPos[0] && 570 modelShortcut.dropPos[1] == shortcut.dropPos[1]))) { 571 // For all intents and purposes, this is the same object 572 return; 573 } 574 } 575 576 // the modelItem needs to match up perfectly with item if our model is 577 // to be consistent with the database-- for now, just require 578 // modelItem == item or the equality check above 579 String msg = "item: " + ((item != null) ? item.toString() : "null") + 580 "modelItem: " + 581 ((modelItem != null) ? modelItem.toString() : "null") + 582 "Error: ItemInfo passed to checkItemInfo doesn't match original"; 583 RuntimeException e = new RuntimeException(msg); 584 if (stackTrace != null) { 585 e.setStackTrace(stackTrace); 586 } 587 throw e; 588 } 589 } 590 591 static void checkItemInfo(final ItemInfo item) { 592 final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); 593 final long itemId = item.id; 594 Runnable r = new Runnable() { 595 public void run() { 596 synchronized (sBgLock) { 597 checkItemInfoLocked(itemId, item, stackTrace); 598 } 599 } 600 }; 601 runOnWorkerThread(r); 602 } 603 604 static void updateItemInDatabaseHelper(Context context, final ContentValues values, 605 final ItemInfo item, final String callingFunction) { 606 final long itemId = item.id; 607 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false); 608 final ContentResolver cr = context.getContentResolver(); 609 610 final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); 611 Runnable r = new Runnable() { 612 public void run() { 613 cr.update(uri, values, null, null); 614 updateItemArrays(item, itemId, stackTrace); 615 } 616 }; 617 runOnWorkerThread(r); 618 } 619 620 static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList, 621 final ArrayList<ItemInfo> items, final String callingFunction) { 622 final ContentResolver cr = context.getContentResolver(); 623 624 final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); 625 Runnable r = new Runnable() { 626 public void run() { 627 ArrayList<ContentProviderOperation> ops = 628 new ArrayList<ContentProviderOperation>(); 629 int count = items.size(); 630 for (int i = 0; i < count; i++) { 631 ItemInfo item = items.get(i); 632 final long itemId = item.id; 633 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false); 634 ContentValues values = valuesList.get(i); 635 636 ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build()); 637 updateItemArrays(item, itemId, stackTrace); 638 639 } 640 try { 641 cr.applyBatch(LauncherProvider.AUTHORITY, ops); 642 } catch (Exception e) { 643 e.printStackTrace(); 644 } 645 } 646 }; 647 runOnWorkerThread(r); 648 } 649 650 static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) { 651 // Lock on mBgLock *after* the db operation 652 synchronized (sBgLock) { 653 checkItemInfoLocked(itemId, item, stackTrace); 654 655 if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP && 656 item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 657 // Item is in a folder, make sure this folder exists 658 if (!sBgFolders.containsKey(item.container)) { 659 // An items container is being set to a that of an item which is not in 660 // the list of Folders. 661 String msg = "item: " + item + " container being set to: " + 662 item.container + ", not in the list of folders"; 663 Log.e(TAG, msg); 664 } 665 } 666 667 // Items are added/removed from the corresponding FolderInfo elsewhere, such 668 // as in Workspace.onDrop. Here, we just add/remove them from the list of items 669 // that are on the desktop, as appropriate 670 ItemInfo modelItem = sBgItemsIdMap.get(itemId); 671 if (modelItem != null && 672 (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || 673 modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) { 674 switch (modelItem.itemType) { 675 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 676 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 677 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 678 if (!sBgWorkspaceItems.contains(modelItem)) { 679 sBgWorkspaceItems.add(modelItem); 680 } 681 break; 682 default: 683 break; 684 } 685 } else { 686 sBgWorkspaceItems.remove(modelItem); 687 } 688 } 689 } 690 691 public void flushWorkerThread() { 692 mFlushingWorkerThread = true; 693 Runnable waiter = new Runnable() { 694 public void run() { 695 synchronized (this) { 696 notifyAll(); 697 mFlushingWorkerThread = false; 698 } 699 } 700 }; 701 702 synchronized(waiter) { 703 runOnWorkerThread(waiter); 704 if (mLoaderTask != null) { 705 synchronized(mLoaderTask) { 706 mLoaderTask.notify(); 707 } 708 } 709 boolean success = false; 710 while (!success) { 711 try { 712 waiter.wait(); 713 success = true; 714 } catch (InterruptedException e) { 715 } 716 } 717 } 718 } 719 720 /** 721 * Move an item in the DB to a new <container, screen, cellX, cellY> 722 */ 723 static void moveItemInDatabase(Context context, final ItemInfo item, final long container, 724 final long screenId, final int cellX, final int cellY) { 725 item.container = container; 726 item.cellX = cellX; 727 item.cellY = cellY; 728 729 // We store hotseat items in canonical form which is this orientation invariant position 730 // in the hotseat 731 if (context instanceof Launcher && screenId < 0 && 732 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 733 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY); 734 } else { 735 item.screenId = screenId; 736 } 737 738 final ContentValues values = new ContentValues(); 739 values.put(LauncherSettings.Favorites.CONTAINER, item.container); 740 values.put(LauncherSettings.Favorites.CELLX, item.cellX); 741 values.put(LauncherSettings.Favorites.CELLY, item.cellY); 742 values.put(LauncherSettings.Favorites.SCREEN, item.screenId); 743 744 updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase"); 745 } 746 747 /** 748 * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the 749 * cellX, cellY have already been updated on the ItemInfos. 750 */ 751 static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items, 752 final long container, final int screen) { 753 754 ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>(); 755 int count = items.size(); 756 757 for (int i = 0; i < count; i++) { 758 ItemInfo item = items.get(i); 759 item.container = container; 760 761 // We store hotseat items in canonical form which is this orientation invariant position 762 // in the hotseat 763 if (context instanceof Launcher && screen < 0 && 764 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 765 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(item.cellX, 766 item.cellY); 767 } else { 768 item.screenId = screen; 769 } 770 771 final ContentValues values = new ContentValues(); 772 values.put(LauncherSettings.Favorites.CONTAINER, item.container); 773 values.put(LauncherSettings.Favorites.CELLX, item.cellX); 774 values.put(LauncherSettings.Favorites.CELLY, item.cellY); 775 values.put(LauncherSettings.Favorites.SCREEN, item.screenId); 776 777 contentValues.add(values); 778 } 779 updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase"); 780 } 781 782 /** 783 * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY> 784 */ 785 static void modifyItemInDatabase(Context context, final ItemInfo item, final long container, 786 final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) { 787 item.container = container; 788 item.cellX = cellX; 789 item.cellY = cellY; 790 item.spanX = spanX; 791 item.spanY = spanY; 792 793 // We store hotseat items in canonical form which is this orientation invariant position 794 // in the hotseat 795 if (context instanceof Launcher && screenId < 0 && 796 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 797 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY); 798 } else { 799 item.screenId = screenId; 800 } 801 802 final ContentValues values = new ContentValues(); 803 values.put(LauncherSettings.Favorites.CONTAINER, item.container); 804 values.put(LauncherSettings.Favorites.CELLX, item.cellX); 805 values.put(LauncherSettings.Favorites.CELLY, item.cellY); 806 values.put(LauncherSettings.Favorites.SPANX, item.spanX); 807 values.put(LauncherSettings.Favorites.SPANY, item.spanY); 808 values.put(LauncherSettings.Favorites.SCREEN, item.screenId); 809 810 updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase"); 811 } 812 813 /** 814 * Update an item to the database in a specified container. 815 */ 816 static void updateItemInDatabase(Context context, final ItemInfo item) { 817 final ContentValues values = new ContentValues(); 818 item.onAddToDatabase(values); 819 item.updateValuesWithCoordinates(values, item.cellX, item.cellY); 820 updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase"); 821 } 822 823 /** 824 * Returns true if the shortcuts already exists in the database. 825 * we identify a shortcut by its title and intent. 826 */ 827 static boolean shortcutExists(Context context, String title, Intent intent) { 828 final ContentResolver cr = context.getContentResolver(); 829 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, 830 new String[] { "title", "intent" }, "title=? and intent=?", 831 new String[] { title, intent.toUri(0) }, null); 832 boolean result = false; 833 try { 834 result = c.moveToFirst(); 835 } finally { 836 c.close(); 837 } 838 return result; 839 } 840 841 /** 842 * Returns true if the shortcuts already exists in the database. 843 * we identify a shortcut by the component name of the intent. 844 */ 845 static boolean appWasRestored(Context context, Intent intent) { 846 final ContentResolver cr = context.getContentResolver(); 847 final ComponentName component = intent.getComponent(); 848 if (component == null) { 849 return false; 850 } 851 String componentName = component.flattenToString(); 852 final String where = "intent glob \"*component=" + componentName + "*\" and restored = 1"; 853 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, 854 new String[]{"intent", "restored"}, where, null, null); 855 boolean result = false; 856 try { 857 result = c.moveToFirst(); 858 } finally { 859 c.close(); 860 } 861 Log.d(TAG, "shortcutWasRestored is " + result + " for " + componentName); 862 return result; 863 } 864 865 /** 866 * Returns an ItemInfo array containing all the items in the LauncherModel. 867 * The ItemInfo.id is not set through this function. 868 */ 869 static ArrayList<ItemInfo> getItemsInLocalCoordinates(Context context) { 870 ArrayList<ItemInfo> items = new ArrayList<ItemInfo>(); 871 final ContentResolver cr = context.getContentResolver(); 872 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, new String[] { 873 LauncherSettings.Favorites.ITEM_TYPE, LauncherSettings.Favorites.CONTAINER, 874 LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.CELLX, LauncherSettings.Favorites.CELLY, 875 LauncherSettings.Favorites.SPANX, LauncherSettings.Favorites.SPANY }, null, null, null); 876 877 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); 878 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); 879 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); 880 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); 881 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); 882 final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX); 883 final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY); 884 885 try { 886 while (c.moveToNext()) { 887 ItemInfo item = new ItemInfo(); 888 item.cellX = c.getInt(cellXIndex); 889 item.cellY = c.getInt(cellYIndex); 890 item.spanX = Math.max(1, c.getInt(spanXIndex)); 891 item.spanY = Math.max(1, c.getInt(spanYIndex)); 892 item.container = c.getInt(containerIndex); 893 item.itemType = c.getInt(itemTypeIndex); 894 item.screenId = c.getInt(screenIndex); 895 896 items.add(item); 897 } 898 } catch (Exception e) { 899 items.clear(); 900 } finally { 901 c.close(); 902 } 903 904 return items; 905 } 906 907 /** 908 * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList. 909 */ 910 FolderInfo getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id) { 911 final ContentResolver cr = context.getContentResolver(); 912 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null, 913 "_id=? and (itemType=? or itemType=?)", 914 new String[] { String.valueOf(id), 915 String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)}, null); 916 917 try { 918 if (c.moveToFirst()) { 919 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); 920 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); 921 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); 922 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); 923 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); 924 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); 925 926 FolderInfo folderInfo = null; 927 switch (c.getInt(itemTypeIndex)) { 928 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 929 folderInfo = findOrMakeFolder(folderList, id); 930 break; 931 } 932 933 folderInfo.title = c.getString(titleIndex); 934 folderInfo.id = id; 935 folderInfo.container = c.getInt(containerIndex); 936 folderInfo.screenId = c.getInt(screenIndex); 937 folderInfo.cellX = c.getInt(cellXIndex); 938 folderInfo.cellY = c.getInt(cellYIndex); 939 940 return folderInfo; 941 } 942 } finally { 943 c.close(); 944 } 945 946 return null; 947 } 948 949 /** 950 * Add an item to the database in a specified container. Sets the container, screen, cellX and 951 * cellY fields of the item. Also assigns an ID to the item. 952 */ 953 static void addItemToDatabase(Context context, final ItemInfo item, final long container, 954 final long screenId, final int cellX, final int cellY, final boolean notify) { 955 item.container = container; 956 item.cellX = cellX; 957 item.cellY = cellY; 958 // We store hotseat items in canonical form which is this orientation invariant position 959 // in the hotseat 960 if (context instanceof Launcher && screenId < 0 && 961 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 962 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY); 963 } else { 964 item.screenId = screenId; 965 } 966 967 final ContentValues values = new ContentValues(); 968 final ContentResolver cr = context.getContentResolver(); 969 item.onAddToDatabase(values); 970 971 item.id = LauncherAppState.getLauncherProvider().generateNewItemId(); 972 values.put(LauncherSettings.Favorites._ID, item.id); 973 item.updateValuesWithCoordinates(values, item.cellX, item.cellY); 974 975 final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); 976 Runnable r = new Runnable() { 977 public void run() { 978 cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI : 979 LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values); 980 981 // Lock on mBgLock *after* the db operation 982 synchronized (sBgLock) { 983 checkItemInfoLocked(item.id, item, stackTrace); 984 sBgItemsIdMap.put(item.id, item); 985 switch (item.itemType) { 986 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 987 sBgFolders.put(item.id, (FolderInfo) item); 988 // Fall through 989 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 990 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 991 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP || 992 item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 993 sBgWorkspaceItems.add(item); 994 } else { 995 if (!sBgFolders.containsKey(item.container)) { 996 // Adding an item to a folder that doesn't exist. 997 String msg = "adding item: " + item + " to a folder that " + 998 " doesn't exist"; 999 Log.e(TAG, msg); 1000 } 1001 } 1002 break; 1003 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 1004 sBgAppWidgets.add((LauncherAppWidgetInfo) item); 1005 break; 1006 } 1007 } 1008 } 1009 }; 1010 runOnWorkerThread(r); 1011 } 1012 1013 /** 1014 * Creates a new unique child id, for a given cell span across all layouts. 1015 */ 1016 static int getCellLayoutChildId( 1017 long container, long screen, int localCellX, int localCellY, int spanX, int spanY) { 1018 return (((int) container & 0xFF) << 24) 1019 | ((int) screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF); 1020 } 1021 1022 /** 1023 * Removes the specified item from the database 1024 * @param context 1025 * @param item 1026 */ 1027 static void deleteItemFromDatabase(Context context, final ItemInfo item) { 1028 final ContentResolver cr = context.getContentResolver(); 1029 final Uri uriToDelete = LauncherSettings.Favorites.getContentUri(item.id, false); 1030 1031 Runnable r = new Runnable() { 1032 public void run() { 1033 cr.delete(uriToDelete, null, null); 1034 1035 // Lock on mBgLock *after* the db operation 1036 synchronized (sBgLock) { 1037 switch (item.itemType) { 1038 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 1039 sBgFolders.remove(item.id); 1040 for (ItemInfo info: sBgItemsIdMap.values()) { 1041 if (info.container == item.id) { 1042 // We are deleting a folder which still contains items that 1043 // think they are contained by that folder. 1044 String msg = "deleting a folder (" + item + ") which still " + 1045 "contains items (" + info + ")"; 1046 Log.e(TAG, msg); 1047 } 1048 } 1049 sBgWorkspaceItems.remove(item); 1050 break; 1051 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 1052 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 1053 sBgWorkspaceItems.remove(item); 1054 break; 1055 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 1056 sBgAppWidgets.remove((LauncherAppWidgetInfo) item); 1057 break; 1058 } 1059 sBgItemsIdMap.remove(item.id); 1060 sBgDbIconCache.remove(item); 1061 } 1062 } 1063 }; 1064 runOnWorkerThread(r); 1065 } 1066 1067 /** 1068 * Update the order of the workspace screens in the database. The array list contains 1069 * a list of screen ids in the order that they should appear. 1070 */ 1071 void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) { 1072 // Log to disk 1073 Launcher.addDumpLog(TAG, "11683562 - updateWorkspaceScreenOrder()", true); 1074 Launcher.addDumpLog(TAG, "11683562 - screens: " + TextUtils.join(", ", screens), true); 1075 1076 final ArrayList<Long> screensCopy = new ArrayList<Long>(screens); 1077 final ContentResolver cr = context.getContentResolver(); 1078 final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI; 1079 1080 // Remove any negative screen ids -- these aren't persisted 1081 Iterator<Long> iter = screensCopy.iterator(); 1082 while (iter.hasNext()) { 1083 long id = iter.next(); 1084 if (id < 0) { 1085 iter.remove(); 1086 } 1087 } 1088 1089 Runnable r = new Runnable() { 1090 @Override 1091 public void run() { 1092 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); 1093 // Clear the table 1094 ops.add(ContentProviderOperation.newDelete(uri).build()); 1095 int count = screensCopy.size(); 1096 for (int i = 0; i < count; i++) { 1097 ContentValues v = new ContentValues(); 1098 long screenId = screensCopy.get(i); 1099 v.put(LauncherSettings.WorkspaceScreens._ID, screenId); 1100 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i); 1101 ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build()); 1102 } 1103 1104 try { 1105 cr.applyBatch(LauncherProvider.AUTHORITY, ops); 1106 } catch (Exception ex) { 1107 throw new RuntimeException(ex); 1108 } 1109 1110 synchronized (sBgLock) { 1111 sBgWorkspaceScreens.clear(); 1112 sBgWorkspaceScreens.addAll(screensCopy); 1113 } 1114 } 1115 }; 1116 runOnWorkerThread(r); 1117 } 1118 1119 /** 1120 * Remove the contents of the specified folder from the database 1121 */ 1122 static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) { 1123 final ContentResolver cr = context.getContentResolver(); 1124 1125 Runnable r = new Runnable() { 1126 public void run() { 1127 cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null); 1128 // Lock on mBgLock *after* the db operation 1129 synchronized (sBgLock) { 1130 sBgItemsIdMap.remove(info.id); 1131 sBgFolders.remove(info.id); 1132 sBgDbIconCache.remove(info); 1133 sBgWorkspaceItems.remove(info); 1134 } 1135 1136 cr.delete(LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, 1137 LauncherSettings.Favorites.CONTAINER + "=" + info.id, null); 1138 // Lock on mBgLock *after* the db operation 1139 synchronized (sBgLock) { 1140 for (ItemInfo childInfo : info.contents) { 1141 sBgItemsIdMap.remove(childInfo.id); 1142 sBgDbIconCache.remove(childInfo); 1143 } 1144 } 1145 } 1146 }; 1147 runOnWorkerThread(r); 1148 } 1149 1150 /** 1151 * Set this as the current Launcher activity object for the loader. 1152 */ 1153 public void initialize(Callbacks callbacks) { 1154 synchronized (mLock) { 1155 mCallbacks = new WeakReference<Callbacks>(callbacks); 1156 } 1157 } 1158 1159 /** 1160 * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and 1161 * ACTION_PACKAGE_CHANGED. 1162 */ 1163 @Override 1164 public void onReceive(Context context, Intent intent) { 1165 if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent); 1166 1167 final String action = intent.getAction(); 1168 1169 if (Intent.ACTION_PACKAGE_CHANGED.equals(action) 1170 || Intent.ACTION_PACKAGE_REMOVED.equals(action) 1171 || Intent.ACTION_PACKAGE_ADDED.equals(action)) { 1172 final String packageName = intent.getData().getSchemeSpecificPart(); 1173 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); 1174 1175 int op = PackageUpdatedTask.OP_NONE; 1176 1177 if (packageName == null || packageName.length() == 0) { 1178 // they sent us a bad intent 1179 return; 1180 } 1181 1182 if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) { 1183 op = PackageUpdatedTask.OP_UPDATE; 1184 } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { 1185 if (!replacing) { 1186 op = PackageUpdatedTask.OP_REMOVE; 1187 } 1188 // else, we are replacing the package, so a PACKAGE_ADDED will be sent 1189 // later, we will update the package at this time 1190 } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { 1191 if (!replacing) { 1192 op = PackageUpdatedTask.OP_ADD; 1193 } else { 1194 op = PackageUpdatedTask.OP_UPDATE; 1195 } 1196 } 1197 1198 if (op != PackageUpdatedTask.OP_NONE) { 1199 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName })); 1200 } 1201 1202 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { 1203 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); 1204 String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); 1205 if (!replacing) { 1206 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packages)); 1207 if (mAppsCanBeOnRemoveableStorage) { 1208 // Only rebind if we support removable storage. It catches the case where 1209 // apps on the external sd card need to be reloaded 1210 startLoaderFromBackground(); 1211 } 1212 } else { 1213 // If we are replacing then just update the packages in the list 1214 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, 1215 packages)); 1216 } 1217 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { 1218 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); 1219 if (!replacing) { 1220 String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); 1221 enqueuePackageUpdated(new PackageUpdatedTask( 1222 PackageUpdatedTask.OP_UNAVAILABLE, packages)); 1223 } 1224 // else, we are replacing the packages, so ignore this event and wait for 1225 // EXTERNAL_APPLICATIONS_AVAILABLE to update the packages at that time 1226 } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) { 1227 // If we have changed locale we need to clear out the labels in all apps/workspace. 1228 forceReload(); 1229 } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { 1230 // Check if configuration change was an mcc/mnc change which would affect app resources 1231 // and we would need to clear out the labels in all apps/workspace. Same handling as 1232 // above for ACTION_LOCALE_CHANGED 1233 Configuration currentConfig = context.getResources().getConfiguration(); 1234 if (mPreviousConfigMcc != currentConfig.mcc) { 1235 Log.d(TAG, "Reload apps on config change. curr_mcc:" 1236 + currentConfig.mcc + " prevmcc:" + mPreviousConfigMcc); 1237 forceReload(); 1238 } 1239 // Update previousConfig 1240 mPreviousConfigMcc = currentConfig.mcc; 1241 } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) || 1242 SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) { 1243 if (mCallbacks != null) { 1244 Callbacks callbacks = mCallbacks.get(); 1245 if (callbacks != null) { 1246 callbacks.bindSearchablesChanged(); 1247 } 1248 } 1249 } 1250 } 1251 1252 private void forceReload() { 1253 resetLoadedState(true, true); 1254 1255 // Do this here because if the launcher activity is running it will be restarted. 1256 // If it's not running startLoaderFromBackground will merely tell it that it needs 1257 // to reload. 1258 startLoaderFromBackground(); 1259 } 1260 1261 public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) { 1262 synchronized (mLock) { 1263 // Stop any existing loaders first, so they don't set mAllAppsLoaded or 1264 // mWorkspaceLoaded to true later 1265 stopLoaderLocked(); 1266 if (resetAllAppsLoaded) mAllAppsLoaded = false; 1267 if (resetWorkspaceLoaded) mWorkspaceLoaded = false; 1268 } 1269 } 1270 1271 /** 1272 * When the launcher is in the background, it's possible for it to miss paired 1273 * configuration changes. So whenever we trigger the loader from the background 1274 * tell the launcher that it needs to re-run the loader when it comes back instead 1275 * of doing it now. 1276 */ 1277 public void startLoaderFromBackground() { 1278 boolean runLoader = false; 1279 if (mCallbacks != null) { 1280 Callbacks callbacks = mCallbacks.get(); 1281 if (callbacks != null) { 1282 // Only actually run the loader if they're not paused. 1283 if (!callbacks.setLoadOnResume()) { 1284 runLoader = true; 1285 } 1286 } 1287 } 1288 if (runLoader) { 1289 startLoader(false, PagedView.INVALID_RESTORE_PAGE); 1290 } 1291 } 1292 1293 // If there is already a loader task running, tell it to stop. 1294 // returns true if isLaunching() was true on the old task 1295 private boolean stopLoaderLocked() { 1296 boolean isLaunching = false; 1297 LoaderTask oldTask = mLoaderTask; 1298 if (oldTask != null) { 1299 if (oldTask.isLaunching()) { 1300 isLaunching = true; 1301 } 1302 oldTask.stopLocked(); 1303 } 1304 return isLaunching; 1305 } 1306 1307 public void startLoader(boolean isLaunching, int synchronousBindPage) { 1308 startLoader(isLaunching, synchronousBindPage, LOADER_FLAG_NONE); 1309 } 1310 1311 public void startLoader(boolean isLaunching, int synchronousBindPage, int loadFlags) { 1312 synchronized (mLock) { 1313 if (DEBUG_LOADERS) { 1314 Log.d(TAG, "startLoader isLaunching=" + isLaunching); 1315 } 1316 1317 // Clear any deferred bind-runnables from the synchronized load process 1318 // We must do this before any loading/binding is scheduled below. 1319 mDeferredBindRunnables.clear(); 1320 1321 // Don't bother to start the thread if we know it's not going to do anything 1322 if (mCallbacks != null && mCallbacks.get() != null) { 1323 // If there is already one running, tell it to stop. 1324 // also, don't downgrade isLaunching if we're already running 1325 isLaunching = isLaunching || stopLoaderLocked(); 1326 mLoaderTask = new LoaderTask(mApp.getContext(), isLaunching, loadFlags); 1327 if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE 1328 && mAllAppsLoaded && mWorkspaceLoaded) { 1329 mLoaderTask.runBindSynchronousPage(synchronousBindPage); 1330 } else { 1331 sWorkerThread.setPriority(Thread.NORM_PRIORITY); 1332 sWorker.post(mLoaderTask); 1333 } 1334 } 1335 } 1336 } 1337 1338 void bindRemainingSynchronousPages() { 1339 // Post the remaining side pages to be loaded 1340 if (!mDeferredBindRunnables.isEmpty()) { 1341 for (final Runnable r : mDeferredBindRunnables) { 1342 mHandler.post(r, MAIN_THREAD_BINDING_RUNNABLE); 1343 } 1344 mDeferredBindRunnables.clear(); 1345 } 1346 } 1347 1348 public void stopLoader() { 1349 synchronized (mLock) { 1350 if (mLoaderTask != null) { 1351 mLoaderTask.stopLocked(); 1352 } 1353 } 1354 } 1355 1356 /** Loads the workspace screens db into a map of Rank -> ScreenId */ 1357 private static TreeMap<Integer, Long> loadWorkspaceScreensDb(Context context) { 1358 final ContentResolver contentResolver = context.getContentResolver(); 1359 final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI; 1360 final Cursor sc = contentResolver.query(screensUri, null, null, null, null); 1361 TreeMap<Integer, Long> orderedScreens = new TreeMap<Integer, Long>(); 1362 1363 try { 1364 final int idIndex = sc.getColumnIndexOrThrow( 1365 LauncherSettings.WorkspaceScreens._ID); 1366 final int rankIndex = sc.getColumnIndexOrThrow( 1367 LauncherSettings.WorkspaceScreens.SCREEN_RANK); 1368 while (sc.moveToNext()) { 1369 try { 1370 long screenId = sc.getLong(idIndex); 1371 int rank = sc.getInt(rankIndex); 1372 orderedScreens.put(rank, screenId); 1373 } catch (Exception e) { 1374 Launcher.addDumpLog(TAG, "Desktop items loading interrupted - invalid screens: " + e, true); 1375 } 1376 } 1377 } finally { 1378 sc.close(); 1379 } 1380 1381 // Log to disk 1382 Launcher.addDumpLog(TAG, "11683562 - loadWorkspaceScreensDb()", true); 1383 ArrayList<String> orderedScreensPairs= new ArrayList<String>(); 1384 for (Integer i : orderedScreens.keySet()) { 1385 orderedScreensPairs.add("{ " + i + ": " + orderedScreens.get(i) + " }"); 1386 } 1387 Launcher.addDumpLog(TAG, "11683562 - screens: " + 1388 TextUtils.join(", ", orderedScreensPairs), true); 1389 return orderedScreens; 1390 } 1391 1392 public boolean isAllAppsLoaded() { 1393 return mAllAppsLoaded; 1394 } 1395 1396 boolean isLoadingWorkspace() { 1397 synchronized (mLock) { 1398 if (mLoaderTask != null) { 1399 return mLoaderTask.isLoadingWorkspace(); 1400 } 1401 } 1402 return false; 1403 } 1404 1405 /** 1406 * Runnable for the thread that loads the contents of the launcher: 1407 * - workspace icons 1408 * - widgets 1409 * - all apps icons 1410 */ 1411 private class LoaderTask implements Runnable { 1412 private Context mContext; 1413 private boolean mIsLaunching; 1414 private boolean mIsLoadingAndBindingWorkspace; 1415 private boolean mStopped; 1416 private boolean mLoadAndBindStepFinished; 1417 private int mFlags; 1418 1419 private HashMap<Object, CharSequence> mLabelCache; 1420 1421 LoaderTask(Context context, boolean isLaunching, int flags) { 1422 mContext = context; 1423 mIsLaunching = isLaunching; 1424 mLabelCache = new HashMap<Object, CharSequence>(); 1425 mFlags = flags; 1426 } 1427 1428 boolean isLaunching() { 1429 return mIsLaunching; 1430 } 1431 1432 boolean isLoadingWorkspace() { 1433 return mIsLoadingAndBindingWorkspace; 1434 } 1435 1436 /** Returns whether this is an upgrade path */ 1437 private boolean loadAndBindWorkspace() { 1438 mIsLoadingAndBindingWorkspace = true; 1439 1440 // Load the workspace 1441 if (DEBUG_LOADERS) { 1442 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded); 1443 } 1444 1445 boolean isUpgradePath = false; 1446 if (!mWorkspaceLoaded) { 1447 isUpgradePath = loadWorkspace(); 1448 synchronized (LoaderTask.this) { 1449 if (mStopped) { 1450 return isUpgradePath; 1451 } 1452 mWorkspaceLoaded = true; 1453 } 1454 } 1455 1456 // Bind the workspace 1457 bindWorkspace(-1, isUpgradePath); 1458 return isUpgradePath; 1459 } 1460 1461 private void waitForIdle() { 1462 // Wait until the either we're stopped or the other threads are done. 1463 // This way we don't start loading all apps until the workspace has settled 1464 // down. 1465 synchronized (LoaderTask.this) { 1466 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 1467 1468 mHandler.postIdle(new Runnable() { 1469 public void run() { 1470 synchronized (LoaderTask.this) { 1471 mLoadAndBindStepFinished = true; 1472 if (DEBUG_LOADERS) { 1473 Log.d(TAG, "done with previous binding step"); 1474 } 1475 LoaderTask.this.notify(); 1476 } 1477 } 1478 }); 1479 1480 while (!mStopped && !mLoadAndBindStepFinished && !mFlushingWorkerThread) { 1481 try { 1482 // Just in case mFlushingWorkerThread changes but we aren't woken up, 1483 // wait no longer than 1sec at a time 1484 this.wait(1000); 1485 } catch (InterruptedException ex) { 1486 // Ignore 1487 } 1488 } 1489 if (DEBUG_LOADERS) { 1490 Log.d(TAG, "waited " 1491 + (SystemClock.uptimeMillis()-workspaceWaitTime) 1492 + "ms for previous step to finish binding"); 1493 } 1494 } 1495 } 1496 1497 void runBindSynchronousPage(int synchronousBindPage) { 1498 if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) { 1499 // Ensure that we have a valid page index to load synchronously 1500 throw new RuntimeException("Should not call runBindSynchronousPage() without " + 1501 "valid page index"); 1502 } 1503 if (!mAllAppsLoaded || !mWorkspaceLoaded) { 1504 // Ensure that we don't try and bind a specified page when the pages have not been 1505 // loaded already (we should load everything asynchronously in that case) 1506 throw new RuntimeException("Expecting AllApps and Workspace to be loaded"); 1507 } 1508 synchronized (mLock) { 1509 if (mIsLoaderTaskRunning) { 1510 // Ensure that we are never running the background loading at this point since 1511 // we also touch the background collections 1512 throw new RuntimeException("Error! Background loading is already running"); 1513 } 1514 } 1515 1516 // XXX: Throw an exception if we are already loading (since we touch the worker thread 1517 // data structures, we can't allow any other thread to touch that data, but because 1518 // this call is synchronous, we can get away with not locking). 1519 1520 // The LauncherModel is static in the LauncherAppState and mHandler may have queued 1521 // operations from the previous activity. We need to ensure that all queued operations 1522 // are executed before any synchronous binding work is done. 1523 mHandler.flush(); 1524 1525 // Divide the set of loaded items into those that we are binding synchronously, and 1526 // everything else that is to be bound normally (asynchronously). 1527 bindWorkspace(synchronousBindPage, false); 1528 // XXX: For now, continue posting the binding of AllApps as there are other issues that 1529 // arise from that. 1530 onlyBindAllApps(); 1531 } 1532 1533 public void run() { 1534 boolean isUpgrade = false; 1535 1536 synchronized (mLock) { 1537 mIsLoaderTaskRunning = true; 1538 } 1539 // Optimize for end-user experience: if the Launcher is up and // running with the 1540 // All Apps interface in the foreground, load All Apps first. Otherwise, load the 1541 // workspace first (default). 1542 keep_running: { 1543 // Elevate priority when Home launches for the first time to avoid 1544 // starving at boot time. Staring at a blank home is not cool. 1545 synchronized (mLock) { 1546 if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " + 1547 (mIsLaunching ? "DEFAULT" : "BACKGROUND")); 1548 android.os.Process.setThreadPriority(mIsLaunching 1549 ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND); 1550 } 1551 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace"); 1552 isUpgrade = loadAndBindWorkspace(); 1553 1554 if (mStopped) { 1555 break keep_running; 1556 } 1557 1558 // Whew! Hard work done. Slow us down, and wait until the UI thread has 1559 // settled down. 1560 synchronized (mLock) { 1561 if (mIsLaunching) { 1562 if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND"); 1563 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 1564 } 1565 } 1566 waitForIdle(); 1567 1568 // second step 1569 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps"); 1570 loadAndBindAllApps(); 1571 1572 // Restore the default thread priority after we are done loading items 1573 synchronized (mLock) { 1574 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); 1575 } 1576 } 1577 1578 // Update the saved icons if necessary 1579 if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons"); 1580 synchronized (sBgLock) { 1581 for (Object key : sBgDbIconCache.keySet()) { 1582 updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key)); 1583 } 1584 sBgDbIconCache.clear(); 1585 } 1586 1587 if (LauncherAppState.isDisableAllApps()) { 1588 // Ensure that all the applications that are in the system are 1589 // represented on the home screen. 1590 if (!UPGRADE_USE_MORE_APPS_FOLDER || !isUpgrade) { 1591 verifyApplications(); 1592 } 1593 } 1594 1595 // Clear out this reference, otherwise we end up holding it until all of the 1596 // callback runnables are done. 1597 mContext = null; 1598 1599 synchronized (mLock) { 1600 // If we are still the last one to be scheduled, remove ourselves. 1601 if (mLoaderTask == this) { 1602 mLoaderTask = null; 1603 } 1604 mIsLoaderTaskRunning = false; 1605 } 1606 } 1607 1608 public void stopLocked() { 1609 synchronized (LoaderTask.this) { 1610 mStopped = true; 1611 this.notify(); 1612 } 1613 } 1614 1615 /** 1616 * Gets the callbacks object. If we've been stopped, or if the launcher object 1617 * has somehow been garbage collected, return null instead. Pass in the Callbacks 1618 * object that was around when the deferred message was scheduled, and if there's 1619 * a new Callbacks object around then also return null. This will save us from 1620 * calling onto it with data that will be ignored. 1621 */ 1622 Callbacks tryGetCallbacks(Callbacks oldCallbacks) { 1623 synchronized (mLock) { 1624 if (mStopped) { 1625 return null; 1626 } 1627 1628 if (mCallbacks == null) { 1629 return null; 1630 } 1631 1632 final Callbacks callbacks = mCallbacks.get(); 1633 if (callbacks != oldCallbacks) { 1634 return null; 1635 } 1636 if (callbacks == null) { 1637 Log.w(TAG, "no mCallbacks"); 1638 return null; 1639 } 1640 1641 return callbacks; 1642 } 1643 } 1644 1645 private void verifyApplications() { 1646 final Context context = mApp.getContext(); 1647 1648 // Cross reference all the applications in our apps list with items in the workspace 1649 ArrayList<ItemInfo> tmpInfos; 1650 ArrayList<ItemInfo> added = new ArrayList<ItemInfo>(); 1651 synchronized (sBgLock) { 1652 for (AppInfo app : mBgAllAppsList.data) { 1653 tmpInfos = getItemInfoForComponentName(app.componentName); 1654 if (tmpInfos.isEmpty()) { 1655 // We are missing an application icon, so add this to the workspace 1656 added.add(app); 1657 // This is a rare event, so lets log it 1658 Log.e(TAG, "Missing Application on load: " + app); 1659 } 1660 } 1661 } 1662 if (!added.isEmpty()) { 1663 addAndBindAddedWorkspaceApps(context, added); 1664 } 1665 } 1666 1667 // check & update map of what's occupied; used to discard overlapping/invalid items 1668 private boolean checkItemPlacement(HashMap<Long, ItemInfo[][]> occupied, ItemInfo item, 1669 AtomicBoolean deleteOnInvalidPlacement) { 1670 LauncherAppState app = LauncherAppState.getInstance(); 1671 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 1672 final int countX = (int) grid.numColumns; 1673 final int countY = (int) grid.numRows; 1674 1675 long containerIndex = item.screenId; 1676 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 1677 // Return early if we detect that an item is under the hotseat button 1678 if (mCallbacks == null || 1679 mCallbacks.get().isAllAppsButtonRank((int) item.screenId)) { 1680 deleteOnInvalidPlacement.set(true); 1681 Log.e(TAG, "Error loading shortcut into hotseat " + item 1682 + " into position (" + item.screenId + ":" + item.cellX + "," 1683 + item.cellY + ") occupied by all apps"); 1684 return false; 1685 } 1686 1687 final ItemInfo[][] hotseatItems = 1688 occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT); 1689 1690 if (item.screenId >= grid.numHotseatIcons) { 1691 Log.e(TAG, "Error loading shortcut " + item 1692 + " into hotseat position " + item.screenId 1693 + ", position out of bounds: (0 to " + (grid.numHotseatIcons - 1) 1694 + ")"); 1695 return false; 1696 } 1697 1698 if (hotseatItems != null) { 1699 if (hotseatItems[(int) item.screenId][0] != null) { 1700 Log.e(TAG, "Error loading shortcut into hotseat " + item 1701 + " into position (" + item.screenId + ":" + item.cellX + "," 1702 + item.cellY + ") occupied by " 1703 + occupied.get(LauncherSettings.Favorites.CONTAINER_HOTSEAT) 1704 [(int) item.screenId][0]); 1705 return false; 1706 } else { 1707 hotseatItems[(int) item.screenId][0] = item; 1708 return true; 1709 } 1710 } else { 1711 final ItemInfo[][] items = new ItemInfo[(int) grid.numHotseatIcons][1]; 1712 items[(int) item.screenId][0] = item; 1713 occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, items); 1714 return true; 1715 } 1716 } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { 1717 // Skip further checking if it is not the hotseat or workspace container 1718 return true; 1719 } 1720 1721 if (!occupied.containsKey(item.screenId)) { 1722 ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1]; 1723 occupied.put(item.screenId, items); 1724 } 1725 1726 final ItemInfo[][] screens = occupied.get(item.screenId); 1727 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && 1728 item.cellX < 0 || item.cellY < 0 || 1729 item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) { 1730 Log.e(TAG, "Error loading shortcut " + item 1731 + " into cell (" + containerIndex + "-" + item.screenId + ":" 1732 + item.cellX + "," + item.cellY 1733 + ") out of screen bounds ( " + countX + "x" + countY + ")"); 1734 return false; 1735 } 1736 1737 // Check if any workspace icons overlap with each other 1738 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) { 1739 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) { 1740 if (screens[x][y] != null) { 1741 Log.e(TAG, "Error loading shortcut " + item 1742 + " into cell (" + containerIndex + "-" + item.screenId + ":" 1743 + x + "," + y 1744 + ") occupied by " 1745 + screens[x][y]); 1746 return false; 1747 } 1748 } 1749 } 1750 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) { 1751 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) { 1752 screens[x][y] = item; 1753 } 1754 } 1755 1756 return true; 1757 } 1758 1759 /** Clears all the sBg data structures */ 1760 private void clearSBgDataStructures() { 1761 synchronized (sBgLock) { 1762 sBgWorkspaceItems.clear(); 1763 sBgAppWidgets.clear(); 1764 sBgFolders.clear(); 1765 sBgItemsIdMap.clear(); 1766 sBgDbIconCache.clear(); 1767 sBgWorkspaceScreens.clear(); 1768 } 1769 } 1770 1771 /** Returns whether this is an upgrade path */ 1772 private boolean loadWorkspace() { 1773 // Log to disk 1774 Launcher.addDumpLog(TAG, "11683562 - loadWorkspace()", true); 1775 1776 final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 1777 1778 final Context context = mContext; 1779 final ContentResolver contentResolver = context.getContentResolver(); 1780 final PackageManager manager = context.getPackageManager(); 1781 final AppWidgetManager widgets = AppWidgetManager.getInstance(context); 1782 final boolean isSafeMode = manager.isSafeMode(); 1783 1784 LauncherAppState app = LauncherAppState.getInstance(); 1785 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 1786 int countX = (int) grid.numColumns; 1787 int countY = (int) grid.numRows; 1788 1789 if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) { 1790 Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true); 1791 LauncherAppState.getLauncherProvider().deleteDatabase(); 1792 } 1793 1794 if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) { 1795 // append the user's Launcher2 shortcuts 1796 Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true); 1797 LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts(); 1798 } else { 1799 // Make sure the default workspace is loaded 1800 Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false); 1801 LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary(0); 1802 } 1803 1804 // Check if we need to do any upgrade-path logic 1805 // (Includes having just imported default favorites) 1806 boolean loadedOldDb = LauncherAppState.getLauncherProvider().justLoadedOldDb(); 1807 1808 // Log to disk 1809 Launcher.addDumpLog(TAG, "11683562 - loadedOldDb: " + loadedOldDb, true); 1810 1811 synchronized (sBgLock) { 1812 clearSBgDataStructures(); 1813 1814 final ArrayList<Long> itemsToRemove = new ArrayList<Long>(); 1815 final ArrayList<Long> restoredRows = new ArrayList<Long>(); 1816 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI; 1817 if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri); 1818 final Cursor c = contentResolver.query(contentUri, null, null, null, null); 1819 1820 // +1 for the hotseat (it can be larger than the workspace) 1821 // Load workspace in reverse order to ensure that latest items are loaded first (and 1822 // before any earlier duplicates) 1823 final HashMap<Long, ItemInfo[][]> occupied = new HashMap<Long, ItemInfo[][]>(); 1824 1825 try { 1826 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); 1827 final int intentIndex = c.getColumnIndexOrThrow 1828 (LauncherSettings.Favorites.INTENT); 1829 final int titleIndex = c.getColumnIndexOrThrow 1830 (LauncherSettings.Favorites.TITLE); 1831 final int iconTypeIndex = c.getColumnIndexOrThrow( 1832 LauncherSettings.Favorites.ICON_TYPE); 1833 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); 1834 final int iconPackageIndex = c.getColumnIndexOrThrow( 1835 LauncherSettings.Favorites.ICON_PACKAGE); 1836 final int iconResourceIndex = c.getColumnIndexOrThrow( 1837 LauncherSettings.Favorites.ICON_RESOURCE); 1838 final int containerIndex = c.getColumnIndexOrThrow( 1839 LauncherSettings.Favorites.CONTAINER); 1840 final int itemTypeIndex = c.getColumnIndexOrThrow( 1841 LauncherSettings.Favorites.ITEM_TYPE); 1842 final int appWidgetIdIndex = c.getColumnIndexOrThrow( 1843 LauncherSettings.Favorites.APPWIDGET_ID); 1844 final int appWidgetProviderIndex = c.getColumnIndexOrThrow( 1845 LauncherSettings.Favorites.APPWIDGET_PROVIDER); 1846 final int screenIndex = c.getColumnIndexOrThrow( 1847 LauncherSettings.Favorites.SCREEN); 1848 final int cellXIndex = c.getColumnIndexOrThrow 1849 (LauncherSettings.Favorites.CELLX); 1850 final int cellYIndex = c.getColumnIndexOrThrow 1851 (LauncherSettings.Favorites.CELLY); 1852 final int spanXIndex = c.getColumnIndexOrThrow 1853 (LauncherSettings.Favorites.SPANX); 1854 final int spanYIndex = c.getColumnIndexOrThrow( 1855 LauncherSettings.Favorites.SPANY); 1856 final int restoredIndex = c.getColumnIndexOrThrow( 1857 LauncherSettings.Favorites.RESTORED); 1858 //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); 1859 //final int displayModeIndex = c.getColumnIndexOrThrow( 1860 // LauncherSettings.Favorites.DISPLAY_MODE); 1861 1862 ShortcutInfo info; 1863 String intentDescription; 1864 LauncherAppWidgetInfo appWidgetInfo; 1865 int container; 1866 long id; 1867 Intent intent; 1868 1869 while (!mStopped && c.moveToNext()) { 1870 AtomicBoolean deleteOnInvalidPlacement = new AtomicBoolean(false); 1871 try { 1872 int itemType = c.getInt(itemTypeIndex); 1873 boolean restored = 0 != c.getInt(restoredIndex); 1874 1875 switch (itemType) { 1876 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 1877 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 1878 id = c.getLong(idIndex); 1879 intentDescription = c.getString(intentIndex); 1880 try { 1881 intent = Intent.parseUri(intentDescription, 0); 1882 ComponentName cn = intent.getComponent(); 1883 if (cn != null && !isValidPackageComponent(manager, cn)) { 1884 if (restored) { 1885 // might be installed later 1886 Launcher.addDumpLog(TAG, 1887 "package not yet restored: " + cn, true); 1888 } else { 1889 if (!mAppsCanBeOnRemoveableStorage) { 1890 // Log the invalid package, and remove it 1891 Launcher.addDumpLog(TAG, 1892 "Invalid package removed: " + cn, true); 1893 itemsToRemove.add(id); 1894 } else { 1895 // If apps can be on external storage, then we just 1896 // leave them for the user to remove (maybe add 1897 // visual treatment to it) 1898 Launcher.addDumpLog(TAG, 1899 "Invalid package found: " + cn, true); 1900 } 1901 continue; 1902 } 1903 } else if (restored) { 1904 // no special handling necessary for this restored item 1905 restoredRows.add(id); 1906 restored = false; 1907 } 1908 } catch (URISyntaxException e) { 1909 Launcher.addDumpLog(TAG, 1910 "Invalid uri: " + intentDescription, true); 1911 continue; 1912 } 1913 1914 if (restored) { 1915 Launcher.addDumpLog(TAG, 1916 "constructing info for partially restored package", 1917 true); 1918 info = getRestoredItemInfo(c, titleIndex, intent); 1919 intent = getRestoredItemIntent(c, context, intent); 1920 } else if (itemType == 1921 LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { 1922 info = getShortcutInfo(manager, intent, context, c, iconIndex, 1923 titleIndex, mLabelCache); 1924 } else { 1925 info = getShortcutInfo(c, context, iconTypeIndex, 1926 iconPackageIndex, iconResourceIndex, iconIndex, 1927 titleIndex); 1928 1929 // App shortcuts that used to be automatically added to Launcher 1930 // didn't always have the correct intent flags set, so do that 1931 // here 1932 if (intent.getAction() != null && 1933 intent.getCategories() != null && 1934 intent.getAction().equals(Intent.ACTION_MAIN) && 1935 intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { 1936 intent.addFlags( 1937 Intent.FLAG_ACTIVITY_NEW_TASK | 1938 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 1939 } 1940 } 1941 1942 if (info != null) { 1943 info.id = id; 1944 info.intent = intent; 1945 container = c.getInt(containerIndex); 1946 info.container = container; 1947 info.screenId = c.getInt(screenIndex); 1948 info.cellX = c.getInt(cellXIndex); 1949 info.cellY = c.getInt(cellYIndex); 1950 info.spanX = 1; 1951 info.spanY = 1; 1952 1953 // check & update map of what's occupied 1954 deleteOnInvalidPlacement.set(false); 1955 if (!checkItemPlacement(occupied, info, deleteOnInvalidPlacement)) { 1956 if (deleteOnInvalidPlacement.get()) { 1957 itemsToRemove.add(id); 1958 } 1959 break; 1960 } 1961 1962 switch (container) { 1963 case LauncherSettings.Favorites.CONTAINER_DESKTOP: 1964 case LauncherSettings.Favorites.CONTAINER_HOTSEAT: 1965 sBgWorkspaceItems.add(info); 1966 break; 1967 default: 1968 // Item is in a user folder 1969 FolderInfo folderInfo = 1970 findOrMakeFolder(sBgFolders, container); 1971 folderInfo.add(info); 1972 break; 1973 } 1974 sBgItemsIdMap.put(info.id, info); 1975 1976 // now that we've loaded everthing re-save it with the 1977 // icon in case it disappears somehow. 1978 queueIconToBeChecked(sBgDbIconCache, info, c, iconIndex); 1979 } else { 1980 throw new RuntimeException("Unexpected null ShortcutInfo"); 1981 } 1982 break; 1983 1984 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 1985 id = c.getLong(idIndex); 1986 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id); 1987 1988 folderInfo.title = c.getString(titleIndex); 1989 folderInfo.id = id; 1990 container = c.getInt(containerIndex); 1991 folderInfo.container = container; 1992 folderInfo.screenId = c.getInt(screenIndex); 1993 folderInfo.cellX = c.getInt(cellXIndex); 1994 folderInfo.cellY = c.getInt(cellYIndex); 1995 folderInfo.spanX = 1; 1996 folderInfo.spanY = 1; 1997 1998 // check & update map of what's occupied 1999 deleteOnInvalidPlacement.set(false); 2000 if (!checkItemPlacement(occupied, folderInfo, 2001 deleteOnInvalidPlacement)) { 2002 if (deleteOnInvalidPlacement.get()) { 2003 itemsToRemove.add(id); 2004 } 2005 break; 2006 } 2007 2008 switch (container) { 2009 case LauncherSettings.Favorites.CONTAINER_DESKTOP: 2010 case LauncherSettings.Favorites.CONTAINER_HOTSEAT: 2011 sBgWorkspaceItems.add(folderInfo); 2012 break; 2013 } 2014 2015 if (restored) { 2016 // no special handling required for restored folders 2017 restoredRows.add(id); 2018 } 2019 2020 sBgItemsIdMap.put(folderInfo.id, folderInfo); 2021 sBgFolders.put(folderInfo.id, folderInfo); 2022 break; 2023 2024 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 2025 // Read all Launcher-specific widget details 2026 int appWidgetId = c.getInt(appWidgetIdIndex); 2027 String savedProvider = c.getString(appWidgetProviderIndex); 2028 2029 id = c.getLong(idIndex); 2030 2031 final AppWidgetProviderInfo provider = 2032 widgets.getAppWidgetInfo(appWidgetId); 2033 2034 if (!isSafeMode && (provider == null || provider.provider == null || 2035 provider.provider.getPackageName() == null)) { 2036 String log = "Deleting widget that isn't installed anymore: id=" 2037 + id + " appWidgetId=" + appWidgetId; 2038 Log.e(TAG, log); 2039 Launcher.addDumpLog(TAG, log, false); 2040 itemsToRemove.add(id); 2041 } else { 2042 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, 2043 provider.provider); 2044 appWidgetInfo.id = id; 2045 appWidgetInfo.screenId = c.getInt(screenIndex); 2046 appWidgetInfo.cellX = c.getInt(cellXIndex); 2047 appWidgetInfo.cellY = c.getInt(cellYIndex); 2048 appWidgetInfo.spanX = c.getInt(spanXIndex); 2049 appWidgetInfo.spanY = c.getInt(spanYIndex); 2050 int[] minSpan = Launcher.getMinSpanForWidget(context, provider); 2051 appWidgetInfo.minSpanX = minSpan[0]; 2052 appWidgetInfo.minSpanY = minSpan[1]; 2053 2054 container = c.getInt(containerIndex); 2055 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP && 2056 container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 2057 Log.e(TAG, "Widget found where container != " + 2058 "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!"); 2059 continue; 2060 } 2061 2062 appWidgetInfo.container = c.getInt(containerIndex); 2063 // check & update map of what's occupied 2064 deleteOnInvalidPlacement.set(false); 2065 if (!checkItemPlacement(occupied, appWidgetInfo, 2066 deleteOnInvalidPlacement)) { 2067 if (deleteOnInvalidPlacement.get()) { 2068 itemsToRemove.add(id); 2069 } 2070 break; 2071 } 2072 String providerName = provider.provider.flattenToString(); 2073 if (!providerName.equals(savedProvider)) { 2074 ContentValues values = new ContentValues(); 2075 values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, 2076 providerName); 2077 String where = BaseColumns._ID + "= ?"; 2078 String[] args = {Integer.toString(c.getInt(idIndex))}; 2079 contentResolver.update(contentUri, values, where, args); 2080 } 2081 sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo); 2082 sBgAppWidgets.add(appWidgetInfo); 2083 } 2084 break; 2085 } 2086 } catch (Exception e) { 2087 Launcher.addDumpLog(TAG, "Desktop items loading interrupted", e, true); 2088 } 2089 } 2090 } finally { 2091 if (c != null) { 2092 c.close(); 2093 } 2094 } 2095 2096 // Break early if we've stopped loading 2097 if (mStopped) { 2098 clearSBgDataStructures(); 2099 return false; 2100 } 2101 2102 if (itemsToRemove.size() > 0) { 2103 ContentProviderClient client = contentResolver.acquireContentProviderClient( 2104 LauncherSettings.Favorites.CONTENT_URI); 2105 // Remove dead items 2106 for (long id : itemsToRemove) { 2107 if (DEBUG_LOADERS) { 2108 Log.d(TAG, "Removed id = " + id); 2109 } 2110 // Don't notify content observers 2111 try { 2112 client.delete(LauncherSettings.Favorites.getContentUri(id, false), 2113 null, null); 2114 } catch (RemoteException e) { 2115 Log.w(TAG, "Could not remove id = " + id); 2116 } 2117 } 2118 } 2119 2120 if (restoredRows.size() > 0) { 2121 ContentProviderClient updater = contentResolver.acquireContentProviderClient( 2122 LauncherSettings.Favorites.CONTENT_URI); 2123 // Update restored items that no longer require special handling 2124 try { 2125 StringBuilder selectionBuilder = new StringBuilder(); 2126 selectionBuilder.append(LauncherSettings.Favorites._ID); 2127 selectionBuilder.append(" IN ("); 2128 selectionBuilder.append(TextUtils.join(", ", restoredRows)); 2129 selectionBuilder.append(")"); 2130 ContentValues values = new ContentValues(); 2131 values.put(LauncherSettings.Favorites.RESTORED, 0); 2132 updater.update(LauncherSettings.Favorites.CONTENT_URI, 2133 values, selectionBuilder.toString(), null); 2134 } catch (RemoteException e) { 2135 Log.w(TAG, "Could not update restored rows"); 2136 } 2137 } 2138 2139 if (loadedOldDb) { 2140 long maxScreenId = 0; 2141 // If we're importing we use the old screen order. 2142 for (ItemInfo item: sBgItemsIdMap.values()) { 2143 long screenId = item.screenId; 2144 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && 2145 !sBgWorkspaceScreens.contains(screenId)) { 2146 sBgWorkspaceScreens.add(screenId); 2147 if (screenId > maxScreenId) { 2148 maxScreenId = screenId; 2149 } 2150 } 2151 } 2152 Collections.sort(sBgWorkspaceScreens); 2153 // Log to disk 2154 Launcher.addDumpLog(TAG, "11683562 - maxScreenId: " + maxScreenId, true); 2155 Launcher.addDumpLog(TAG, "11683562 - sBgWorkspaceScreens: " + 2156 TextUtils.join(", ", sBgWorkspaceScreens), true); 2157 2158 LauncherAppState.getLauncherProvider().updateMaxScreenId(maxScreenId); 2159 updateWorkspaceScreenOrder(context, sBgWorkspaceScreens); 2160 2161 // Update the max item id after we load an old db 2162 long maxItemId = 0; 2163 // If we're importing we use the old screen order. 2164 for (ItemInfo item: sBgItemsIdMap.values()) { 2165 maxItemId = Math.max(maxItemId, item.id); 2166 } 2167 LauncherAppState.getLauncherProvider().updateMaxItemId(maxItemId); 2168 } else { 2169 TreeMap<Integer, Long> orderedScreens = loadWorkspaceScreensDb(mContext); 2170 for (Integer i : orderedScreens.keySet()) { 2171 sBgWorkspaceScreens.add(orderedScreens.get(i)); 2172 } 2173 // Log to disk 2174 Launcher.addDumpLog(TAG, "11683562 - sBgWorkspaceScreens: " + 2175 TextUtils.join(", ", sBgWorkspaceScreens), true); 2176 2177 // Remove any empty screens 2178 ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens); 2179 for (ItemInfo item: sBgItemsIdMap.values()) { 2180 long screenId = item.screenId; 2181 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && 2182 unusedScreens.contains(screenId)) { 2183 unusedScreens.remove(screenId); 2184 } 2185 } 2186 2187 // If there are any empty screens remove them, and update. 2188 if (unusedScreens.size() != 0) { 2189 // Log to disk 2190 Launcher.addDumpLog(TAG, "11683562 - unusedScreens (to be removed): " + 2191 TextUtils.join(", ", unusedScreens), true); 2192 2193 sBgWorkspaceScreens.removeAll(unusedScreens); 2194 updateWorkspaceScreenOrder(context, sBgWorkspaceScreens); 2195 } 2196 } 2197 2198 if (DEBUG_LOADERS) { 2199 Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms"); 2200 Log.d(TAG, "workspace layout: "); 2201 int nScreens = occupied.size(); 2202 for (int y = 0; y < countY; y++) { 2203 String line = ""; 2204 2205 Iterator<Long> iter = occupied.keySet().iterator(); 2206 while (iter.hasNext()) { 2207 long screenId = iter.next(); 2208 if (screenId > 0) { 2209 line += " | "; 2210 } 2211 for (int x = 0; x < countX; x++) { 2212 ItemInfo[][] screen = occupied.get(screenId); 2213 if (x < screen.length && y < screen[x].length) { 2214 line += (screen[x][y] != null) ? "#" : "."; 2215 } else { 2216 line += "!"; 2217 } 2218 } 2219 } 2220 Log.d(TAG, "[ " + line + " ]"); 2221 } 2222 } 2223 } 2224 return loadedOldDb; 2225 } 2226 2227 /** Filters the set of items who are directly or indirectly (via another container) on the 2228 * specified screen. */ 2229 private void filterCurrentWorkspaceItems(long currentScreenId, 2230 ArrayList<ItemInfo> allWorkspaceItems, 2231 ArrayList<ItemInfo> currentScreenItems, 2232 ArrayList<ItemInfo> otherScreenItems) { 2233 // Purge any null ItemInfos 2234 Iterator<ItemInfo> iter = allWorkspaceItems.iterator(); 2235 while (iter.hasNext()) { 2236 ItemInfo i = iter.next(); 2237 if (i == null) { 2238 iter.remove(); 2239 } 2240 } 2241 2242 // Order the set of items by their containers first, this allows use to walk through the 2243 // list sequentially, build up a list of containers that are in the specified screen, 2244 // as well as all items in those containers. 2245 Set<Long> itemsOnScreen = new HashSet<Long>(); 2246 Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() { 2247 @Override 2248 public int compare(ItemInfo lhs, ItemInfo rhs) { 2249 return (int) (lhs.container - rhs.container); 2250 } 2251 }); 2252 for (ItemInfo info : allWorkspaceItems) { 2253 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 2254 if (info.screenId == currentScreenId) { 2255 currentScreenItems.add(info); 2256 itemsOnScreen.add(info.id); 2257 } else { 2258 otherScreenItems.add(info); 2259 } 2260 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 2261 currentScreenItems.add(info); 2262 itemsOnScreen.add(info.id); 2263 } else { 2264 if (itemsOnScreen.contains(info.container)) { 2265 currentScreenItems.add(info); 2266 itemsOnScreen.add(info.id); 2267 } else { 2268 otherScreenItems.add(info); 2269 } 2270 } 2271 } 2272 } 2273 2274 /** Filters the set of widgets which are on the specified screen. */ 2275 private void filterCurrentAppWidgets(long currentScreenId, 2276 ArrayList<LauncherAppWidgetInfo> appWidgets, 2277 ArrayList<LauncherAppWidgetInfo> currentScreenWidgets, 2278 ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) { 2279 2280 for (LauncherAppWidgetInfo widget : appWidgets) { 2281 if (widget == null) continue; 2282 if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && 2283 widget.screenId == currentScreenId) { 2284 currentScreenWidgets.add(widget); 2285 } else { 2286 otherScreenWidgets.add(widget); 2287 } 2288 } 2289 } 2290 2291 /** Filters the set of folders which are on the specified screen. */ 2292 private void filterCurrentFolders(long currentScreenId, 2293 HashMap<Long, ItemInfo> itemsIdMap, 2294 HashMap<Long, FolderInfo> folders, 2295 HashMap<Long, FolderInfo> currentScreenFolders, 2296 HashMap<Long, FolderInfo> otherScreenFolders) { 2297 2298 for (long id : folders.keySet()) { 2299 ItemInfo info = itemsIdMap.get(id); 2300 FolderInfo folder = folders.get(id); 2301 if (info == null || folder == null) continue; 2302 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && 2303 info.screenId == currentScreenId) { 2304 currentScreenFolders.put(id, folder); 2305 } else { 2306 otherScreenFolders.put(id, folder); 2307 } 2308 } 2309 } 2310 2311 /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to 2312 * right) */ 2313 private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) { 2314 final LauncherAppState app = LauncherAppState.getInstance(); 2315 final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 2316 // XXX: review this 2317 Collections.sort(workspaceItems, new Comparator<ItemInfo>() { 2318 @Override 2319 public int compare(ItemInfo lhs, ItemInfo rhs) { 2320 int cellCountX = (int) grid.numColumns; 2321 int cellCountY = (int) grid.numRows; 2322 int screenOffset = cellCountX * cellCountY; 2323 int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat 2324 long lr = (lhs.container * containerOffset + lhs.screenId * screenOffset + 2325 lhs.cellY * cellCountX + lhs.cellX); 2326 long rr = (rhs.container * containerOffset + rhs.screenId * screenOffset + 2327 rhs.cellY * cellCountX + rhs.cellX); 2328 return (int) (lr - rr); 2329 } 2330 }); 2331 } 2332 2333 private void bindWorkspaceScreens(final Callbacks oldCallbacks, 2334 final ArrayList<Long> orderedScreens) { 2335 final Runnable r = new Runnable() { 2336 @Override 2337 public void run() { 2338 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 2339 if (callbacks != null) { 2340 callbacks.bindScreens(orderedScreens); 2341 } 2342 } 2343 }; 2344 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); 2345 } 2346 2347 private void bindWorkspaceItems(final Callbacks oldCallbacks, 2348 final ArrayList<ItemInfo> workspaceItems, 2349 final ArrayList<LauncherAppWidgetInfo> appWidgets, 2350 final HashMap<Long, FolderInfo> folders, 2351 ArrayList<Runnable> deferredBindRunnables) { 2352 2353 final boolean postOnMainThread = (deferredBindRunnables != null); 2354 2355 // Bind the workspace items 2356 int N = workspaceItems.size(); 2357 for (int i = 0; i < N; i += ITEMS_CHUNK) { 2358 final int start = i; 2359 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i); 2360 final Runnable r = new Runnable() { 2361 @Override 2362 public void run() { 2363 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 2364 if (callbacks != null) { 2365 callbacks.bindItems(workspaceItems, start, start+chunkSize, 2366 false); 2367 } 2368 } 2369 }; 2370 if (postOnMainThread) { 2371 deferredBindRunnables.add(r); 2372 } else { 2373 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); 2374 } 2375 } 2376 2377 // Bind the folders 2378 if (!folders.isEmpty()) { 2379 final Runnable r = new Runnable() { 2380 public void run() { 2381 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 2382 if (callbacks != null) { 2383 callbacks.bindFolders(folders); 2384 } 2385 } 2386 }; 2387 if (postOnMainThread) { 2388 deferredBindRunnables.add(r); 2389 } else { 2390 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); 2391 } 2392 } 2393 2394 // Bind the widgets, one at a time 2395 N = appWidgets.size(); 2396 for (int i = 0; i < N; i++) { 2397 final LauncherAppWidgetInfo widget = appWidgets.get(i); 2398 final Runnable r = new Runnable() { 2399 public void run() { 2400 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 2401 if (callbacks != null) { 2402 callbacks.bindAppWidget(widget); 2403 } 2404 } 2405 }; 2406 if (postOnMainThread) { 2407 deferredBindRunnables.add(r); 2408 } else { 2409 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); 2410 } 2411 } 2412 } 2413 2414 /** 2415 * Binds all loaded data to actual views on the main thread. 2416 */ 2417 private void bindWorkspace(int synchronizeBindPage, final boolean isUpgradePath) { 2418 final long t = SystemClock.uptimeMillis(); 2419 Runnable r; 2420 2421 // Don't use these two variables in any of the callback runnables. 2422 // Otherwise we hold a reference to them. 2423 final Callbacks oldCallbacks = mCallbacks.get(); 2424 if (oldCallbacks == null) { 2425 // This launcher has exited and nobody bothered to tell us. Just bail. 2426 Log.w(TAG, "LoaderTask running with no launcher"); 2427 return; 2428 } 2429 2430 // Save a copy of all the bg-thread collections 2431 ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>(); 2432 ArrayList<LauncherAppWidgetInfo> appWidgets = 2433 new ArrayList<LauncherAppWidgetInfo>(); 2434 HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>(); 2435 HashMap<Long, ItemInfo> itemsIdMap = new HashMap<Long, ItemInfo>(); 2436 ArrayList<Long> orderedScreenIds = new ArrayList<Long>(); 2437 synchronized (sBgLock) { 2438 workspaceItems.addAll(sBgWorkspaceItems); 2439 appWidgets.addAll(sBgAppWidgets); 2440 folders.putAll(sBgFolders); 2441 itemsIdMap.putAll(sBgItemsIdMap); 2442 orderedScreenIds.addAll(sBgWorkspaceScreens); 2443 } 2444 2445 final boolean isLoadingSynchronously = 2446 synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE; 2447 int currScreen = isLoadingSynchronously ? synchronizeBindPage : 2448 oldCallbacks.getCurrentWorkspaceScreen(); 2449 if (currScreen >= orderedScreenIds.size()) { 2450 // There may be no workspace screens (just hotseat items and an empty page). 2451 currScreen = PagedView.INVALID_RESTORE_PAGE; 2452 } 2453 final int currentScreen = currScreen; 2454 final long currentScreenId = currentScreen < 0 2455 ? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen); 2456 2457 // Load all the items that are on the current page first (and in the process, unbind 2458 // all the existing workspace items before we call startBinding() below. 2459 unbindWorkspaceItemsOnMainThread(); 2460 2461 // Separate the items that are on the current screen, and all the other remaining items 2462 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>(); 2463 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>(); 2464 ArrayList<LauncherAppWidgetInfo> currentAppWidgets = 2465 new ArrayList<LauncherAppWidgetInfo>(); 2466 ArrayList<LauncherAppWidgetInfo> otherAppWidgets = 2467 new ArrayList<LauncherAppWidgetInfo>(); 2468 HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>(); 2469 HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>(); 2470 2471 filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems, 2472 otherWorkspaceItems); 2473 filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets, 2474 otherAppWidgets); 2475 filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders, 2476 otherFolders); 2477 sortWorkspaceItemsSpatially(currentWorkspaceItems); 2478 sortWorkspaceItemsSpatially(otherWorkspaceItems); 2479 2480 // Tell the workspace that we're about to start binding items 2481 r = new Runnable() { 2482 public void run() { 2483 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 2484 if (callbacks != null) { 2485 callbacks.startBinding(); 2486 } 2487 } 2488 }; 2489 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); 2490 2491 bindWorkspaceScreens(oldCallbacks, orderedScreenIds); 2492 2493 // Load items on the current page 2494 bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, 2495 currentFolders, null); 2496 if (isLoadingSynchronously) { 2497 r = new Runnable() { 2498 public void run() { 2499 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 2500 if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) { 2501 callbacks.onPageBoundSynchronously(currentScreen); 2502 } 2503 } 2504 }; 2505 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); 2506 } 2507 2508 // Load all the remaining pages (if we are loading synchronously, we want to defer this 2509 // work until after the first render) 2510 mDeferredBindRunnables.clear(); 2511 bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders, 2512 (isLoadingSynchronously ? mDeferredBindRunnables : null)); 2513 2514 // Tell the workspace that we're done binding items 2515 r = new Runnable() { 2516 public void run() { 2517 Callbacks callbacks = tryGetCallbacks(oldCallbacks); 2518 if (callbacks != null) { 2519 callbacks.finishBindingItems(isUpgradePath); 2520 } 2521 2522 // If we're profiling, ensure this is the last thing in the queue. 2523 if (DEBUG_LOADERS) { 2524 Log.d(TAG, "bound workspace in " 2525 + (SystemClock.uptimeMillis()-t) + "ms"); 2526 } 2527 2528 mIsLoadingAndBindingWorkspace = false; 2529 } 2530 }; 2531 if (isLoadingSynchronously) { 2532 mDeferredBindRunnables.add(r); 2533 } else { 2534 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); 2535 } 2536 } 2537 2538 private void loadAndBindAllApps() { 2539 if (DEBUG_LOADERS) { 2540 Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded); 2541 } 2542 if (!mAllAppsLoaded) { 2543 loadAllApps(); 2544 synchronized (LoaderTask.this) { 2545 if (mStopped) { 2546 return; 2547 } 2548 mAllAppsLoaded = true; 2549 } 2550 } else { 2551 onlyBindAllApps(); 2552 } 2553 } 2554 2555 private void onlyBindAllApps() { 2556 final Callbacks oldCallbacks = mCallbacks.get(); 2557 if (oldCallbacks == null) { 2558 // This launcher has exited and nobody bothered to tell us. Just bail. 2559 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)"); 2560 return; 2561 } 2562 2563 // shallow copy 2564 @SuppressWarnings("unchecked") 2565 final ArrayList<AppInfo> list 2566 = (ArrayList<AppInfo>) mBgAllAppsList.data.clone(); 2567 Runnable r = new Runnable() { 2568 public void run() { 2569 final long t = SystemClock.uptimeMillis(); 2570 final Callbacks callbacks = tryGetCallbacks(oldCallbacks); 2571 if (callbacks != null) { 2572 callbacks.bindAllApplications(list); 2573 } 2574 if (DEBUG_LOADERS) { 2575 Log.d(TAG, "bound all " + list.size() + " apps from cache in " 2576 + (SystemClock.uptimeMillis()-t) + "ms"); 2577 } 2578 } 2579 }; 2580 boolean isRunningOnMainThread = !(sWorkerThread.getThreadId() == Process.myTid()); 2581 if (isRunningOnMainThread) { 2582 r.run(); 2583 } else { 2584 mHandler.post(r); 2585 } 2586 } 2587 2588 private void loadAllApps() { 2589 final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 2590 2591 final Callbacks oldCallbacks = mCallbacks.get(); 2592 if (oldCallbacks == null) { 2593 // This launcher has exited and nobody bothered to tell us. Just bail. 2594 Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)"); 2595 return; 2596 } 2597 2598 final PackageManager packageManager = mContext.getPackageManager(); 2599 final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); 2600 mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); 2601 2602 // Clear the list of apps 2603 mBgAllAppsList.clear(); 2604 2605 // Query for the set of apps 2606 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 2607 List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0); 2608 if (DEBUG_LOADERS) { 2609 Log.d(TAG, "queryIntentActivities took " 2610 + (SystemClock.uptimeMillis()-qiaTime) + "ms"); 2611 Log.d(TAG, "queryIntentActivities got " + apps.size() + " apps"); 2612 } 2613 // Fail if we don't have any apps 2614 if (apps == null || apps.isEmpty()) { 2615 return; 2616 } 2617 // Sort the applications by name 2618 final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; 2619 Collections.sort(apps, 2620 new LauncherModel.ShortcutNameComparator(packageManager, mLabelCache)); 2621 if (DEBUG_LOADERS) { 2622 Log.d(TAG, "sort took " 2623 + (SystemClock.uptimeMillis()-sortTime) + "ms"); 2624 } 2625 2626 // Create the ApplicationInfos 2627 for (int i = 0; i < apps.size(); i++) { 2628 ResolveInfo app = apps.get(i); 2629 // This builds the icon bitmaps. 2630 mBgAllAppsList.add(new AppInfo(packageManager, app, 2631 mIconCache, mLabelCache)); 2632 } 2633 2634 // Huh? Shouldn't this be inside the Runnable below? 2635 final ArrayList<AppInfo> added = mBgAllAppsList.added; 2636 mBgAllAppsList.added = new ArrayList<AppInfo>(); 2637 2638 // Post callback on main thread 2639 mHandler.post(new Runnable() { 2640 public void run() { 2641 final long bindTime = SystemClock.uptimeMillis(); 2642 final Callbacks callbacks = tryGetCallbacks(oldCallbacks); 2643 if (callbacks != null) { 2644 callbacks.bindAllApplications(added); 2645 if (DEBUG_LOADERS) { 2646 Log.d(TAG, "bound " + added.size() + " apps in " 2647 + (SystemClock.uptimeMillis() - bindTime) + "ms"); 2648 } 2649 } else { 2650 Log.i(TAG, "not binding apps: no Launcher activity"); 2651 } 2652 } 2653 }); 2654 2655 if (DEBUG_LOADERS) { 2656 Log.d(TAG, "Icons processed in " 2657 + (SystemClock.uptimeMillis() - loadTime) + "ms"); 2658 } 2659 } 2660 2661 public void dumpState() { 2662 synchronized (sBgLock) { 2663 Log.d(TAG, "mLoaderTask.mContext=" + mContext); 2664 Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching); 2665 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped); 2666 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished); 2667 Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size()); 2668 } 2669 } 2670 } 2671 2672 void enqueuePackageUpdated(PackageUpdatedTask task) { 2673 sWorker.post(task); 2674 } 2675 2676 private class PackageUpdatedTask implements Runnable { 2677 int mOp; 2678 String[] mPackages; 2679 2680 public static final int OP_NONE = 0; 2681 public static final int OP_ADD = 1; 2682 public static final int OP_UPDATE = 2; 2683 public static final int OP_REMOVE = 3; // uninstlled 2684 public static final int OP_UNAVAILABLE = 4; // external media unmounted 2685 2686 2687 public PackageUpdatedTask(int op, String[] packages) { 2688 mOp = op; 2689 mPackages = packages; 2690 } 2691 2692 public void run() { 2693 final Context context = mApp.getContext(); 2694 2695 final String[] packages = mPackages; 2696 final int N = packages.length; 2697 switch (mOp) { 2698 case OP_ADD: 2699 for (int i=0; i<N; i++) { 2700 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]); 2701 mIconCache.remove(packages[i]); 2702 mBgAllAppsList.addPackage(context, packages[i]); 2703 } 2704 break; 2705 case OP_UPDATE: 2706 for (int i=0; i<N; i++) { 2707 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]); 2708 mBgAllAppsList.updatePackage(context, packages[i]); 2709 WidgetPreviewLoader.removePackageFromDb( 2710 mApp.getWidgetPreviewCacheDb(), packages[i]); 2711 } 2712 break; 2713 case OP_REMOVE: 2714 case OP_UNAVAILABLE: 2715 for (int i=0; i<N; i++) { 2716 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]); 2717 mBgAllAppsList.removePackage(packages[i]); 2718 WidgetPreviewLoader.removePackageFromDb( 2719 mApp.getWidgetPreviewCacheDb(), packages[i]); 2720 } 2721 break; 2722 } 2723 2724 ArrayList<AppInfo> added = null; 2725 ArrayList<AppInfo> modified = null; 2726 final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>(); 2727 2728 if (mBgAllAppsList.added.size() > 0) { 2729 added = new ArrayList<AppInfo>(mBgAllAppsList.added); 2730 mBgAllAppsList.added.clear(); 2731 } 2732 if (mBgAllAppsList.modified.size() > 0) { 2733 modified = new ArrayList<AppInfo>(mBgAllAppsList.modified); 2734 mBgAllAppsList.modified.clear(); 2735 } 2736 if (mBgAllAppsList.removed.size() > 0) { 2737 removedApps.addAll(mBgAllAppsList.removed); 2738 mBgAllAppsList.removed.clear(); 2739 } 2740 2741 final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null; 2742 if (callbacks == null) { 2743 Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading."); 2744 return; 2745 } 2746 2747 if (added != null) { 2748 // Ensure that we add all the workspace applications to the db 2749 if (LauncherAppState.isDisableAllApps()) { 2750 final ArrayList<ItemInfo> addedInfos = new ArrayList<ItemInfo>(added); 2751 addAndBindAddedWorkspaceApps(context, addedInfos); 2752 } else { 2753 addAppsToAllApps(context, added); 2754 } 2755 } 2756 2757 if (modified != null) { 2758 final ArrayList<AppInfo> modifiedFinal = modified; 2759 2760 // Update the launcher db to reflect the changes 2761 for (AppInfo a : modifiedFinal) { 2762 ArrayList<ItemInfo> infos = 2763 getItemInfoForComponentName(a.componentName); 2764 for (ItemInfo i : infos) { 2765 if (isShortcutInfoUpdateable(i)) { 2766 ShortcutInfo info = (ShortcutInfo) i; 2767 info.title = a.title.toString(); 2768 updateItemInDatabase(context, info); 2769 } 2770 } 2771 } 2772 2773 mHandler.post(new Runnable() { 2774 public void run() { 2775 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; 2776 if (callbacks == cb && cb != null) { 2777 callbacks.bindAppsUpdated(modifiedFinal); 2778 } 2779 } 2780 }); 2781 } 2782 2783 final ArrayList<String> removedPackageNames = 2784 new ArrayList<String>(); 2785 if (mOp == OP_REMOVE) { 2786 // Mark all packages in the broadcast to be removed 2787 removedPackageNames.addAll(Arrays.asList(packages)); 2788 } else if (mOp == OP_UPDATE) { 2789 // Mark disabled packages in the broadcast to be removed 2790 final PackageManager pm = context.getPackageManager(); 2791 for (int i=0; i<N; i++) { 2792 if (isPackageDisabled(pm, packages[i])) { 2793 removedPackageNames.add(packages[i]); 2794 } 2795 } 2796 } 2797 // Remove all the components associated with this package 2798 for (String pn : removedPackageNames) { 2799 ArrayList<ItemInfo> infos = getItemInfoForPackageName(pn); 2800 for (ItemInfo i : infos) { 2801 deleteItemFromDatabase(context, i); 2802 } 2803 } 2804 // Remove all the specific components 2805 for (AppInfo a : removedApps) { 2806 ArrayList<ItemInfo> infos = getItemInfoForComponentName(a.componentName); 2807 for (ItemInfo i : infos) { 2808 deleteItemFromDatabase(context, i); 2809 } 2810 } 2811 if (!removedPackageNames.isEmpty() || !removedApps.isEmpty()) { 2812 // Remove any queued items from the install queue 2813 String spKey = LauncherAppState.getSharedPreferencesKey(); 2814 SharedPreferences sp = 2815 context.getSharedPreferences(spKey, Context.MODE_PRIVATE); 2816 InstallShortcutReceiver.removeFromInstallQueue(sp, removedPackageNames); 2817 // Call the components-removed callback 2818 mHandler.post(new Runnable() { 2819 public void run() { 2820 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; 2821 if (callbacks == cb && cb != null) { 2822 callbacks.bindComponentsRemoved(removedPackageNames, removedApps); 2823 } 2824 } 2825 }); 2826 } 2827 2828 final ArrayList<Object> widgetsAndShortcuts = 2829 getSortedWidgetsAndShortcuts(context); 2830 mHandler.post(new Runnable() { 2831 @Override 2832 public void run() { 2833 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; 2834 if (callbacks == cb && cb != null) { 2835 callbacks.bindPackagesUpdated(widgetsAndShortcuts); 2836 } 2837 } 2838 }); 2839 2840 // Write all the logs to disk 2841 mHandler.post(new Runnable() { 2842 public void run() { 2843 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; 2844 if (callbacks == cb && cb != null) { 2845 callbacks.dumpLogsToLocalData(); 2846 } 2847 } 2848 }); 2849 } 2850 } 2851 2852 // Returns a list of ResolveInfos/AppWindowInfos in sorted order 2853 public static ArrayList<Object> getSortedWidgetsAndShortcuts(Context context) { 2854 PackageManager packageManager = context.getPackageManager(); 2855 final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>(); 2856 widgetsAndShortcuts.addAll(AppWidgetManager.getInstance(context).getInstalledProviders()); 2857 Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT); 2858 widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0)); 2859 Collections.sort(widgetsAndShortcuts, 2860 new LauncherModel.WidgetAndShortcutNameComparator(packageManager)); 2861 return widgetsAndShortcuts; 2862 } 2863 2864 private static boolean isPackageDisabled(PackageManager pm, String packageName) { 2865 try { 2866 PackageInfo pi = pm.getPackageInfo(packageName, 0); 2867 return !pi.applicationInfo.enabled; 2868 } catch (NameNotFoundException e) { 2869 // Fall through 2870 } 2871 return false; 2872 } 2873 2874 public static boolean isValidPackageComponent(PackageManager pm, ComponentName cn) { 2875 if (cn == null) { 2876 return false; 2877 } 2878 if (isPackageDisabled(pm, cn.getPackageName())) { 2879 return false; 2880 } 2881 2882 try { 2883 // Check the activity 2884 PackageInfo pi = pm.getPackageInfo(cn.getPackageName(), 0); 2885 return (pm.getActivityInfo(cn, 0) != null); 2886 } catch (NameNotFoundException e) { 2887 return false; 2888 } 2889 } 2890 2891 /** 2892 * Make an ShortcutInfo object for a restored application or shortcut item that points 2893 * to a package that is not yet installed on the system. 2894 */ 2895 public ShortcutInfo getRestoredItemInfo(Cursor cursor, int titleIndex, Intent intent) { 2896 final ShortcutInfo info = new ShortcutInfo(); 2897 if (cursor != null) { 2898 info.title = cursor.getString(titleIndex); 2899 } else { 2900 info.title = ""; 2901 } 2902 info.setIcon(mIconCache.getIcon(intent, info.title.toString())); 2903 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; 2904 info.restoredIntent = intent; 2905 return info; 2906 } 2907 2908 /** 2909 * Make an Intent object for a restored application or shortcut item that points 2910 * to the market page for the item. 2911 */ 2912 private Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) { 2913 final boolean debug = false; 2914 ComponentName componentName = intent.getComponent(); 2915 Intent marketIntent = new Intent(Intent.ACTION_VIEW); 2916 Uri marketUri = new Uri.Builder() 2917 .scheme("market") 2918 .authority("details") 2919 .appendQueryParameter("id", componentName.getPackageName()) 2920 .build(); 2921 if (debug) Log.d(TAG, "manufactured intent uri: " + marketUri.toString()); 2922 marketIntent.setData(marketUri); 2923 return marketIntent; 2924 } 2925 2926 /** 2927 * This is called from the code that adds shortcuts from the intent receiver. This 2928 * doesn't have a Cursor, but 2929 */ 2930 public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context) { 2931 return getShortcutInfo(manager, intent, context, null, -1, -1, null); 2932 } 2933 2934 /** 2935 * Make an ShortcutInfo object for a shortcut that is an application. 2936 * 2937 * If c is not null, then it will be used to fill in missing data like the title and icon. 2938 */ 2939 public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context, 2940 Cursor c, int iconIndex, int titleIndex, HashMap<Object, CharSequence> labelCache) { 2941 ComponentName componentName = intent.getComponent(); 2942 final ShortcutInfo info = new ShortcutInfo(); 2943 if (componentName != null && !isValidPackageComponent(manager, componentName)) { 2944 Log.d(TAG, "Invalid package found in getShortcutInfo: " + componentName); 2945 return null; 2946 } else { 2947 try { 2948 PackageInfo pi = manager.getPackageInfo(componentName.getPackageName(), 0); 2949 info.initFlagsAndFirstInstallTime(pi); 2950 } catch (NameNotFoundException e) { 2951 Log.d(TAG, "getPackInfo failed for package " + 2952 componentName.getPackageName()); 2953 } 2954 } 2955 2956 // TODO: See if the PackageManager knows about this case. If it doesn't 2957 // then return null & delete this. 2958 2959 // the resource -- This may implicitly give us back the fallback icon, 2960 // but don't worry about that. All we're doing with usingFallbackIcon is 2961 // to avoid saving lots of copies of that in the database, and most apps 2962 // have icons anyway. 2963 2964 // Attempt to use queryIntentActivities to get the ResolveInfo (with IntentFilter info) and 2965 // if that fails, or is ambiguious, fallback to the standard way of getting the resolve info 2966 // via resolveActivity(). 2967 Bitmap icon = null; 2968 ResolveInfo resolveInfo = null; 2969 ComponentName oldComponent = intent.getComponent(); 2970 Intent newIntent = new Intent(intent.getAction(), null); 2971 newIntent.addCategory(Intent.CATEGORY_LAUNCHER); 2972 newIntent.setPackage(oldComponent.getPackageName()); 2973 List<ResolveInfo> infos = manager.queryIntentActivities(newIntent, 0); 2974 for (ResolveInfo i : infos) { 2975 ComponentName cn = new ComponentName(i.activityInfo.packageName, 2976 i.activityInfo.name); 2977 if (cn.equals(oldComponent)) { 2978 resolveInfo = i; 2979 } 2980 } 2981 if (resolveInfo == null) { 2982 resolveInfo = manager.resolveActivity(intent, 0); 2983 } 2984 if (resolveInfo != null) { 2985 icon = mIconCache.getIcon(componentName, resolveInfo, labelCache); 2986 } 2987 // the db 2988 if (icon == null) { 2989 if (c != null) { 2990 icon = getIconFromCursor(c, iconIndex, context); 2991 } 2992 } 2993 // the fallback icon 2994 if (icon == null) { 2995 icon = getFallbackIcon(); 2996 info.usingFallbackIcon = true; 2997 } 2998 info.setIcon(icon); 2999 3000 // from the resource 3001 if (resolveInfo != null) { 3002 ComponentName key = LauncherModel.getComponentNameFromResolveInfo(resolveInfo); 3003 if (labelCache != null && labelCache.containsKey(key)) { 3004 info.title = labelCache.get(key); 3005 } else { 3006 info.title = resolveInfo.activityInfo.loadLabel(manager); 3007 if (labelCache != null) { 3008 labelCache.put(key, info.title); 3009 } 3010 } 3011 } 3012 // from the db 3013 if (info.title == null) { 3014 if (c != null) { 3015 info.title = c.getString(titleIndex); 3016 } 3017 } 3018 // fall back to the class name of the activity 3019 if (info.title == null) { 3020 info.title = componentName.getClassName(); 3021 } 3022 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; 3023 return info; 3024 } 3025 3026 static ArrayList<ItemInfo> filterItemInfos(Collection<ItemInfo> infos, 3027 ItemInfoFilter f) { 3028 HashSet<ItemInfo> filtered = new HashSet<ItemInfo>(); 3029 for (ItemInfo i : infos) { 3030 if (i instanceof ShortcutInfo) { 3031 ShortcutInfo info = (ShortcutInfo) i; 3032 ComponentName cn = info.intent.getComponent(); 3033 if (cn != null && f.filterItem(null, info, cn)) { 3034 filtered.add(info); 3035 } 3036 } else if (i instanceof FolderInfo) { 3037 FolderInfo info = (FolderInfo) i; 3038 for (ShortcutInfo s : info.contents) { 3039 ComponentName cn = s.intent.getComponent(); 3040 if (cn != null && f.filterItem(info, s, cn)) { 3041 filtered.add(s); 3042 } 3043 } 3044 } else if (i instanceof LauncherAppWidgetInfo) { 3045 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i; 3046 ComponentName cn = info.providerName; 3047 if (cn != null && f.filterItem(null, info, cn)) { 3048 filtered.add(info); 3049 } 3050 } 3051 } 3052 return new ArrayList<ItemInfo>(filtered); 3053 } 3054 3055 private ArrayList<ItemInfo> getItemInfoForPackageName(final String pn) { 3056 ItemInfoFilter filter = new ItemInfoFilter() { 3057 @Override 3058 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) { 3059 return cn.getPackageName().equals(pn); 3060 } 3061 }; 3062 return filterItemInfos(sBgItemsIdMap.values(), filter); 3063 } 3064 3065 private ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname) { 3066 ItemInfoFilter filter = new ItemInfoFilter() { 3067 @Override 3068 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) { 3069 return cn.equals(cname); 3070 } 3071 }; 3072 return filterItemInfos(sBgItemsIdMap.values(), filter); 3073 } 3074 3075 public static boolean isShortcutInfoUpdateable(ItemInfo i) { 3076 if (i instanceof ShortcutInfo) { 3077 ShortcutInfo info = (ShortcutInfo) i; 3078 // We need to check for ACTION_MAIN otherwise getComponent() might 3079 // return null for some shortcuts (for instance, for shortcuts to 3080 // web pages.) 3081 Intent intent = info.intent; 3082 ComponentName name = intent.getComponent(); 3083 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION && 3084 Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 3085 return true; 3086 } 3087 // placeholder shortcuts get special treatment, let them through too. 3088 if (info.getRestoredIntent() != null) { 3089 return true; 3090 } 3091 } 3092 return false; 3093 } 3094 3095 /** 3096 * Make an ShortcutInfo object for a shortcut that isn't an application. 3097 */ 3098 private ShortcutInfo getShortcutInfo(Cursor c, Context context, 3099 int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex, 3100 int titleIndex) { 3101 3102 Bitmap icon = null; 3103 final ShortcutInfo info = new ShortcutInfo(); 3104 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; 3105 3106 // TODO: If there's an explicit component and we can't install that, delete it. 3107 3108 info.title = c.getString(titleIndex); 3109 3110 int iconType = c.getInt(iconTypeIndex); 3111 switch (iconType) { 3112 case LauncherSettings.Favorites.ICON_TYPE_RESOURCE: 3113 String packageName = c.getString(iconPackageIndex); 3114 String resourceName = c.getString(iconResourceIndex); 3115 PackageManager packageManager = context.getPackageManager(); 3116 info.customIcon = false; 3117 // the resource 3118 try { 3119 Resources resources = packageManager.getResourcesForApplication(packageName); 3120 if (resources != null) { 3121 final int id = resources.getIdentifier(resourceName, null, null); 3122 icon = Utilities.createIconBitmap( 3123 mIconCache.getFullResIcon(resources, id), context); 3124 } 3125 } catch (Exception e) { 3126 // drop this. we have other places to look for icons 3127 } 3128 // the db 3129 if (icon == null) { 3130 icon = getIconFromCursor(c, iconIndex, context); 3131 } 3132 // the fallback icon 3133 if (icon == null) { 3134 icon = getFallbackIcon(); 3135 info.usingFallbackIcon = true; 3136 } 3137 break; 3138 case LauncherSettings.Favorites.ICON_TYPE_BITMAP: 3139 icon = getIconFromCursor(c, iconIndex, context); 3140 if (icon == null) { 3141 icon = getFallbackIcon(); 3142 info.customIcon = false; 3143 info.usingFallbackIcon = true; 3144 } else { 3145 info.customIcon = true; 3146 } 3147 break; 3148 default: 3149 icon = getFallbackIcon(); 3150 info.usingFallbackIcon = true; 3151 info.customIcon = false; 3152 break; 3153 } 3154 info.setIcon(icon); 3155 return info; 3156 } 3157 3158 Bitmap getIconFromCursor(Cursor c, int iconIndex, Context context) { 3159 @SuppressWarnings("all") // suppress dead code warning 3160 final boolean debug = false; 3161 if (debug) { 3162 Log.d(TAG, "getIconFromCursor app=" 3163 + c.getString(c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE))); 3164 } 3165 byte[] data = c.getBlob(iconIndex); 3166 try { 3167 return Utilities.createIconBitmap( 3168 BitmapFactory.decodeByteArray(data, 0, data.length), context); 3169 } catch (Exception e) { 3170 return null; 3171 } 3172 } 3173 3174 ShortcutInfo addShortcut(Context context, Intent data, long container, int screen, 3175 int cellX, int cellY, boolean notify) { 3176 final ShortcutInfo info = infoFromShortcutIntent(context, data, null); 3177 if (info == null) { 3178 return null; 3179 } 3180 addItemToDatabase(context, info, container, screen, cellX, cellY, notify); 3181 3182 return info; 3183 } 3184 3185 /** 3186 * Attempts to find an AppWidgetProviderInfo that matches the given component. 3187 */ 3188 AppWidgetProviderInfo findAppWidgetProviderInfoWithComponent(Context context, 3189 ComponentName component) { 3190 List<AppWidgetProviderInfo> widgets = 3191 AppWidgetManager.getInstance(context).getInstalledProviders(); 3192 for (AppWidgetProviderInfo info : widgets) { 3193 if (info.provider.equals(component)) { 3194 return info; 3195 } 3196 } 3197 return null; 3198 } 3199 3200 /** 3201 * Returns a list of all the widgets that can handle configuration with a particular mimeType. 3202 */ 3203 List<WidgetMimeTypeHandlerData> resolveWidgetsForMimeType(Context context, String mimeType) { 3204 final PackageManager packageManager = context.getPackageManager(); 3205 final List<WidgetMimeTypeHandlerData> supportedConfigurationActivities = 3206 new ArrayList<WidgetMimeTypeHandlerData>(); 3207 3208 final Intent supportsIntent = 3209 new Intent(InstallWidgetReceiver.ACTION_SUPPORTS_CLIPDATA_MIMETYPE); 3210 supportsIntent.setType(mimeType); 3211 3212 // Create a set of widget configuration components that we can test against 3213 final List<AppWidgetProviderInfo> widgets = 3214 AppWidgetManager.getInstance(context).getInstalledProviders(); 3215 final HashMap<ComponentName, AppWidgetProviderInfo> configurationComponentToWidget = 3216 new HashMap<ComponentName, AppWidgetProviderInfo>(); 3217 for (AppWidgetProviderInfo info : widgets) { 3218 configurationComponentToWidget.put(info.configure, info); 3219 } 3220 3221 // Run through each of the intents that can handle this type of clip data, and cross 3222 // reference them with the components that are actual configuration components 3223 final List<ResolveInfo> activities = packageManager.queryIntentActivities(supportsIntent, 3224 PackageManager.MATCH_DEFAULT_ONLY); 3225 for (ResolveInfo info : activities) { 3226 final ActivityInfo activityInfo = info.activityInfo; 3227 final ComponentName infoComponent = new ComponentName(activityInfo.packageName, 3228 activityInfo.name); 3229 if (configurationComponentToWidget.containsKey(infoComponent)) { 3230 supportedConfigurationActivities.add( 3231 new InstallWidgetReceiver.WidgetMimeTypeHandlerData(info, 3232 configurationComponentToWidget.get(infoComponent))); 3233 } 3234 } 3235 return supportedConfigurationActivities; 3236 } 3237 3238 ShortcutInfo infoFromShortcutIntent(Context context, Intent data, Bitmap fallbackIcon) { 3239 Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); 3240 String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); 3241 Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON); 3242 3243 if (intent == null) { 3244 // If the intent is null, we can't construct a valid ShortcutInfo, so we return null 3245 Log.e(TAG, "Can't construct ShorcutInfo with null intent"); 3246 return null; 3247 } 3248 3249 Bitmap icon = null; 3250 boolean customIcon = false; 3251 ShortcutIconResource iconResource = null; 3252 3253 if (bitmap != null && bitmap instanceof Bitmap) { 3254 icon = Utilities.createIconBitmap(new FastBitmapDrawable((Bitmap)bitmap), context); 3255 customIcon = true; 3256 } else { 3257 Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); 3258 if (extra != null && extra instanceof ShortcutIconResource) { 3259 try { 3260 iconResource = (ShortcutIconResource) extra; 3261 final PackageManager packageManager = context.getPackageManager(); 3262 Resources resources = packageManager.getResourcesForApplication( 3263 iconResource.packageName); 3264 final int id = resources.getIdentifier(iconResource.resourceName, null, null); 3265 icon = Utilities.createIconBitmap( 3266 mIconCache.getFullResIcon(resources, id), context); 3267 } catch (Exception e) { 3268 Log.w(TAG, "Could not load shortcut icon: " + extra); 3269 } 3270 } 3271 } 3272 3273 final ShortcutInfo info = new ShortcutInfo(); 3274 3275 if (icon == null) { 3276 if (fallbackIcon != null) { 3277 icon = fallbackIcon; 3278 } else { 3279 icon = getFallbackIcon(); 3280 info.usingFallbackIcon = true; 3281 } 3282 } 3283 info.setIcon(icon); 3284 3285 info.title = name; 3286 info.intent = intent; 3287 info.customIcon = customIcon; 3288 info.iconResource = iconResource; 3289 3290 return info; 3291 } 3292 3293 boolean queueIconToBeChecked(HashMap<Object, byte[]> cache, ShortcutInfo info, Cursor c, 3294 int iconIndex) { 3295 // If apps can't be on SD, don't even bother. 3296 if (!mAppsCanBeOnRemoveableStorage) { 3297 return false; 3298 } 3299 // If this icon doesn't have a custom icon, check to see 3300 // what's stored in the DB, and if it doesn't match what 3301 // we're going to show, store what we are going to show back 3302 // into the DB. We do this so when we're loading, if the 3303 // package manager can't find an icon (for example because 3304 // the app is on SD) then we can use that instead. 3305 if (!info.customIcon && !info.usingFallbackIcon) { 3306 cache.put(info, c.getBlob(iconIndex)); 3307 return true; 3308 } 3309 return false; 3310 } 3311 void updateSavedIcon(Context context, ShortcutInfo info, byte[] data) { 3312 boolean needSave = false; 3313 try { 3314 if (data != null) { 3315 Bitmap saved = BitmapFactory.decodeByteArray(data, 0, data.length); 3316 Bitmap loaded = info.getIcon(mIconCache); 3317 needSave = !saved.sameAs(loaded); 3318 } else { 3319 needSave = true; 3320 } 3321 } catch (Exception e) { 3322 needSave = true; 3323 } 3324 if (needSave) { 3325 Log.d(TAG, "going to save icon bitmap for info=" + info); 3326 // This is slower than is ideal, but this only happens once 3327 // or when the app is updated with a new icon. 3328 updateItemInDatabase(context, info); 3329 } 3330 } 3331 3332 /** 3333 * Return an existing FolderInfo object if we have encountered this ID previously, 3334 * or make a new one. 3335 */ 3336 private static FolderInfo findOrMakeFolder(HashMap<Long, FolderInfo> folders, long id) { 3337 // See if a placeholder was created for us already 3338 FolderInfo folderInfo = folders.get(id); 3339 if (folderInfo == null) { 3340 // No placeholder -- create a new instance 3341 folderInfo = new FolderInfo(); 3342 folders.put(id, folderInfo); 3343 } 3344 return folderInfo; 3345 } 3346 3347 public static final Comparator<AppInfo> getAppNameComparator() { 3348 final Collator collator = Collator.getInstance(); 3349 return new Comparator<AppInfo>() { 3350 public final int compare(AppInfo a, AppInfo b) { 3351 int result = collator.compare(a.title.toString().trim(), 3352 b.title.toString().trim()); 3353 if (result == 0) { 3354 result = a.componentName.compareTo(b.componentName); 3355 } 3356 return result; 3357 } 3358 }; 3359 } 3360 public static final Comparator<AppInfo> APP_INSTALL_TIME_COMPARATOR 3361 = new Comparator<AppInfo>() { 3362 public final int compare(AppInfo a, AppInfo b) { 3363 if (a.firstInstallTime < b.firstInstallTime) return 1; 3364 if (a.firstInstallTime > b.firstInstallTime) return -1; 3365 return 0; 3366 } 3367 }; 3368 public static final Comparator<AppWidgetProviderInfo> getWidgetNameComparator() { 3369 final Collator collator = Collator.getInstance(); 3370 return new Comparator<AppWidgetProviderInfo>() { 3371 public final int compare(AppWidgetProviderInfo a, AppWidgetProviderInfo b) { 3372 return collator.compare(a.label.toString().trim(), b.label.toString().trim()); 3373 } 3374 }; 3375 } 3376 static ComponentName getComponentNameFromResolveInfo(ResolveInfo info) { 3377 if (info.activityInfo != null) { 3378 return new ComponentName(info.activityInfo.packageName, info.activityInfo.name); 3379 } else { 3380 return new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name); 3381 } 3382 } 3383 public static class ShortcutNameComparator implements Comparator<ResolveInfo> { 3384 private Collator mCollator; 3385 private PackageManager mPackageManager; 3386 private HashMap<Object, CharSequence> mLabelCache; 3387 ShortcutNameComparator(PackageManager pm) { 3388 mPackageManager = pm; 3389 mLabelCache = new HashMap<Object, CharSequence>(); 3390 mCollator = Collator.getInstance(); 3391 } 3392 ShortcutNameComparator(PackageManager pm, HashMap<Object, CharSequence> labelCache) { 3393 mPackageManager = pm; 3394 mLabelCache = labelCache; 3395 mCollator = Collator.getInstance(); 3396 } 3397 public final int compare(ResolveInfo a, ResolveInfo b) { 3398 CharSequence labelA, labelB; 3399 ComponentName keyA = LauncherModel.getComponentNameFromResolveInfo(a); 3400 ComponentName keyB = LauncherModel.getComponentNameFromResolveInfo(b); 3401 if (mLabelCache.containsKey(keyA)) { 3402 labelA = mLabelCache.get(keyA); 3403 } else { 3404 labelA = a.loadLabel(mPackageManager).toString().trim(); 3405 3406 mLabelCache.put(keyA, labelA); 3407 } 3408 if (mLabelCache.containsKey(keyB)) { 3409 labelB = mLabelCache.get(keyB); 3410 } else { 3411 labelB = b.loadLabel(mPackageManager).toString().trim(); 3412 3413 mLabelCache.put(keyB, labelB); 3414 } 3415 return mCollator.compare(labelA, labelB); 3416 } 3417 }; 3418 public static class WidgetAndShortcutNameComparator implements Comparator<Object> { 3419 private Collator mCollator; 3420 private PackageManager mPackageManager; 3421 private HashMap<Object, String> mLabelCache; 3422 WidgetAndShortcutNameComparator(PackageManager pm) { 3423 mPackageManager = pm; 3424 mLabelCache = new HashMap<Object, String>(); 3425 mCollator = Collator.getInstance(); 3426 } 3427 public final int compare(Object a, Object b) { 3428 String labelA, labelB; 3429 if (mLabelCache.containsKey(a)) { 3430 labelA = mLabelCache.get(a); 3431 } else { 3432 labelA = (a instanceof AppWidgetProviderInfo) ? 3433 ((AppWidgetProviderInfo) a).label : 3434 ((ResolveInfo) a).loadLabel(mPackageManager).toString().trim(); 3435 mLabelCache.put(a, labelA); 3436 } 3437 if (mLabelCache.containsKey(b)) { 3438 labelB = mLabelCache.get(b); 3439 } else { 3440 labelB = (b instanceof AppWidgetProviderInfo) ? 3441 ((AppWidgetProviderInfo) b).label : 3442 ((ResolveInfo) b).loadLabel(mPackageManager).toString().trim(); 3443 mLabelCache.put(b, labelB); 3444 } 3445 return mCollator.compare(labelA, labelB); 3446 } 3447 }; 3448 3449 public void dumpState() { 3450 Log.d(TAG, "mCallbacks=" + mCallbacks); 3451 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data); 3452 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added); 3453 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed); 3454 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified); 3455 if (mLoaderTask != null) { 3456 mLoaderTask.dumpState(); 3457 } else { 3458 Log.d(TAG, "mLoaderTask=null"); 3459 } 3460 } 3461} 3462