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