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