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