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