From c078d6554ca58a7ceed0d14e0a90a779bd38758c Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Thu, 17 Apr 2025 23:41:12 -0700 Subject: [PATCH] Moving various launcher binding logic, outside LauncherModel This brings the BgCallbacks closer to a repository pattern making is easier to switch eventually Bug: 390572144 Flag: EXEMPT refactor Test: Updated AsyncBindingTest to use real ModelCalbacks Change-Id: I9c932b00ea8ac7330473b9c0f5d778453fe7a390 --- .../hybridhotseat/HotseatEduController.java | 16 +- .../taskbar/TaskbarModelCallbacks.java | 40 +-- .../launcher3/taskbar/TaskbarOverflowTest.kt | 16 +- src/com/android/launcher3/Launcher.java | 84 ++---- src/com/android/launcher3/LauncherModel.kt | 4 - src/com/android/launcher3/ModelCallbacks.kt | 281 +++++++++++++++--- .../LauncherAccessibilityDelegate.java | 2 +- .../ShortcutMenuAccessibilityDelegate.java | 3 +- .../model/AddWorkspaceItemsTask.java | 23 +- .../launcher3/model/BaseLauncherBinder.java | 215 +------------- .../android/launcher3/model/BgDataModel.java | 54 ++-- .../android/launcher3/model/ModelWriter.java | 2 +- .../SecondaryDisplayLauncher.java | 5 - .../model/AddWorkspaceItemsTaskTest.kt | 32 +- .../launcher3/model/AsyncBindingTest.kt | 129 ++++---- .../model/ModelMultiCallbacksTest.java | 71 ++--- 16 files changed, 401 insertions(+), 576 deletions(-) diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java index b77c43fc2f..7c53360ea4 100644 --- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java +++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java @@ -33,7 +33,6 @@ import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; -import com.android.launcher3.util.IntArray; import com.android.launcher3.views.ArrowTipView; import com.android.launcher3.views.Snackbar; @@ -57,7 +56,6 @@ public class HotseatEduController { private HotseatEduDialog mActiveDialog; private ArrayList mNewItems = new ArrayList<>(); - private IntArray mNewScreens = null; HotseatEduController(Launcher launcher) { mLauncher = launcher; @@ -96,7 +94,6 @@ public class HotseatEduController { } if (pageId == -1) { pageId = mLauncher.getModel().getModelDbController().getNewScreenId(); - mNewScreens = IntArray.wrap(pageId); } boolean isPortrait = !mLauncher.getDeviceProfile().isVerticalBarLayout(); int hotseatItemsNum = mLauncher.getDeviceProfile().numShownHotseatIcons; @@ -117,18 +114,7 @@ public class HotseatEduController { void moveHotseatItems() { mHotseat.removeAllViewsInLayout(); if (!mNewItems.isEmpty()) { - int lastPage = mNewItems.get(mNewItems.size() - 1).screenId; - ArrayList animated = new ArrayList<>(); - ArrayList nonAnimated = new ArrayList<>(); - - for (ItemInfo info : mNewItems) { - if (info.screenId == lastPage) { - animated.add(info); - } else { - nonAnimated.add(info); - } - } - mLauncher.bindAppsAdded(mNewScreens, nonAnimated, animated); + mLauncher.bindItemsAdded(mNewItems); } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java index 836c06cb38..b610f6786e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java @@ -29,12 +29,12 @@ import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.celllayout.CellInfo; import com.android.launcher3.model.BgDataModel; import com.android.launcher3.model.BgDataModel.FixedContainerItems; +import com.android.launcher3.model.StringCache; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.taskbar.TaskbarView.TaskbarLayoutParams; import com.android.launcher3.util.ComponentKey; -import com.android.launcher3.util.IntArray; -import com.android.launcher3.util.IntSet; +import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.LauncherBindableItemsContainer; import com.android.launcher3.util.PackageUserKey; @@ -42,7 +42,6 @@ import com.android.launcher3.util.Preconditions; import com.android.quickstep.util.GroupTask; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -68,7 +67,6 @@ public class TaskbarModelCallbacks implements // Used to defer any UI updates during the SUW unstash animation. private boolean mDeferUpdatesForSUW; private Runnable mDeferredUpdates; - private boolean mBindingItems = false; public TaskbarModelCallbacks( TaskbarActivityContext context, TaskbarView container) { @@ -81,31 +79,25 @@ public class TaskbarModelCallbacks implements } @Override - public void startBinding() { - mBindingItems = true; + public void bindCompleteModel(IntSparseArrayMap itemIdMap, + List extraItems, StringCache stringCache, boolean isBindingSync) { mHotseatItems.clear(); mPredictedItems = Collections.emptyList(); - } + handleItemsAdded(itemIdMap); - @Override - public void finishBindingItems(IntSet pagesBoundFirst) { - mBindingItems = false; + for (FixedContainerItems item: extraItems) { + if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) { + mPredictedItems = item.items; + } else if (item.containerId == Favorites.CONTAINER_PREDICTION) { + mControllers.taskbarAllAppsController.setPredictedApps(item.items); + } + } commitItemsToUI(); } @Override - public void bindAppsAdded(IntArray newScreens, ArrayList addNotAnimated, - ArrayList addAnimated) { - boolean add1 = handleItemsAdded(addNotAnimated); - boolean add2 = handleItemsAdded(addAnimated); - if (add1 || add2) { - commitItemsToUI(); - } - } - - @Override - public void bindItems(List shortcuts, boolean forceAnimateIcons) { - if (handleItemsAdded(shortcuts)) { + public void bindItemsAdded(List items) { + if (handleItemsAdded(items)) { commitItemsToUI(); } } @@ -184,10 +176,6 @@ public class TaskbarModelCallbacks implements } private void commitItemsToUI() { - if (mBindingItems) { - return; - } - ItemInfo[] hotseatItemInfos = new ItemInfo[mContext.getDeviceProfile().numShownHotseatIcons]; int predictionSize = mPredictedItems.size(); diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt index 61d7c77f2f..7be58fbfa1 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt @@ -33,6 +33,8 @@ import com.android.launcher3.R import com.android.launcher3.dagger.LauncherAppSingleton import com.android.launcher3.dagger.LauncherComponentProvider.appComponent import com.android.launcher3.model.BgDataModel +import com.android.launcher3.model.BgDataModel.FixedContainerItems +import com.android.launcher3.model.StringCache import com.android.launcher3.model.data.ItemInfo import com.android.launcher3.model.data.TaskItemInfo import com.android.launcher3.model.data.WorkspaceItemInfo @@ -55,6 +57,7 @@ import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext import com.android.launcher3.util.AllModulesForTest import com.android.launcher3.util.FakePrefsModule +import com.android.launcher3.util.IntSparseArrayMap import com.android.launcher3.util.LauncherMultivalentJUnit import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices import com.android.launcher3.util.Preconditions.assertNotNull @@ -566,7 +569,7 @@ class TaskbarOverflowTest { taskbarView.updateItems(hotseatItems, recentAppsController.shownTasks) modelCallback.recentAppsController = recentAppsController context.baseContext.appComponent.launcherAppState.model.addCallbacksAndLoad(modelCallback) - modelCallback.bindItems(hotseatItems.toList(), false) + modelCallback.bindItemsAdded(hotseatItems.toList()) return taskbarView } @@ -691,9 +694,16 @@ class TaskbarOverflowTest { var hotseatItems = mutableListOf() var recentAppsController: TaskbarRecentAppsController? = null - override fun bindItems(shortcuts: List, forceAnimateIcons: Boolean) { + override fun bindCompleteModel( + itemIdMap: IntSparseArrayMap, + extraItems: MutableList, + stringCache: StringCache, + isBindingSync: Boolean, + ) = bindItemsAdded(itemIdMap.toList()) + + override fun bindItemsAdded(items: List) { runOnMainSync { - shortcuts + items .filter { item -> item is WorkspaceItemInfo && !hotseatItems.any { it.targetPackage == item.targetPackage } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 2d77762563..0dd3ff2c2e 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -189,7 +189,6 @@ import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.dragndrop.LauncherDragController; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; -import com.android.launcher3.icons.IconCache; import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.logger.LauncherAtom.ContainerInfo; @@ -202,6 +201,7 @@ import com.android.launcher3.logging.StartupLatencyLogger; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.logging.StatsLogManager.LauncherLatencyEvent; import com.android.launcher3.model.BgDataModel.Callbacks; +import com.android.launcher3.model.BgDataModel.FixedContainerItems; import com.android.launcher3.model.ItemInstallQueue; import com.android.launcher3.model.ModelWriter; import com.android.launcher3.model.StringCache; @@ -230,8 +230,8 @@ import com.android.launcher3.util.BackPressHandler; import com.android.launcher3.util.CannedAnimationCoordinator; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.ContextTracker; -import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; +import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.ItemInflater; import com.android.launcher3.util.KeyboardShortcutsDelegate; import com.android.launcher3.util.LauncherBindableItemsContainer; @@ -285,7 +285,6 @@ import java.util.Optional; import java.util.Set; import java.util.function.Predicate; import java.util.function.Supplier; -import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -372,7 +371,6 @@ public class Launcher extends StatefulActivity private LauncherModel mModel; private ModelWriter mModelWriter; - private IconCache mIconCache; private LauncherAccessibilityDelegate mAccessibilityDelegate; private PopupDataProvider mPopupDataProvider; @@ -525,7 +523,6 @@ public class Launcher extends StatefulActivity initDeviceProfile(idp); idp.addOnChangeListener(this); mSharedPrefs = LauncherPrefs.getPrefs(this); - mIconCache = app.getIconCache(); mAccessibilityDelegate = createAccessibilityDelegate(); initDragController(); @@ -2200,60 +2197,21 @@ public class Launcher extends StatefulActivity } @Override - public IntSet getPagesToBindSynchronously(IntArray orderedScreenIds) { - return mModelCallbacks.getPagesToBindSynchronously(orderedScreenIds); + public void bindCompleteModelAsync(IntSparseArrayMap itemIdMap, + List extraItems, StringCache stringCache, boolean isBindingSync) { + mModelCallbacks.bindCompleteModelAsync(itemIdMap, extraItems, stringCache, isBindingSync); } @Override - public void startBinding() { - mModelCallbacks.startBinding(); + public void bindItemsAdded(@NonNull List items) { + mModelCallbacks.bindItemsAdded(items); } - @Override - public void bindScreens(IntArray orderedScreenIds) { - mModelCallbacks.bindScreens(orderedScreenIds); - } - - /** - * Remove odd number because they are already included when isTwoPanels and add the pair screen - * if not present. - */ - private IntArray filterTwoPanelScreenIds(IntArray orderedScreenIds) { - IntSet screenIds = IntSet.wrap(orderedScreenIds); - orderedScreenIds.forEach(screenId -> { - if (screenId % 2 == 1) { - screenIds.remove(screenId); - // In case the pair is not added, add it - if (!mWorkspace.containsScreenId(screenId - 1)) { - screenIds.add(screenId - 1); - } - } - }); - return screenIds.getArray(); - } - - @Override - public void bindAppsAdded(IntArray newScreens, ArrayList addNotAnimated, - ArrayList addAnimated) { - mModelCallbacks.bindAppsAdded(newScreens, addNotAnimated, addAnimated); - } - - /** - * Bind the items start-end from the list. - * - * Implementation of the method from LauncherModel.Callbacks. - */ - @Override - public void bindItems(final List items, final boolean forceAnimateIcons) { - bindInflatedItems(items.stream() - .map(i -> Pair.create(i, getItemInflater().inflateItem(i))) - .collect(Collectors.toList()), - forceAnimateIcons ? new AnimatorSet() : null); - } - - @Override - public void bindInflatedItems(List> items) { - bindInflatedItems(items, null); + /** Inflates the binds the provided item using animation */ + public void inflateAndBindItemWithAnimation(ItemInfo info) { + bindInflatedItems( + Collections.singletonList(Pair.create(info, getItemInflater().inflateItem(info))), + new AnimatorSet()); } /** @@ -2365,6 +2323,9 @@ public class Launcher extends StatefulActivity return info; } + /** Called when a new LauncherModel data binding is starting */ + public void startBinding() { } + /** * Call back when ModelCallbacks finish binding the Launcher data. */ @@ -2379,19 +2340,11 @@ public class Launcher extends StatefulActivity .logCardinality(workspaceItemCount) .logEnd(LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC); } - MAIN_EXECUTOR.getHandler().postAtFrontOfQueue(() -> { + MAIN_EXECUTOR.getHandler().postAtFrontOfQueue(() -> mStartupLatencyLogger .logEnd(LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION) .log() - .reset(); - }); - } - - @Override - public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks, - RunnableList onCompleteSignal, int workspaceItemCount, boolean isBindSync) { - mModelCallbacks.onInitialBindComplete(boundPages, pendingTasks, onCompleteSignal, - workspaceItemCount, isBindSync); + .reset()); if (mIsColdStartupAfterReboot) { Trace.endAsyncSection(COLD_STARTUP_TRACE_METHOD_NAME, COLD_STARTUP_TRACE_COOKIE); @@ -2404,7 +2357,7 @@ public class Launcher extends StatefulActivity * Implementation of the method from LauncherModel.Callbacks. */ public void finishBindingItems(IntSet pagesBoundFirst) { - mModelCallbacks.finishBindingItems(pagesBoundFirst); + TestEventEmitter.sendEvent(TestEvent.WORKSPACE_FINISH_LOADING); } private boolean canAnimatePageChange() { @@ -3056,7 +3009,6 @@ public class Launcher extends StatefulActivity return super.getStatsLogManager().withDefaultInstanceId(mAllAppsSessionLogId); } - @Override @NonNull public ItemInflater getItemInflater() { return mItemInflater; diff --git a/src/com/android/launcher3/LauncherModel.kt b/src/com/android/launcher3/LauncherModel.kt index 6d35de5020..b3e99050c9 100644 --- a/src/com/android/launcher3/LauncherModel.kt +++ b/src/com/android/launcher3/LauncherModel.kt @@ -48,7 +48,6 @@ import com.android.launcher3.model.data.WorkspaceItemInfo import com.android.launcher3.pm.UserCache import com.android.launcher3.shortcuts.ShortcutRequest import com.android.launcher3.util.DaggerSingletonTracker -import com.android.launcher3.util.Executors.MAIN_EXECUTOR import com.android.launcher3.util.Executors.MODEL_EXECUTOR import com.android.launcher3.util.PackageUserKey import java.io.PrintWriter @@ -281,9 +280,6 @@ constructor( val bindAllCallbacks = wasRunning || !bindDirectly || newCallbacks.isEmpty() val callbacksList = if (bindAllCallbacks) callbacks else newCallbacks if (callbacksList.isNotEmpty()) { - // Clear any pending bind-runnables from the synchronized load process. - callbacksList.forEach { MAIN_EXECUTOR.execute(it::clearPendingBinds) } - val launcherBinder = binderFactory.createBinder(callbacksList) if (bindDirectly) { // Divide the set of loaded items into those that we are binding synchronously, diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt index cc5ac499a8..d0490c7a13 100644 --- a/src/com/android/launcher3/ModelCallbacks.kt +++ b/src/com/android/launcher3/ModelCallbacks.kt @@ -1,25 +1,39 @@ package com.android.launcher3 -import android.annotation.TargetApi -import android.os.Build +import android.animation.AnimatorSet +import android.os.CancellationSignal import android.os.Trace import android.util.Log +import android.util.Pair +import androidx.annotation.AnyThread import androidx.annotation.UiThread +import androidx.annotation.VisibleForTesting +import com.android.launcher3.BuildConfig.QSB_ON_FIRST_SCREEN import com.android.launcher3.LauncherConstants.TraceEvents +import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP +import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT import com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET import com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID import com.android.launcher3.allapps.AllAppsStore import com.android.launcher3.config.FeatureFlags -import com.android.launcher3.debug.TestEventEmitter -import com.android.launcher3.debug.TestEventEmitter.TestEvent import com.android.launcher3.model.BgDataModel +import com.android.launcher3.model.BgDataModel.FixedContainerItems +import com.android.launcher3.model.ItemInstallQueue +import com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING +import com.android.launcher3.model.ModelUtils.WIDGET_FILTER +import com.android.launcher3.model.ModelUtils.currentScreenContentFilter import com.android.launcher3.model.StringCache import com.android.launcher3.model.data.AppInfo import com.android.launcher3.model.data.ItemInfo import com.android.launcher3.popup.PopupContainerWithArrow import com.android.launcher3.util.ComponentKey +import com.android.launcher3.util.Executors +import com.android.launcher3.util.Executors.MAIN_EXECUTOR import com.android.launcher3.util.IntArray as LIntArray +import com.android.launcher3.util.IntArray import com.android.launcher3.util.IntSet as LIntSet +import com.android.launcher3.util.IntSet +import com.android.launcher3.util.IntSparseArrayMap import com.android.launcher3.util.ItemInfoMatcher import com.android.launcher3.util.PackageUserKey import com.android.launcher3.util.Preconditions @@ -28,19 +42,19 @@ import com.android.launcher3.util.TraceHelper import com.android.launcher3.util.ViewOnDrawExecutor import com.android.launcher3.widget.PendingAddWidgetInfo import com.android.launcher3.widget.model.WidgetsListBaseEntry +import java.util.concurrent.Executor +import java.util.concurrent.atomic.AtomicReference import java.util.function.Predicate -private const val TAG = "ModelCallbacks" - class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { - var synchronouslyBoundPages = LIntSet() - var pagesToBindSynchronously = LIntSet() + private var activeBindTask = AtomicReference(CancellationSignal()) + var synchronouslyBoundPages = IntSet() + var pagesToBindSynchronously = IntSet() var stringCache: StringCache? = null var pendingExecutor: ViewOnDrawExecutor? = null - var workspaceLoading = true /** @@ -48,7 +62,7 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { * * Implementation of the method from LauncherModel.Callbacks. */ - override fun startBinding() { + private fun startBinding() { TraceHelper.INSTANCE.beginSection("startBinding") // Floating panels (except the full widget sheet) are associated with individual icons. If // we are starting a fresh bind, close all such panels as all the icons are about @@ -74,11 +88,11 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { " and is setting to ${launcher.deviceProfile.isVerticalBarLayout}", ) launcher.hotseat?.resetLayout(launcher.deviceProfile.isVerticalBarLayout) + launcher.startBinding() TraceHelper.INSTANCE.endSection() } - @TargetApi(Build.VERSION_CODES.S) - override fun onInitialBindComplete( + private fun onInitialBindComplete( boundPages: LIntSet, pendingTasks: RunnableList, onCompleteSignal: RunnableList, @@ -123,7 +137,7 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { * * Implementation of the method from LauncherModel.Callbacks. */ - override fun finishBindingItems(pagesBoundFirst: LIntSet?) { + private fun finishBindingItems(pagesBoundFirst: IntSet?) { TraceHelper.INSTANCE.beginSection("finishBindingItems") val deviceProfile = launcher.deviceProfile launcher.workspace.restoreInstanceStateForRemainingPages() @@ -137,7 +151,7 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { // Since we are just resetting the current page without user interaction, // override the previous page so we don't log the page switch. launcher.workspace.setCurrentPage(currentPage, currentPage /* overridePrevPage */) - pagesToBindSynchronously = LIntSet() + pagesToBindSynchronously = IntSet() // Cache one page worth of icons launcher.viewCache.setCacheSize( @@ -151,14 +165,14 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { /*pause=*/ false, deviceProfile.isTwoPanels, ) - TestEventEmitter.sendEvent(TestEvent.WORKSPACE_FINISH_LOADING) + launcher.finishBindingItems(pagesBoundFirst) } /** * Clear any pending bind callbacks. This is called when is loader is planning to perform a full * rebind from scratch. */ - override fun clearPendingBinds() { + fun clearPendingBinds() { pendingExecutor?.cancel() ?: return pendingExecutor = null @@ -213,7 +227,7 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { .filter { workspace.isContainerSupported(it.container) } .let { if (it.isNotEmpty()) { - launcher.bindItems(it, false) + bindItems(it, false) } } workspace.stripEmptyScreens() @@ -236,7 +250,7 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { } /** Returns the ids of the workspaces to bind. */ - override fun getPagesToBindSynchronously(orderedScreenIds: LIntArray): LIntSet { + private fun getPagesToBindSynchronously(orderedScreenIds: IntArray): IntSet { // If workspace binding is still in progress, getCurrentPageScreenIds won't be // accurate, and we should use mSynchronouslyBoundPages that's set during initial binding. val visibleIds = @@ -246,7 +260,7 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { else -> synchronouslyBoundPages } // Launcher IntArray has the same name as Kotlin IntArray - val result = LIntSet() + val result = IntSet() if (visibleIds.isEmpty) { return result } @@ -301,7 +315,7 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { ) } - override fun bindScreens(orderedScreenIds: LIntArray) { + private fun bindScreens(orderedScreenIds: LIntArray) { launcher.workspace.pageIndicator.setPauseScroll( /*pause=*/ true, launcher.deviceProfile.isTwoPanels, @@ -325,26 +339,38 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { launcher.workspace.unlockWallpaperFromDefaultPageOnNextLayout() } - override fun bindAppsAdded( - newScreens: LIntArray?, - addNotAnimated: java.util.ArrayList?, - addAnimated: java.util.ArrayList?, - ) { - // Add the new screens - if (newScreens != null) { - // newScreens can contain an empty right panel that is already bound, but not known - // by BgDataModel. - newScreens.removeAllValues(launcher.workspace.mScreenOrder) - bindAddScreens(newScreens) + override fun bindItemsAdded(items: List) { + val newScreens = LIntSet() + val nonAnimatedItems = mutableListOf() + val animatedItems = mutableListOf() + val folderItems = mutableListOf() + val lastScreen = + items + .maxByOrNull { if (it.container == CONTAINER_DESKTOP) it.screenId else 0 } + ?.screenId ?: 0 + + items.forEach { + when (it.container) { + CONTAINER_HOTSEAT -> nonAnimatedItems.add(it) + CONTAINER_DESKTOP -> { + newScreens.add(it.screenId) + if (it.screenId == lastScreen) animatedItems.add(it) + else nonAnimatedItems.add(it) + } + else -> folderItems.add(it) + } } + launcher.workspace.mScreenOrder.forEach { newScreens.remove(it) } + if (!newScreens.isEmpty) bindAddScreens(newScreens.array) + // We add the items without animation on non-visible pages, and with // animations on the new page (which we will try and snap to). - if (!addNotAnimated.isNullOrEmpty()) { - launcher.bindItems(addNotAnimated, false) + if (nonAnimatedItems.isNotEmpty()) { + bindItems(nonAnimatedItems, false) } - if (!addAnimated.isNullOrEmpty()) { - launcher.bindItems(addAnimated, true) + if (animatedItems.isNotEmpty()) { + bindItems(animatedItems, true) } // Remove the extra empty screen @@ -398,5 +424,188 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { launcher.appsView.updateWorkUI() } - override fun getItemInflater() = launcher.itemInflater + /** Bind the items start-end from the list. */ + @VisibleForTesting + fun bindItems(items: List, forceAnimateIcons: Boolean) { + launcher.bindInflatedItems( + items.map { Pair.create(it, launcher.itemInflater.inflateItem(it)) }, + if (forceAnimateIcons) AnimatorSet() else null, + ) + } + + @AnyThread + override fun bindCompleteModelAsync( + itemIdMap: IntSparseArrayMap, + extraItems: List, + stringCache: StringCache, + isBindingSync: Boolean, + ) { + val taskTracker = CancellationSignal() + activeBindTask.getAndSet(taskTracker).cancel() + + val inflater = launcher.itemInflater + + fun executeCallbacksTask(executor: Executor = MAIN_EXECUTOR, task: () -> Unit) { + executor.execute { + if (taskTracker.isCanceled) { + Log.d(TAG, "Too many consecutive reloads, skipping obsolete data-bind") + } else { + task.invoke() + } + } + } + + // Tries to inflate the items asynchronously and bind. Returns true on success or false if + // async-binding is not supported in this case + fun inflateAsyncAndBind(items: List, executor: Executor) { + if (taskTracker.isCanceled) { + Log.d(TAG, "Too many consecutive reloads, skipping obsolete view inflation") + return + } + + val bindItems = items.map { Pair.create(it, inflater.inflateItem(it, null)) } + if (bindItems.isNotEmpty()) + executeCallbacksTask(executor) { launcher.bindInflatedItems(bindItems, null) } + } + + fun bindItemsInChunks(items: List, chuckSize: Int, executor: Executor) { + // Bind the workspace items + val itemCount = items.size + var i = 0 + while (i < itemCount) { + val start = i + val end = (start + chuckSize).coerceAtMost(itemCount) + executeCallbacksTask(executor) { bindItems(items.subList(start, end), false) } + i = end + } + } + + MAIN_EXECUTOR.execute { clearPendingBinds() } + + val orderedScreenIds = + IntSet() + .apply { + itemIdMap.forEach { if (it.container == CONTAINER_DESKTOP) add(it.screenId) } + if ((QSB_ON_FIRST_SCREEN && !SHOULD_SHOW_FIRST_PAGE_WIDGET) || isEmpty) + add(Workspace.FIRST_SCREEN_ID) + } + .array + val currentScreenIds = getPagesToBindSynchronously(orderedScreenIds) + + fun setupPendingBind(pendingExecutor: Executor) { + executeCallbacksTask(pendingExecutor) { launcher.bindStringCache(stringCache) } + executeCallbacksTask(pendingExecutor) { finishBindingItems(currentScreenIds) } + pendingExecutor.execute { + ItemInstallQueue.INSTANCE[launcher].resumeModelPush(FLAG_LOADER_RUNNING) + } + } + + // Separate the items that are on the current screen, and all the other remaining items + val currentWorkspaceItems = ArrayList() + val otherWorkspaceItems = ArrayList() + val currentAppWidgets = ArrayList() + val otherAppWidgets = ArrayList() + + val currentScreenCheck = currentScreenContentFilter(currentScreenIds) + itemIdMap.forEach { item: ItemInfo -> + if (currentScreenCheck.test(item)) { + (if (WIDGET_FILTER.test(item)) currentAppWidgets else currentWorkspaceItems).add( + item + ) + } else if (item.container == CONTAINER_DESKTOP) { + (if (WIDGET_FILTER.test(item)) otherAppWidgets else otherWorkspaceItems).add(item) + } + } + + sortWorkspaceItemsSpatially(currentWorkspaceItems) + sortWorkspaceItemsSpatially(otherWorkspaceItems) + + // Tell the workspace that we're about to start binding items + executeCallbacksTask { + clearPendingBinds() + startBinding() + } + + // Bind workspace screens + executeCallbacksTask { bindScreens(orderedScreenIds) } + + // Load items on the current page. + if (Flags.enableWorkspaceInflation()) { + inflateAsyncAndBind(currentWorkspaceItems, MAIN_EXECUTOR) + inflateAsyncAndBind(currentAppWidgets, MAIN_EXECUTOR) + } else { + bindItemsInChunks(currentWorkspaceItems, ITEMS_CHUNK, MAIN_EXECUTOR) + bindItemsInChunks(currentAppWidgets, 1, MAIN_EXECUTOR) + } + extraItems.forEach { + it?.let { executeCallbacksTask { launcher.bindExtraContainerItems(it) } } + } + + val pendingTasks = RunnableList() + val pendingExecutor = Executor { pendingTasks.add(it) } + + val onCompleteSignal = RunnableList() + onCompleteSignal.add { Log.d(TAG, "Calling onCompleteSignal") } + + if (Flags.enableWorkspaceInflation()) { + Log.d(TAG, "Starting async inflation") + Executors.MODEL_EXECUTOR.execute { + inflateAsyncAndBind(otherWorkspaceItems, pendingExecutor) + inflateAsyncAndBind(otherAppWidgets, pendingExecutor) + setupPendingBind(pendingExecutor) + + // Wait for the async inflation to complete and then notify the completion + // signal on UI thread. + MAIN_EXECUTOR.execute { onCompleteSignal.executeAllAndDestroy() } + } + } else { + Log.d(TAG, "Starting sync inflation") + bindItemsInChunks(otherWorkspaceItems, ITEMS_CHUNK, pendingExecutor) + bindItemsInChunks(otherAppWidgets, 1, pendingExecutor) + setupPendingBind(pendingExecutor) + onCompleteSignal.executeAllAndDestroy() + } + + val workspaceItemCount = itemIdMap.size() + executeCallbacksTask { + onInitialBindComplete( + currentScreenIds, + pendingTasks, + onCompleteSignal, + workspaceItemCount, + isBindingSync, + ) + } + } + + /** + * Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to right) + */ + private fun sortWorkspaceItemsSpatially(workspaceItems: MutableList) { + val idp = launcher.deviceProfile.inv + val screenCols = idp.numColumns + val screenCellCount = idp.numColumns * idp.numRows + workspaceItems.sortWith { lhs: ItemInfo, rhs: ItemInfo -> + when { + // Between containers, order by hotseat, desktop + lhs.container != rhs.container -> lhs.container.compareTo(rhs.container) + + // Within workspace, order by their spatial position in that container + lhs.container == CONTAINER_DESKTOP -> + compareValuesBy(lhs, rhs) { + it.screenId * screenCellCount + it.cellY * screenCols + it.cellX + } + + // We currently use the screen id as the rank + lhs.container == CONTAINER_HOTSEAT -> lhs.screenId.compareTo(rhs.screenId) + + else -> 0 + } + } + } + + companion object { + private const val TAG = "ModelCallbacks" + private const val ITEMS_CHUNK: Int = 6 // batch size for the workspace icons + } } diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index e16a296947..fd596bd62f 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -536,7 +536,7 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate { - mContext.bindItems(Collections.singletonList(item), true); + mContext.inflateAndBindItemWithAnimation(item); announceConfirmation(R.string.item_moved); }); return true; diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java index c91e78341f..f71d5055f7 100644 --- a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java @@ -28,7 +28,6 @@ import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.shortcuts.DeepShortcutView; -import java.util.Collections; import java.util.List; /** @@ -64,7 +63,7 @@ public class ShortcutMenuAccessibilityDelegate extends LauncherAccessibilityDele mContext.getModelWriter().addItemToDatabase(info, LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, coordinates[0], coordinates[1]); - mContext.bindItems(Collections.singletonList(info), true); + mContext.inflateAndBindItemWithAnimation(info); AbstractFloatingView.closeAllOpenViews(mContext); })); return true; diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java index dfba4bb192..abcc0cb031 100644 --- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java +++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java @@ -28,12 +28,10 @@ import android.util.Pair; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.launcher3.LauncherModel.CallbackTask; import com.android.launcher3.LauncherModel.ModelUpdateTask; import com.android.launcher3.LauncherSettings; import com.android.launcher3.icons.IconCache; import com.android.launcher3.logging.FileLog; -import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.CollectionInfo; import com.android.launcher3.model.data.ItemInfo; @@ -207,26 +205,7 @@ public class AddWorkspaceItemsTask implements ModelUpdateTask { } if (!addedItemsFinal.isEmpty()) { - taskController.scheduleCallbackTask(new CallbackTask() { - @Override - public void execute(@NonNull Callbacks callbacks) { - final ArrayList addAnimated = new ArrayList<>(); - final ArrayList addNotAnimated = new ArrayList<>(); - if (!addedItemsFinal.isEmpty()) { - ItemInfo info = addedItemsFinal.get(addedItemsFinal.size() - 1); - int lastScreenId = info.screenId; - for (ItemInfo i : addedItemsFinal) { - if (i.screenId == lastScreenId) { - addAnimated.add(i); - } else { - addNotAnimated.add(i); - } - } - } - callbacks.bindAppsAdded(addedWorkspaceScreensFinal, - addNotAnimated, addAnimated); - } - }); + taskController.scheduleCallbackTask(cb -> cb.bindItemsAdded(addedItemsFinal)); } } diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java index 5b95e370ae..dc4b6f02da 100644 --- a/src/com/android/launcher3/model/BaseLauncherBinder.java +++ b/src/com/android/launcher3/model/BaseLauncherBinder.java @@ -17,41 +17,24 @@ package com.android.launcher3.model; import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED; -import static com.android.launcher3.Flags.enableWorkspaceInflation; -import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP; -import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING; -import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER; -import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.content.Context; import android.os.Trace; import android.util.Log; -import android.util.Pair; -import android.view.View; -import androidx.annotation.NonNull; - -import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherModel.CallbackTask; -import com.android.launcher3.LauncherSettings; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dagger.ApplicationContext; import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.BgDataModel.FixedContainerItems; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.util.ComponentKey; -import com.android.launcher3.util.IntArray; -import com.android.launcher3.util.IntSet; import com.android.launcher3.util.IntSparseArrayMap; -import com.android.launcher3.util.ItemInflater; import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.util.LooperIdleLock; import com.android.launcher3.util.PackageUserKey; -import com.android.launcher3.util.RunnableList; import com.android.launcher3.widget.model.WidgetsListBaseEntriesBuilder; import com.android.launcher3.widget.model.WidgetsListBaseEntry; @@ -61,13 +44,10 @@ import dagger.assisted.AssistedInject; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.concurrent.Executor; -import java.util.function.Predicate; import java.util.stream.Collectors; /** @@ -76,12 +56,10 @@ import java.util.stream.Collectors; public class BaseLauncherBinder { protected static final String TAG = "LauncherBinder"; - private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons protected final LooperExecutor mUiExecutor; private final Context mContext; - private final InvariantDeviceProfile mIDP; private final LauncherModel mModel; protected final BgDataModel mBgDataModel; private final AllAppsList mBgAllAppsList; @@ -93,14 +71,12 @@ public class BaseLauncherBinder { @AssistedInject public BaseLauncherBinder( @ApplicationContext Context context, - InvariantDeviceProfile idp, LauncherModel model, BgDataModel dataModel, AllAppsList allAppsList, @Assisted Callbacks[] callbacksList) { mUiExecutor = MAIN_EXECUTOR; mContext = context; - mIDP = idp; mModel = model; mBgDataModel = dataModel; mBgAllAppsList = allAppsList; @@ -115,24 +91,22 @@ public class BaseLauncherBinder { try { // Save a copy of all the bg-thread collections IntSparseArrayMap itemsIdMap; - final IntArray orderedScreenIds = new IntArray(); ArrayList extraItems = new ArrayList<>(); - final int workspaceItemCount; + StringCache stringCache; + synchronized (mBgDataModel) { itemsIdMap = mBgDataModel.itemsIdMap.clone(); - orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens()); mBgDataModel.extraItems.forEach(extraItems::add); if (incrementBindId) { mBgDataModel.lastBindId++; mBgDataModel.lastLoadId = mModel.getLastLoadId(); } mMyBindingId = mBgDataModel.lastBindId; - workspaceItemCount = mBgDataModel.itemsIdMap.size(); + stringCache = mBgDataModel.stringCache.clone(); } for (Callbacks cb : mCallbacksList) { - new UnifiedWorkspaceBinder(cb, itemsIdMap, extraItems, orderedScreenIds) - .bind(isBindSync, workspaceItemCount); + cb.bindCompleteModelAsync(itemsIdMap, extraItems, stringCache, isBindSync); } } finally { Trace.endSection(); @@ -190,41 +164,6 @@ public class BaseLauncherBinder { executeCallbacksTask(c -> c.bindSmartspaceWidget(), mUiExecutor); } - /** - * Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to right) - */ - protected void sortWorkspaceItemsSpatially(ArrayList workspaceItems) { - final int screenCols = mIDP.numColumns; - final int screenCellCount = mIDP.numColumns * mIDP.numRows; - Collections.sort(workspaceItems, (lhs, rhs) -> { - if (lhs.container == rhs.container) { - // Within containers, order by their spatial position in that container - switch (lhs.container) { - case LauncherSettings.Favorites.CONTAINER_DESKTOP: { - int lr = (lhs.screenId * screenCellCount + lhs.cellY * screenCols - + lhs.cellX); - int rr = (rhs.screenId * screenCellCount + +rhs.cellY * screenCols - + rhs.cellX); - return Integer.compare(lr, rr); - } - case LauncherSettings.Favorites.CONTAINER_HOTSEAT: { - // We currently use the screen id as the rank - return Integer.compare(lhs.screenId, rhs.screenId); - } - default: - if (FeatureFlags.IS_STUDIO_BUILD) { - throw new RuntimeException( - "Unexpected container type when sorting workspace items."); - } - return 0; - } - } else { - // Between containers, order by hotseat, desktop - return Integer.compare(lhs.container, rhs.container); - } - }); - } - protected void executeCallbacksTask(CallbackTask task, Executor executor) { executor.execute(() -> { if (mMyBindingId != mBgDataModel.lastBindId) { @@ -248,152 +187,6 @@ public class BaseLauncherBinder { } return idleLock; } - - private class UnifiedWorkspaceBinder { - - private final Callbacks mCallbacks; - - private final IntSparseArrayMap mItemIdMap; - private final IntArray mOrderedScreenIds; - private final ArrayList mExtraItems; - - UnifiedWorkspaceBinder( - Callbacks callbacks, - IntSparseArrayMap itemIdMap, - ArrayList extraItems, - IntArray orderedScreenIds) { - mCallbacks = callbacks; - mItemIdMap = itemIdMap; - mExtraItems = extraItems; - mOrderedScreenIds = orderedScreenIds; - } - - private void bind(boolean isBindSync, int workspaceItemCount) { - final IntSet currentScreenIds = - mCallbacks.getPagesToBindSynchronously(mOrderedScreenIds); - Objects.requireNonNull(currentScreenIds, "Null screen ids provided by " + mCallbacks); - - // Separate the items that are on the current screen, and all the other remaining items - ArrayList currentWorkspaceItems = new ArrayList<>(); - ArrayList otherWorkspaceItems = new ArrayList<>(); - ArrayList currentAppWidgets = new ArrayList<>(); - ArrayList otherAppWidgets = new ArrayList<>(); - - Predicate currentScreenCheck = currentScreenContentFilter(currentScreenIds); - mItemIdMap.forEach(item -> { - if (currentScreenCheck.test(item)) { - (WIDGET_FILTER.test(item) ? currentAppWidgets : currentWorkspaceItems) - .add(item); - } else if (item.container == CONTAINER_DESKTOP) { - (WIDGET_FILTER.test(item) ? otherAppWidgets : otherWorkspaceItems).add(item); - } - }); - sortWorkspaceItemsSpatially(currentWorkspaceItems); - sortWorkspaceItemsSpatially(otherWorkspaceItems); - - // Tell the workspace that we're about to start binding items - executeCallbacksTask(c -> { - c.clearPendingBinds(); - c.startBinding(); - }, mUiExecutor); - - // Bind workspace screens - executeCallbacksTask(c -> c.bindScreens(mOrderedScreenIds), mUiExecutor); - - ItemInflater inflater = mCallbacks.getItemInflater(); - - // Load items on the current page. - if (enableWorkspaceInflation() && inflater != null) { - inflateAsyncAndBind(currentWorkspaceItems, inflater, mUiExecutor); - inflateAsyncAndBind(currentAppWidgets, inflater, mUiExecutor); - } else { - bindItemsInChunks(currentWorkspaceItems, ITEMS_CHUNK, mUiExecutor); - bindItemsInChunks(currentAppWidgets, 1, mUiExecutor); - } - mExtraItems.forEach(item -> - executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor)); - - RunnableList pendingTasks = new RunnableList(); - Executor pendingExecutor = pendingTasks::add; - - RunnableList onCompleteSignal = new RunnableList(); - onCompleteSignal.add(() -> Log.d(TAG, "Calling onCompleteSignal")); - - if (enableWorkspaceInflation() && inflater != null) { - Log.d(TAG, "Starting async inflation"); - MODEL_EXECUTOR.execute(() -> { - inflateAsyncAndBind(otherWorkspaceItems, inflater, pendingExecutor); - inflateAsyncAndBind(otherAppWidgets, inflater, pendingExecutor); - setupPendingBind(currentScreenIds, pendingExecutor); - - // Wait for the async inflation to complete and then notify the completion - // signal on UI thread. - MAIN_EXECUTOR.execute(onCompleteSignal::executeAllAndDestroy); - }); - } else { - Log.d(TAG, "Starting sync inflation"); - bindItemsInChunks(otherWorkspaceItems, ITEMS_CHUNK, pendingExecutor); - bindItemsInChunks(otherAppWidgets, 1, pendingExecutor); - setupPendingBind(currentScreenIds, pendingExecutor); - onCompleteSignal.executeAllAndDestroy(); - } - - executeCallbacksTask(c -> c.onInitialBindComplete(currentScreenIds, pendingTasks, - onCompleteSignal, workspaceItemCount, isBindSync), mUiExecutor); - } - - private void setupPendingBind( - IntSet currentScreenIds, - Executor pendingExecutor) { - StringCache cacheClone = mBgDataModel.stringCache.clone(); - executeCallbacksTask(c -> c.bindStringCache(cacheClone), pendingExecutor); - - executeCallbacksTask(c -> c.finishBindingItems(currentScreenIds), pendingExecutor); - pendingExecutor.execute(() -> ItemInstallQueue.INSTANCE.get(mContext) - .resumeModelPush(FLAG_LOADER_RUNNING)); - } - - /** - * Tries to inflate the items asynchronously and bind. Returns true on success or false if - * async-binding is not supported in this case. - */ - private void inflateAsyncAndBind( - List items, @NonNull ItemInflater inflater, Executor executor) { - if (mMyBindingId != mBgDataModel.lastBindId) { - Log.d(TAG, "Too many consecutive reloads, skipping obsolete view inflation"); - return; - } - - List> bindItems = items.stream() - .map(i -> Pair.create(i, inflater.inflateItem(i, null))) - .collect(Collectors.toList()); - executeCallbacksTask(c -> c.bindInflatedItems(bindItems), executor); - } - - private void bindItemsInChunks( - List workspaceItems, int chunkCount, Executor executor) { - // Bind the workspace items - int count = workspaceItems.size(); - for (int i = 0; i < count; i += chunkCount) { - final int start = i; - final int chunkSize = (i + chunkCount <= count) ? chunkCount : (count - i); - executeCallbacksTask( - c -> c.bindItems(workspaceItems.subList(start, start + chunkSize), false), - executor); - } - } - - protected void executeCallbacksTask(CallbackTask task, Executor executor) { - executor.execute(() -> { - if (mMyBindingId != mBgDataModel.lastBindId) { - Log.d(TAG, "Too many consecutive reloads, skipping obsolete data-bind"); - return; - } - task.execute(mCallbacks); - }); - } - } - @AssistedFactory public interface BaseLauncherBinderFactory { BaseLauncherBinder createBinder(Callbacks[] callbacks); diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java index 9189eb8e3a..5e0f8a074f 100644 --- a/src/com/android/launcher3/model/BgDataModel.java +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -25,6 +25,7 @@ import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SH import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER; import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET; import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.mapping; @@ -35,9 +36,8 @@ import android.content.pm.ShortcutInfo; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; -import android.util.Pair; -import android.view.View; +import androidx.annotation.AnyThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -66,9 +66,7 @@ import com.android.launcher3.util.DaggerSingletonTracker; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.IntSparseArrayMap; -import com.android.launcher3.util.ItemInflater; import com.android.launcher3.util.PackageUserKey; -import com.android.launcher3.util.RunnableList; import com.android.launcher3.widget.model.WidgetsListBaseEntry; import java.io.PrintWriter; @@ -439,41 +437,31 @@ public class BgDataModel implements LauncherDumpable { int FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED = 1 << 4; /** - * Returns an IntSet of page ids to bind first, synchronously if possible - * or an empty IntSet - * @param orderedScreenIds All the page ids to be bound + * Does a complete model rebind. The callback can be called on any thread and it is up to + * the client to move the executor to appropriate thread */ - @NonNull - default IntSet getPagesToBindSynchronously(IntArray orderedScreenIds) { - return new IntSet(); + @AnyThread + default void bindCompleteModelAsync(IntSparseArrayMap itemIdMap, + List extraItems, StringCache stringCache, + boolean isBindingSync) { + MAIN_EXECUTOR.execute( + () -> bindCompleteModel(itemIdMap, extraItems, stringCache, isBindingSync)); } - default void clearPendingBinds() { } - default void startBinding() { } + default void bindCompleteModel(IntSparseArrayMap itemIdMap, + List extraItems, StringCache stringCache, + boolean isBindingSync) { } - @Nullable - default ItemInflater getItemInflater() { - return null; - } - - default void bindItems(@NonNull List shortcuts, boolean forceAnimateIcons) { } - /** Alternate method to bind preinflated views */ - default void bindInflatedItems(@NonNull List> items) { } - - default void bindScreens(IntArray orderedScreenIds) { } - default void finishBindingItems(IntSet pagesBoundFirst) { } - default void bindAppsAdded(IntArray newScreens, - ArrayList addNotAnimated, ArrayList addAnimated) { } + default void bindItemsAdded(@NonNull List items) { } + /** Called when a runtime property of the ItemInfo is updated due to some system event */ + default void bindItemsUpdated(Set updates) { } + default void bindWorkspaceComponentsRemoved(Predicate matcher) { } /** * Binds updated incremental download progress */ default void bindIncrementalDownloadProgressUpdated(AppInfo app) { } - /** Called when a runtime property of the ItemInfo is updated due to some system event */ - default void bindItemsUpdated(Set updates) { } - default void bindWorkspaceComponentsRemoved(Predicate matcher) { } - /** * Binds the app widgets to the providers that share widgets with the UI. */ @@ -481,14 +469,6 @@ public class BgDataModel implements LauncherDumpable { default void bindSmartspaceWidget() { } - /** Called when workspace has been bound. */ - default void onInitialBindComplete(@NonNull IntSet boundPages, - @NonNull RunnableList pendingTasks, - @NonNull RunnableList onCompleteSignal, - int workspaceItemCount, boolean isBindSync) { - pendingTasks.executeAllAndDestroy(); - } - default void bindDeepShortcutMap(HashMap deepShortcutMap) { } /** diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java index 01e1730f47..d591a75383 100644 --- a/src/com/android/launcher3/model/ModelWriter.java +++ b/src/com/android/launcher3/model/ModelWriter.java @@ -242,7 +242,7 @@ public class ModelWriter { updateItemInfoProps(item, container, screenId, cellX, cellY); item.id = mModel.getModelDbController().generateNewItemId(); - notifyOtherCallbacks(c -> c.bindItems(Collections.singletonList(item), false)); + notifyOtherCallbacks(c -> c.bindItemsAdded(Collections.singletonList(item))); ModelVerifier verifier = new ModelVerifier(); final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java index 54efccdac3..8c0f8378e3 100644 --- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java +++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java @@ -234,11 +234,6 @@ public class SecondaryDisplayLauncher extends BaseActivity animator.start(); } - @Override - public void startBinding() { - mDragController.cancelDrag(); - } - @Override public void bindDeepShortcutMap(HashMap deepShortcutMap) { mPopupDataProvider.setDeepShortcutMap(deepShortcutMap); diff --git a/tests/multivalentTests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt index 6d366db0e6..d19048d87e 100644 --- a/tests/multivalentTests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt @@ -61,8 +61,7 @@ class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() { val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd) assertThat(addedItems.size).isEqualTo(1) - assertThat(addedItems.first().itemInfo.screenId).isEqualTo(1) - assertThat(addedItems.first().isAnimated).isTrue() + assertThat(addedItems.first().screenId).isEqualTo(1) verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1) } @@ -75,8 +74,7 @@ class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() { val addedItems = testAddItems(nonEmptyScreenIds, *itemsToAdd) assertThat(addedItems.size).isEqualTo(1) - assertThat(addedItems.first().itemInfo.screenId).isEqualTo(1) - assertThat(addedItems.first().isAnimated).isTrue() + assertThat(addedItems.first().screenId).isEqualTo(1) verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1) } @@ -102,8 +100,7 @@ class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() { val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd) assertThat(addedItems.size).isEqualTo(1) - assertThat(addedItems.first().itemInfo.screenId).isEqualTo(2) - assertThat(addedItems.first().isAnimated).isTrue() + assertThat(addedItems.first().screenId).isEqualTo(2) verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1) } @@ -120,14 +117,12 @@ class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() { assertThat(addedItems.size).isEqualTo(3) // Items that are added to the first screen should not be animated - val itemsAddedToFirstScreen = addedItems.filter { it.itemInfo.screenId == 1 } + val itemsAddedToFirstScreen = addedItems.filter { it.screenId == 1 } assertThat(itemsAddedToFirstScreen.size).isEqualTo(1) - assertThat(itemsAddedToFirstScreen.first().isAnimated).isFalse() // Items that are added to the second screen should be animated - val itemsAddedToSecondScreen = addedItems.filter { it.itemInfo.screenId == 2 } + val itemsAddedToSecondScreen = addedItems.filter { it.screenId == 2 } assertThat(itemsAddedToSecondScreen.size).isEqualTo(2) - itemsAddedToSecondScreen.forEach { assertThat(it.isAnimated).isTrue() } verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 3) } @@ -160,11 +155,11 @@ class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() { private fun testAddItems( nonEmptyScreenIds: List, vararg itemsToAdd: WorkspaceItemInfo, - ): List { + ): List { setupWorkspaces(nonEmptyScreenIds) val task = newTask(*itemsToAdd) - val addedItems = mutableListOf() + val addedItems = mutableListOf() runOnExecutorSync(Executors.MODEL_EXECUTOR) { mDataModelCallbacks.addedItems.clear() @@ -187,18 +182,11 @@ class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() { .let { AddWorkspaceItemsTask(it, mWorkspaceItemSpaceFinder) } } -private data class AddedItem(val itemInfo: ItemInfo, val isAnimated: Boolean) - private class MyCallbacks : BgDataModel.Callbacks { - val addedItems = mutableListOf() + val addedItems = mutableListOf() - override fun bindAppsAdded( - newScreens: IntArray?, - addNotAnimated: ArrayList, - addAnimated: ArrayList, - ) { - addedItems.addAll(addAnimated.map { AddedItem(it, true) }) - addedItems.addAll(addNotAnimated.map { AddedItem(it, false) }) + override fun bindItemsAdded(items: List) { + addedItems.addAll(items) } } diff --git a/tests/multivalentTests/src/com/android/launcher3/model/AsyncBindingTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/AsyncBindingTest.kt index 9f55f392c3..61d96bab00 100644 --- a/tests/multivalentTests/src/com/android/launcher3/model/AsyncBindingTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/model/AsyncBindingTest.kt @@ -17,45 +17,52 @@ package com.android.launcher3.model import android.os.Looper +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule -import android.util.Pair import android.util.SparseArray import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.launcher3.Flags +import com.android.launcher3.InvariantDeviceProfile +import com.android.launcher3.Launcher import com.android.launcher3.LauncherAppState import com.android.launcher3.LauncherModel -import com.android.launcher3.model.BgDataModel.Callbacks +import com.android.launcher3.ModelCallbacks import com.android.launcher3.model.data.ItemInfo +import com.android.launcher3.pageindicators.PageIndicatorDots import com.android.launcher3.util.Executors.MAIN_EXECUTOR import com.android.launcher3.util.Executors.MODEL_EXECUTOR -import com.android.launcher3.util.IntArray import com.android.launcher3.util.IntSet import com.android.launcher3.util.ItemInflater import com.android.launcher3.util.LauncherLayoutBuilder import com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE import com.android.launcher3.util.ModelTestExtensions.loadModelSync -import com.android.launcher3.util.RunnableList import com.android.launcher3.util.SandboxApplication +import com.android.launcher3.util.TestUtil.runOnExecutorSync import com.android.launcher3.util.rule.LayoutProviderRule import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Answers import org.mockito.Mock import org.mockito.MockitoAnnotations -import org.mockito.Spy import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.argThat import org.mockito.kotlin.atLeastOnce +import org.mockito.kotlin.clearInvocations import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn import org.mockito.kotlin.isNull import org.mockito.kotlin.never -import org.mockito.kotlin.reset +import org.mockito.kotlin.spy import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -69,8 +76,13 @@ class AsyncBindingTest { @get:Rule val context = SandboxApplication().withModelDependency() @get:Rule val layoutProvider = LayoutProviderRule(context) - @Spy private var callbacks = MyCallbacks() @Mock private lateinit var itemInflater: ItemInflater<*> + // PageIndicatorDots need to be mocked separately as Workspace uses generics and doesn't define + // the actual class of PageIndicator being used + @Mock private lateinit var pageIndicatorDots: PageIndicatorDots + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var launcher: Launcher + + private lateinit var callbacks: ModelCallbacks private val inflationLooper = SparseArray() @@ -79,7 +91,6 @@ class AsyncBindingTest { @Before fun setUp() { - setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WORKSPACE_INFLATION) MockitoAnnotations.initMocks(this) doAnswer { i -> @@ -89,6 +100,13 @@ class AsyncBindingTest { .whenever(itemInflater) .inflateItem(any(), isNull()) + doReturn(itemInflater).whenever(launcher).itemInflater + doReturn(InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context)) + .whenever(launcher) + .deviceProfile + launcher.workspace.apply { doReturn(pageIndicatorDots).whenever(this).getPageIndicator() } + doReturn(context).whenever(launcher).applicationContext + // Set up the workspace with 3 pages of apps layoutProvider.setupDefaultLayoutProvider( LauncherLayoutBuilder() @@ -103,30 +121,29 @@ class AsyncBindingTest { .atWorkspace(0, 1, 2) .putApp(TEST_PACKAGE, TEST_PACKAGE) ) + callbacks = + spy(ModelCallbacks(launcher).apply { pagesToBindSynchronously = IntSet.wrap(0) }) } @Test + @DisableFlags(Flags.FLAG_ENABLE_WORKSPACE_INFLATION) fun test_bind_normally_without_itemInflater() { MAIN_EXECUTOR.execute { model.addCallbacksAndLoad(callbacks) } waitForLoaderAndTempMainThread() - verify(callbacks, never()).bindInflatedItems(any()) verify(callbacks, atLeastOnce()).bindItems(any(), any()) } @Test + @EnableFlags(Flags.FLAG_ENABLE_WORKSPACE_INFLATION) fun test_bind_inflates_item_on_background() { - callbacks.inflater = itemInflater MAIN_EXECUTOR.execute { model.addCallbacksAndLoad(callbacks) } waitForLoaderAndTempMainThread() verify(callbacks, never()).bindItems(any(), any()) - verify(callbacks, times(1)).bindInflatedItems(argThat { t -> t.size == 2 }) - - // Verify remaining items are bound using pendingTasks - reset(callbacks) - MAIN_EXECUTOR.submit(callbacks.pendingTasks!!::executeAllAndDestroy).get() - verify(callbacks, times(1)).bindInflatedItems(argThat { t -> t.size == 3 }) + // First 2 items were bound and eventually remaining items were bound + verify(launcher, times(1)).bindInflatedItems(argThat { size == 2 }, anyOrNull()) + verify(launcher, times(1)).bindInflatedItems(argThat { size == 3 }, anyOrNull()) // Verify that all items were inflated on the background thread assertEquals(5, inflationLooper.size()) @@ -134,31 +151,34 @@ class AsyncBindingTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_WORKSPACE_INFLATION) fun test_bind_sync_partially_inflates_on_background() { model.loadModelSync() assertTrue(model.isModelLoaded()) - callbacks.inflater = itemInflater - val firstPageBindIds = IntSet() + val firstPageBindIds = mutableSetOf() + runOnExecutorSync(MAIN_EXECUTOR) { + model.addCallbacksAndLoad(callbacks) + verify(callbacks, never()).bindItems(any(), any()) + verify(launcher, times(1)) + .bindInflatedItems( + argThat { + firstPageBindIds.addAll(map { it.first.id }) + size == 2 + }, + anyOrNull(), + ) - MAIN_EXECUTOR.submit { - model.addCallbacksAndLoad(callbacks) - verify(callbacks, never()).bindItems(any(), any()) - verify(callbacks, times(1)) - .bindInflatedItems( - argThat { t -> - t.forEach { firstPageBindIds.add(it.first.id) } - t.size == 2 - } - ) - - // Verify that onInitialBindComplete is called and the binding is not yet complete - assertFalse(callbacks.onCompleteSignal!!.isDestroyed) - } - .get() + // Verify that onInitialBindComplete is called and the binding is not yet complete + assertNotNull(callbacks.pendingExecutor) + clearInvocations(launcher) + } waitForLoaderAndTempMainThread() - assertTrue(callbacks.onCompleteSignal!!.isDestroyed) + + // Verify remaining 3 times are bound using pending tasks + assertNull(callbacks.pendingExecutor) + verify(launcher, times(1)).bindInflatedItems(argThat { t -> t.size == 3 }, anyOrNull()) // Verify that firstPageBindIds are loaded on the main thread and remaining // on the background thread. @@ -168,45 +188,12 @@ class AsyncBindingTest { assertEquals(MAIN_EXECUTOR.looper, inflationLooper.valueAt(i)) else assertEquals(MODEL_EXECUTOR.looper, inflationLooper.valueAt(i)) } - - MAIN_EXECUTOR.submit { - reset(callbacks) - callbacks.pendingTasks!!.executeAllAndDestroy() - // Verify remaining 3 times are bound using pending tasks - verify(callbacks, times(1)).bindInflatedItems(argThat { t -> t.size == 3 }) - } - .get() } private fun waitForLoaderAndTempMainThread() { - MAIN_EXECUTOR.submit {}.get() - MODEL_EXECUTOR.submit {}.get() - MAIN_EXECUTOR.submit {}.get() - } - - class MyCallbacks : Callbacks { - - var inflater: ItemInflater<*>? = null - var pendingTasks: RunnableList? = null - var onCompleteSignal: RunnableList? = null - - override fun bindItems(shortcuts: MutableList, forceAnimateIcons: Boolean) {} - - override fun bindInflatedItems(items: MutableList>) {} - - override fun getPagesToBindSynchronously(orderedScreenIds: IntArray?) = IntSet.wrap(0) - - override fun onInitialBindComplete( - boundPages: IntSet, - pendingTasks: RunnableList, - onCompleteSignal: RunnableList, - workspaceItemCount: Int, - isBindSync: Boolean, - ) { - this.pendingTasks = pendingTasks - this.onCompleteSignal = onCompleteSignal + repeat(5) { + runOnExecutorSync(MAIN_EXECUTOR) {} + runOnExecutorSync(MODEL_EXECUTOR) {} } - - override fun getItemInflater() = inflater } } diff --git a/tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java b/tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java index 3115899262..d64049d384 100644 --- a/tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java +++ b/tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java @@ -32,14 +32,13 @@ import androidx.test.filters.SmallTest; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; import com.android.launcher3.model.BgDataModel.Callbacks; +import com.android.launcher3.model.BgDataModel.FixedContainerItems; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.util.Executors; -import com.android.launcher3.util.IntArray; -import com.android.launcher3.util.IntSet; +import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.LauncherLayoutBuilder; import com.android.launcher3.util.PackageUserKey; -import com.android.launcher3.util.RunnableList; import com.android.launcher3.util.SandboxApplication; import com.android.launcher3.util.TestUtil; import com.android.launcher3.util.rule.LayoutProviderRule; @@ -49,7 +48,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -83,17 +81,16 @@ public class ModelMultiCallbacksTest { Executors.MAIN_EXECUTOR.execute(() -> getModel().addCallbacksAndLoad(cb1)); waitForLoaderAndTempMainThread(); - cb1.verifySynchronouslyBound(3); + cb1.verifyItemsBound(3); // Add a new callback cb1.reset(); MyCallbacks cb2 = spy(MyCallbacks.class); - cb2.mPageToBindSync = IntSet.wrap(2); Executors.MAIN_EXECUTOR.execute(() -> getModel().addCallbacksAndLoad(cb2)); waitForLoaderAndTempMainThread(); - assertFalse(cb1.bindStarted); - cb2.verifySynchronouslyBound(3); + assertNull(cb1.mItems); + cb2.verifyItemsBound(3); // Remove callbacks cb1.reset(); @@ -102,14 +99,14 @@ public class ModelMultiCallbacksTest { // No effect on callbacks when removing an callback Executors.MAIN_EXECUTOR.execute(() -> getModel().removeCallbacks(cb2)); waitForLoaderAndTempMainThread(); - assertNull(cb1.mPendingTasks); - assertNull(cb2.mPendingTasks); + assertNull(cb1.mItems); + assertNull(cb2.mItems); // Reloading only loads registered callbacks getModel().startLoader(); waitForLoaderAndTempMainThread(); - cb1.verifySynchronouslyBound(3); - assertNull(cb2.mPendingTasks); + cb1.verifyItemsBound(3); + assertNull(cb2.mItems); } @Test @@ -173,30 +170,16 @@ public class ModelMultiCallbacksTest { private abstract static class MyCallbacks implements Callbacks { - final List mItems = new ArrayList<>(); - IntSet mPageToBindSync = IntSet.wrap(0); - IntSet mPageBoundSync = new IntSet(); - RunnableList mPendingTasks; + List mItems = null; AppInfo[] mAppInfos; - boolean bindStarted; MyCallbacks() { } @Override - public void startBinding() { - bindStarted = true; - } - - @Override - public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks, - RunnableList onCompleteSignal, int workspaceItemCount, boolean isBindSync) { - mPageBoundSync = boundPages; - mPendingTasks = pendingTasks; - } - - @Override - public void bindItems(List shortcuts, boolean forceAnimateIcons) { - mItems.addAll(shortcuts); + public void bindCompleteModel(IntSparseArrayMap itemIdMap, + List extraItems, StringCache stringCache, + boolean isBindingSync) { + mItems = itemIdMap.stream().toList(); } @Override @@ -205,29 +188,13 @@ public class ModelMultiCallbacksTest { mAppInfos = apps; } - @Override - public IntSet getPagesToBindSynchronously(IntArray orderedScreenIds) { - return mPageToBindSync; - } - public void reset() { - mItems.clear(); - mPageBoundSync = new IntSet(); - mPendingTasks = null; + mItems = null; mAppInfos = null; - bindStarted = false; } - public void verifySynchronouslyBound(int totalItems) { - // Verify that the requested page is bound synchronously - assertTrue(bindStarted); - assertEquals(mPageToBindSync, mPageBoundSync); - assertEquals(mItems.size(), 1); - assertEquals(IntSet.wrap(mItems.get(0).screenId), mPageBoundSync); - assertNotNull(mPendingTasks); - - // Verify that all other pages are bound properly - mPendingTasks.executeAllAndDestroy(); + public void verifyItemsBound(int totalItems) { + assertNotNull(mItems); assertEquals(mItems.size(), totalItems); } @@ -236,9 +203,5 @@ public class ModelMultiCallbacksTest { .map(ai -> ai.getTargetComponent().getPackageName()) .collect(Collectors.toSet()); } - - public void verifyApps(String... apps) { - assertTrue(allApps().containsAll(Arrays.asList(apps))); - } } }