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 { sWorkerThread.start()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 { setLoadOnResume()191 public boolean setLoadOnResume(); getCurrentWorkspaceScreen()192 public int getCurrentWorkspaceScreen(); startBinding()193 public void startBinding(); bindItems(ArrayList<ItemInfo> shortcuts, int start, int end, boolean forceAnimateIcons)194 public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end, 195 boolean forceAnimateIcons); bindScreens(ArrayList<Long> orderedScreenIds)196 public void bindScreens(ArrayList<Long> orderedScreenIds); bindAddScreens(ArrayList<Long> orderedScreenIds)197 public void bindAddScreens(ArrayList<Long> orderedScreenIds); bindFolders(HashMap<Long,FolderInfo> folders)198 public void bindFolders(HashMap<Long,FolderInfo> folders); finishBindingItems(boolean upgradePath)199 public void finishBindingItems(boolean upgradePath); bindAppWidget(LauncherAppWidgetInfo info)200 public void bindAppWidget(LauncherAppWidgetInfo info); bindAllApplications(ArrayList<AppInfo> apps)201 public void bindAllApplications(ArrayList<AppInfo> apps); bindAppsAdded(ArrayList<Long> newScreens, ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated, ArrayList<AppInfo> addedApps)202 public void bindAppsAdded(ArrayList<Long> newScreens, 203 ArrayList<ItemInfo> addNotAnimated, 204 ArrayList<ItemInfo> addAnimated, 205 ArrayList<AppInfo> addedApps); bindAppsUpdated(ArrayList<AppInfo> apps)206 public void bindAppsUpdated(ArrayList<AppInfo> apps); bindShortcutsChanged(ArrayList<ShortcutInfo> updated, ArrayList<ShortcutInfo> removed, UserHandleCompat user)207 public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated, 208 ArrayList<ShortcutInfo> removed, UserHandleCompat user); bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets)209 public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets); updatePackageState(ArrayList<PackageInstallInfo> installInfo)210 public void updatePackageState(ArrayList<PackageInstallInfo> installInfo); updatePackageBadge(String packageName)211 public void updatePackageBadge(String packageName); bindComponentsRemoved(ArrayList<String> packageNames, ArrayList<AppInfo> appInfos, UserHandleCompat user, int reason)212 public void bindComponentsRemoved(ArrayList<String> packageNames, 213 ArrayList<AppInfo> appInfos, UserHandleCompat user, int reason); bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts)214 public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts); bindSearchablesChanged()215 public void bindSearchablesChanged(); isAllAppsButtonRank(int rank)216 public boolean isAllAppsButtonRank(int rank); onPageBoundSynchronously(int page)217 public void onPageBoundSynchronously(int page); dumpLogsToLocalData()218 public void dumpLogsToLocalData(); 219 } 220 221 public interface ItemInfoFilter { filterItem(ItemInfo parent, ItemInfo info, ComponentName cn)222 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn); 223 } 224 LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter)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. */ runOnMainThread(Runnable r)260 private void runOnMainThread(Runnable r) { 261 runOnMainThread(r, 0); 262 } runOnMainThread(Runnable r, int type)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. */ runOnWorkerThread(Runnable r)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 canMigrateFromOldLauncherDb(Launcher launcher)283 boolean canMigrateFromOldLauncherDb(Launcher launcher) { 284 return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ; 285 } 286 findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> items, int[] xy, long screen)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 } findNextAvailableIconSpace(Context context, String name, Intent launchIntent, int firstScreenIndex, ArrayList<Long> workspaceScreens)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 setPackageState(final ArrayList<PackageInstallInfo> installInfo)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 updatePackageBadge(final String packageName)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 addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps)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 addAndBindAddedWorkspaceApps(final Context context, final ArrayList<ItemInfo> workspaceApps)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 unbindItemInfosAndClearQueuedBindRunnables()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 */ unbindWorkspaceItemsOnMainThread()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 */ addOrMoveItemInDatabase(Context context, ItemInfo item, long container, long screenId, int cellX, int cellY)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 checkItemInfoLocked( final long itemId, final ItemInfo item, StackTraceElement[] stackTrace)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 checkItemInfo(final ItemInfo item)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 updateItemInDatabaseHelper(Context context, final ContentValues values, final ItemInfo item, final String callingFunction)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 updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList, final ArrayList<ItemInfo> items, final String callingFunction)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 updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace)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 flushWorkerThread()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 */ moveItemInDatabase(Context context, final ItemInfo item, final long container, final long screenId, final int cellX, final int cellY)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 */ moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items, final long container, final int screen)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 */ modifyItemInDatabase(Context context, final ItemInfo item, final long container, final long screenId, final int cellX, final int cellY, final int spanX, final int spanY)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 */ updateItemInDatabase(Context context, final ItemInfo item)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 */ shortcutExists(Context context, String title, Intent intent, UserHandleCompat user)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 */ getItemsInLocalCoordinates(Context context)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 */ getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id)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 */ addItemToDatabase(Context context, final ItemInfo item, final long container, final long screenId, final int cellX, final int cellY, final boolean notify)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 */ getCellLayoutChildId( long container, long screen, int localCellX, int localCellY, int spanX, int spanY)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 getItemsByPackageName( final String pn, final UserHandleCompat user)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 */ deletePackageFromDatabase(Context context, final String pn, final UserHandleCompat user)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 */ deleteItemFromDatabase(Context context, final ItemInfo item)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 */ deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items)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 */ updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens)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 */ deleteFolderContentsFromDatabase(Context context, final FolderInfo info)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 */ initialize(Callbacks callbacks)1214 public void initialize(Callbacks callbacks) { 1215 synchronized (mLock) { 1216 mCallbacks = new WeakReference<Callbacks>(callbacks); 1217 } 1218 } 1219 1220 @Override onPackageChanged(String packageName, UserHandleCompat user)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 onPackageRemoved(String packageName, UserHandleCompat user)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 onPackageAdded(String packageName, UserHandleCompat user)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 onPackagesAvailable(String[] packageNames, UserHandleCompat user, boolean replacing)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 onPackagesUnavailable(String[] packageNames, UserHandleCompat user, boolean replacing)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 onReceive(Context context, Intent intent)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 forceReload()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 resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded)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 */ startLoaderFromBackground()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 stopLoaderLocked()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 isCurrentCallbacks(Callbacks callbacks)1357 public boolean isCurrentCallbacks(Callbacks callbacks) { 1358 return (mCallbacks != null && mCallbacks.get() == callbacks); 1359 } 1360 startLoader(boolean isLaunching, int synchronousBindPage)1361 public void startLoader(boolean isLaunching, int synchronousBindPage) { 1362 startLoader(isLaunching, synchronousBindPage, LOADER_FLAG_NONE); 1363 } 1364 startLoader(boolean isLaunching, int synchronousBindPage, int loadFlags)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 bindRemainingSynchronousPages()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 stopLoader()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 */ loadWorkspaceScreensDb(Context context)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 isAllAppsLoaded()1453 public boolean isAllAppsLoaded() { 1454 return mAllAppsLoaded; 1455 } 1456 isLoadingWorkspace()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 LoaderTask(Context context, boolean isLaunching, int flags)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 isLaunching()1489 boolean isLaunching() { 1490 return mIsLaunching; 1491 } 1492 isLoadingWorkspace()1493 boolean isLoadingWorkspace() { 1494 return mIsLoadingAndBindingWorkspace; 1495 } 1496 1497 /** Returns whether this is an upgrade path */ loadAndBindWorkspace()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 waitForIdle()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 runBindSynchronousPage(int synchronousBindPage)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 run()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 stopLocked()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 */ tryGetCallbacks(Callbacks oldCallbacks)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 verifyApplications()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 checkItemPlacement(HashMap<Long, ItemInfo[][]> occupied, ItemInfo item)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 */ clearSBgDataStructures()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 */ loadWorkspace()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. */ filterCurrentWorkspaceItems(long currentScreenId, ArrayList<ItemInfo> allWorkspaceItems, ArrayList<ItemInfo> currentScreenItems, ArrayList<ItemInfo> otherScreenItems)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. */ filterCurrentAppWidgets(long currentScreenId, ArrayList<LauncherAppWidgetInfo> appWidgets, ArrayList<LauncherAppWidgetInfo> currentScreenWidgets, ArrayList<LauncherAppWidgetInfo> otherScreenWidgets)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. */ filterCurrentFolders(long currentScreenId, HashMap<Long, ItemInfo> itemsIdMap, HashMap<Long, FolderInfo> folders, HashMap<Long, FolderInfo> currentScreenFolders, HashMap<Long, FolderInfo> otherScreenFolders)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) */ sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems)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 bindWorkspaceScreens(final Callbacks oldCallbacks, final ArrayList<Long> orderedScreens)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 bindWorkspaceItems(final Callbacks oldCallbacks, final ArrayList<ItemInfo> workspaceItems, final ArrayList<LauncherAppWidgetInfo> appWidgets, final HashMap<Long, FolderInfo> folders, ArrayList<Runnable> deferredBindRunnables)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 */ bindWorkspace(int synchronizeBindPage, final boolean isUpgradePath)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