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