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