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