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