From 562ab4d1882cdbecf2a91a432ada5cd924b50de8 Mon Sep 17 00:00:00 2001 From: vinayjoglekar Date: Mon, 23 Sep 2024 09:52:04 +0100 Subject: [PATCH] Fixed split in Desktop windowing When desktop windowing is enable and split is selected, during second app selection apps' scroll was far off on left. There were several things needed to fix this : 1. Hide DesktopTaskView during split select mode by changing splitAlpha 2. Fix min/max scroll calculation by excluding DesktopTaskView in split select mode 3. Exclude DesktopTaskView in updateGridProperties row length calcualtion Testing: Manually needed to test multiple scenarios 1. Split focused task. 2. Split Even/Odd tasks in top and bottom rows. 3. Split tasks from home screen 4. Split and rotate screen. 5. Split from app icon chip Test: SplitSelectStateControllerTest, Manual BUG: 330342294 FIX: 330342294 Flag: com.android.launcher3.enable_large_desktop_windowing_tile Change-Id: I789873100f42896c9ed3084accb0f6970abcba0c --- .../RecentsViewStateController.java | 3 + .../quickstep/util/RecentsViewUtils.kt | 16 ++- .../quickstep/util/SplitAnimationTimings.java | 4 + .../util/SplitSelectStateController.java | 1 + .../android/quickstep/views/RecentsView.java | 52 +++++++-- .../com/android/quickstep/views/TaskView.kt | 13 ++- .../util/SplitSelectStateControllerTest.kt | 100 ++++++++++-------- 7 files changed, 129 insertions(+), 60 deletions(-) diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java index 235ec7b101..111069ffc4 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java @@ -155,6 +155,9 @@ public final class RecentsViewStateController extends 0, timings.getGridSlideSecondaryInterpolator()); + mRecentsView.handleDesktopTaskInSplitSelectState(builder, + timings.getDesktopTaskFadeInterpolator()); + if (!animate) { AnimatorSet as = builder.buildAnim(); as.start(); diff --git a/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt index aa0fd88573..68690d2f43 100644 --- a/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt +++ b/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt @@ -73,9 +73,21 @@ class RecentsViewUtils { * Returns the first TaskView that should be displayed as a large tile. * * @param taskViews List of [TaskView]s + * @param splitSelectActive current split state */ - fun getFirstLargeTaskView(taskViews: Iterable): TaskView? = - taskViews.firstOrNull { it.isLargeTile } + fun getFirstLargeTaskView( + taskViews: MutableIterable, + splitSelectActive: Boolean, + ): TaskView? = + taskViews.firstOrNull { it.isLargeTile && !(splitSelectActive && it is DesktopTaskView) } + + /** + * Returns the first TaskView that is not large + * + * @param taskViews List of [TaskView]s + */ + fun getFirstSmallTaskView(taskViews: MutableIterable): TaskView? = + taskViews.firstOrNull { !it.isLargeTile } /** Returns the last TaskView that should be displayed as a large tile. */ fun getLastLargeTaskView(taskViews: Iterable): TaskView? = diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java index b618546576..90569b49a4 100644 --- a/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java +++ b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java @@ -105,6 +105,10 @@ public interface SplitAnimationTimings { default Interpolator getGridSlidePrimaryInterpolator() { return LINEAR; } default Interpolator getGridSlideSecondaryInterpolator() { return LINEAR; } + default Interpolator getDesktopTaskFadeInterpolator() { + return LINEAR; + } + // Defaults for HomeToSplit default float getScrimFadeInStartOffset() { return 0; } default float getScrimFadeInEndOffset() { return 0; } diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java index 60c6adebbd..8f579e2371 100644 --- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java +++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java @@ -743,6 +743,7 @@ public class SplitSelectStateController { */ public void resetState() { mSplitSelectDataHolder.resetState(); + mContainer.getOverviewPanel().resetDesktopTaskFromSplitSelectState(); dispatchOnSplitSelectionExit(); mRecentsAnimationRunning = false; mLaunchingTaskView = null; diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index a075c58a00..0d2acf0dab 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -3140,7 +3140,7 @@ public abstract class RecentsView< // Horizontal grid translation for each task float[] gridTranslations = new float[taskCount]; - int focusedTaskIndex = Integer.MAX_VALUE; + int lastLargeTaskIndex = Integer.MAX_VALUE; Set largeTasksIndices = new HashSet<>(); int focusedTaskShift = 0; int largeTaskWidthAndSpacing = 0; @@ -3163,8 +3163,12 @@ public abstract class RecentsView< boolean isLargeTile = taskView.isLargeTile(); if (isLargeTile) { - topRowWidth += taskWidthAndSpacing; - bottomRowWidth += taskWidthAndSpacing; + // DesktopTaskView`s are hidden during split select state, so we shouldn't count + // them when calculating row width. + if (!(taskView instanceof DesktopTaskView && isSplitSelectionActive())) { + topRowWidth += taskWidthAndSpacing; + bottomRowWidth += taskWidthAndSpacing; + } gridTranslations[i] += focusedTaskShift; gridTranslations[i] += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing; @@ -3172,9 +3176,7 @@ public abstract class RecentsView< taskView.setGridTranslationY((mLastComputedTaskSize.height() + taskTopMargin - taskView.getLayoutParams().height) / 2f); - if (taskView.getTaskViewId() == mFocusedTaskViewId) { - focusedTaskIndex = i; - } + lastLargeTaskIndex = i; largeTasksIndices.add(i); largeTaskWidthAndSpacing = taskWidthAndSpacing; @@ -3183,8 +3185,8 @@ public abstract class RecentsView< snappedTaskRowWidth = taskWidthAndSpacing; } } else { - if (i > focusedTaskIndex) { - // For tasks after the focused task, shift by focused task's width and spacing. + if (i > lastLargeTaskIndex) { + // For tasks after the last large task, shift by large task's width and spacing. gridTranslations[i] += mIsRtl ? largeTaskWidthAndSpacing : -largeTaskWidthAndSpacing; } else { @@ -5023,6 +5025,35 @@ public abstract class RecentsView< splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTaskId); } + /** + * Animate DesktopTaskView(s) to hide in split select + */ + public void handleDesktopTaskInSplitSelectState(PendingAnimation builder, + Interpolator deskTopFadeInterPolator) { + if (enableLargeDesktopWindowingTile()) { + for (TaskView taskView : getTaskViews()) { + if (taskView instanceof DesktopTaskView) { + builder.addFloat(taskView.getSplitAlphaProperty(), + MULTI_PROPERTY_VALUE, 1f, 0f, + deskTopFadeInterPolator); + } + } + } + } + + /** + * While exiting from split mode, show all existing DesktopTaskViews. + */ + public void resetDesktopTaskFromSplitSelectState() { + if (enableLargeDesktopWindowingTile()) { + for (TaskView taskView : getTaskViews()) { + if (taskView instanceof DesktopTaskView) { + taskView.setSplitAlpha(1f); + } + } + } + } + /** * Modifies a PendingAnimation with the animations for entering split staging */ @@ -5857,11 +5888,12 @@ public abstract class RecentsView< if (mShowAsGridLastOnLayout) { // For grid Overview, it always start if a large tile (focused task or desktop task) if // they exist, otherwise it start with the first task. - TaskView firstLargeTaskView = mUtils.getFirstLargeTaskView(getTaskViews()); + TaskView firstLargeTaskView = mUtils.getFirstLargeTaskView(getTaskViews(), + isSplitSelectionActive()); if (firstLargeTaskView != null) { firstView = firstLargeTaskView; } else { - firstView = getTaskViewAt(0); + firstView = mUtils.getFirstSmallTaskView(getTaskViews()); } } else { firstView = mUtils.getFirstTaskViewInCarousel( diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt index f513a829f7..cc64dbae98 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.kt +++ b/quickstep/src/com/android/quickstep/views/TaskView.kt @@ -402,6 +402,15 @@ constructor( } get() = taskViewAlpha.get(ALPHA_INDEX_ATTACH).value + var splitAlpha + set(value) { + splitAlphaProperty.value = value + } + get() = splitAlphaProperty.value + + val splitAlphaProperty: MultiPropertyFactory.MultiProperty + get() = taskViewAlpha.get(ALPHA_INDEX_SPLIT) + protected var shouldShowScreenshot = false get() = !isRunningTask || field private set @@ -606,6 +615,7 @@ constructor( override fun onRecycle() { resetPersistentViewTransforms() attachAlpha = 1f + splitAlpha = 1f // Clear any references to the thumbnail (it will be re-read either from the cache or the // system on next bind) if (!enableRefactorTaskThumbnail()) { @@ -1687,8 +1697,9 @@ constructor( private const val ALPHA_INDEX_STABLE = 0 private const val ALPHA_INDEX_ATTACH = 1 + private const val ALPHA_INDEX_SPLIT = 2 - private const val NUM_ALPHA_CHANNELS = 2 + private const val NUM_ALPHA_CHANNELS = 3 /** The maximum amount that a task view can be scrimmed, dimmed or tinted. */ const val MAX_PAGE_SCRIM_ALPHA = 0.4f diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt index 9d0b2ab40e..cb706944d5 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt @@ -36,6 +36,7 @@ import com.android.launcher3.util.SplitConfigurationOptions import com.android.quickstep.RecentsModel import com.android.quickstep.SystemUiProxy import com.android.quickstep.util.SplitSelectStateController.SplitFromDesktopController +import com.android.quickstep.views.RecentsView import com.android.quickstep.views.RecentsViewContainer import com.android.systemui.shared.recents.model.Task import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50 @@ -50,6 +51,7 @@ import org.mockito.Mockito.any import org.mockito.Mockito.`when` import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.mock +import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import java.util.function.Consumer @@ -66,6 +68,7 @@ class SplitSelectStateControllerTest { private val recentsModel: RecentsModel = mock() private val pendingIntent: PendingIntent = mock() private val splitFromDesktopController: SplitFromDesktopController = mock() + private val recentsView: RecentsView<*, *> = mock() private lateinit var splitSelectStateController: SplitSelectStateController @@ -73,6 +76,7 @@ class SplitSelectStateControllerTest { private val nonPrimaryUserHandle = UserHandle(ActivityManager.RunningTaskInfo().userId + 10) private var taskIdCounter = 0 + private fun getUniqueId(): Int { return ++taskIdCounter } @@ -90,7 +94,7 @@ class SplitSelectStateControllerTest { statsLogManager, systemUiProxy, recentsModel, - null /*activityBackCallback*/ + null, /*activityBackCallback*/ ) } @@ -100,12 +104,12 @@ class SplitSelectStateControllerTest { val groupTask1 = generateGroupTask( ComponentName("pomegranate", "juice"), - ComponentName("pumpkin", "pie") + ComponentName("pumpkin", "pie"), ) val groupTask2 = generateGroupTask( ComponentName("hotdog", "juice"), - ComponentName("personal", "computer") + ComponentName("personal", "computer"), ) val tasks: ArrayList = ArrayList() tasks.add(groupTask1) @@ -122,7 +126,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(nonMatchingComponent), false /* findExactPairMatch */, - taskConsumer + taskConsumer, ) verify(recentsModel).getTasks(capture()) } @@ -141,12 +145,12 @@ class SplitSelectStateControllerTest { val groupTask1 = generateGroupTask( ComponentName(matchingPackage, matchingClass), - ComponentName("pomegranate", "juice") + ComponentName("pomegranate", "juice"), ) val groupTask2 = generateGroupTask( ComponentName("pumpkin", "pie"), - ComponentName("personal", "computer") + ComponentName("personal", "computer"), ) val tasks: ArrayList = ArrayList() tasks.add(groupTask1) @@ -159,12 +163,12 @@ class SplitSelectStateControllerTest { assertEquals( "ComponentName package mismatched", it[0].key.baseIntent.component?.packageName, - matchingPackage + matchingPackage, ) assertEquals( "ComponentName class mismatched", it[0].key.baseIntent.component?.className, - matchingClass + matchingClass, ) assertEquals(it[0], groupTask1.task1) } @@ -175,7 +179,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(matchingComponent), false /* findExactPairMatch */, - taskConsumer + taskConsumer, ) verify(recentsModel).getTasks(capture()) } @@ -194,12 +198,12 @@ class SplitSelectStateControllerTest { val groupTask1 = generateGroupTask( ComponentName(matchingPackage, matchingClass), - ComponentName("pomegranate", "juice") + ComponentName("pomegranate", "juice"), ) val groupTask2 = generateGroupTask( ComponentName("pumpkin", "pie"), - ComponentName("personal", "computer") + ComponentName("personal", "computer"), ) val tasks: ArrayList = ArrayList() tasks.add(groupTask1) @@ -216,7 +220,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(nonPrimaryUserComponent), false /* findExactPairMatch */, - taskConsumer + taskConsumer, ) verify(recentsModel).getTasks(capture()) } @@ -237,12 +241,12 @@ class SplitSelectStateControllerTest { ComponentName(matchingPackage, matchingClass), nonPrimaryUserHandle, ComponentName("pomegranate", "juice"), - nonPrimaryUserHandle + nonPrimaryUserHandle, ) val groupTask2 = generateGroupTask( ComponentName("pumpkin", "pie"), - ComponentName("personal", "computer") + ComponentName("personal", "computer"), ) val tasks: ArrayList = ArrayList() tasks.add(groupTask1) @@ -255,12 +259,12 @@ class SplitSelectStateControllerTest { assertEquals( "ComponentName package mismatched", it[0].key.baseIntent.component?.packageName, - matchingPackage + matchingPackage, ) assertEquals( "ComponentName class mismatched", it[0].key.baseIntent.component?.className, - matchingClass + matchingClass, ) assertEquals("userId mismatched", it[0].key.userId, nonPrimaryUserHandle.identifier) assertEquals(it[0], groupTask1.task1) @@ -272,7 +276,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(nonPrimaryUserComponent), false /* findExactPairMatch */, - taskConsumer + taskConsumer, ) verify(recentsModel).getTasks(capture()) } @@ -291,12 +295,12 @@ class SplitSelectStateControllerTest { val groupTask1 = generateGroupTask( ComponentName(matchingPackage, matchingClass), - ComponentName("pumpkin", "pie") + ComponentName("pumpkin", "pie"), ) val groupTask2 = generateGroupTask( ComponentName("pomegranate", "juice"), - ComponentName(matchingPackage, matchingClass) + ComponentName(matchingPackage, matchingClass), ) val tasks: ArrayList = ArrayList() tasks.add(groupTask2) @@ -309,12 +313,12 @@ class SplitSelectStateControllerTest { assertEquals( "ComponentName package mismatched", it[0].key.baseIntent.component?.packageName, - matchingPackage + matchingPackage, ) assertEquals( "ComponentName class mismatched", it[0].key.baseIntent.component?.className, - matchingClass + matchingClass, ) assertEquals(it[0], groupTask1.task1) } @@ -325,7 +329,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(matchingComponent), false /* findExactPairMatch */, - taskConsumer + taskConsumer, ) verify(recentsModel).getTasks(capture()) } @@ -348,7 +352,7 @@ class SplitSelectStateControllerTest { val groupTask2 = generateGroupTask( ComponentName("pomegranate", "juice"), - ComponentName(matchingPackage, matchingClass) + ComponentName(matchingPackage, matchingClass), ) val tasks: ArrayList = ArrayList() tasks.add(groupTask2) @@ -363,12 +367,12 @@ class SplitSelectStateControllerTest { assertEquals( "ComponentName package mismatched", it[1].key.baseIntent.component?.packageName, - matchingPackage + matchingPackage, ) assertEquals( "ComponentName class mismatched", it[1].key.baseIntent.component?.className, - matchingClass + matchingClass, ) assertEquals(it[1], groupTask2.task2) } @@ -379,7 +383,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(nonMatchingComponent, matchingComponent), false /* findExactPairMatch */, - taskConsumer + taskConsumer, ) verify(recentsModel).getTasks(capture()) } @@ -401,7 +405,7 @@ class SplitSelectStateControllerTest { val groupTask2 = generateGroupTask( ComponentName("pomegranate", "juice"), - ComponentName(matchingPackage, matchingClass) + ComponentName(matchingPackage, matchingClass), ) val tasks: ArrayList = ArrayList() tasks.add(groupTask2) @@ -415,12 +419,12 @@ class SplitSelectStateControllerTest { assertEquals( "ComponentName package mismatched", it[0].key.baseIntent.component?.packageName, - matchingPackage + matchingPackage, ) assertEquals( "ComponentName class mismatched", it[0].key.baseIntent.component?.className, - matchingClass + matchingClass, ) assertEquals(it[0], groupTask2.task2) assertNull("No tasks should have matched", it[1] /*task*/) @@ -432,7 +436,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(matchingComponent, matchingComponent), false /* findExactPairMatch */, - taskConsumer + taskConsumer, ) verify(recentsModel).getTasks(capture()) } @@ -452,12 +456,12 @@ class SplitSelectStateControllerTest { val groupTask1 = generateGroupTask( ComponentName(matchingPackage, matchingClass), - ComponentName("pumpkin", "pie") + ComponentName("pumpkin", "pie"), ) val groupTask2 = generateGroupTask( ComponentName("pomegranate", "juice"), - ComponentName(matchingPackage, matchingClass) + ComponentName(matchingPackage, matchingClass), ) val tasks: ArrayList = ArrayList() tasks.add(groupTask2) @@ -471,23 +475,23 @@ class SplitSelectStateControllerTest { assertEquals( "ComponentName package mismatched", it[0].key.baseIntent.component?.packageName, - matchingPackage + matchingPackage, ) assertEquals( "ComponentName class mismatched", it[0].key.baseIntent.component?.className, - matchingClass + matchingClass, ) assertEquals(it[0], groupTask1.task1) assertEquals( "ComponentName package mismatched", it[1].key.baseIntent.component?.packageName, - matchingPackage + matchingPackage, ) assertEquals( "ComponentName class mismatched", it[1].key.baseIntent.component?.className, - matchingClass + matchingClass, ) assertEquals(it[1], groupTask2.task2) } @@ -498,7 +502,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(matchingComponent, matchingComponent), false /* findExactPairMatch */, - taskConsumer + taskConsumer, ) verify(recentsModel).getTasks(capture()) } @@ -524,12 +528,12 @@ class SplitSelectStateControllerTest { val groupTask2 = generateGroupTask( ComponentName(matchingPackage2, matchingClass2), - ComponentName(matchingPackage, matchingClass) + ComponentName(matchingPackage, matchingClass), ) val groupTask3 = generateGroupTask( ComponentName("hotdog", "pie"), - ComponentName(matchingPackage, matchingClass) + ComponentName(matchingPackage, matchingClass), ) val tasks: ArrayList = ArrayList() tasks.add(groupTask3) @@ -550,7 +554,7 @@ class SplitSelectStateControllerTest { splitSelectStateController.findLastActiveTasksAndRunCallback( listOf(matchingComponent2, matchingComponent), true /* findExactPairMatch */, - taskConsumer + taskConsumer, ) verify(recentsModel).getTasks(capture()) } @@ -567,7 +571,7 @@ class SplitSelectStateControllerTest { -1 /*stagePosition*/, ItemInfo(), null /*splitEvent*/, - 10 /*alreadyRunningTask*/ + 10, /*alreadyRunningTask*/ ) assertTrue(splitSelectStateController.isSplitSelectActive) } @@ -579,21 +583,23 @@ class SplitSelectStateControllerTest { -1 /*stagePosition*/, ItemInfo(), null /*splitEvent*/, - -1 /*alreadyRunningTask*/ + -1, /*alreadyRunningTask*/ ) assertTrue(splitSelectStateController.isSplitSelectActive) } @Test fun resetAfterInitial() { + whenever(context.getOverviewPanel>()).thenReturn(recentsView) splitSelectStateController.setInitialTaskSelect( Intent() /*intent*/, -1 /*stagePosition*/, ItemInfo(), null /*splitEvent*/, - -1 + -1, ) splitSelectStateController.resetState() + verify(recentsView, times(1)).resetDesktopTaskFromSplitSelectState() assertFalse(splitSelectStateController.isSplitSelectActive) } @@ -622,7 +628,7 @@ class SplitSelectStateControllerTest { // Generate GroupTask with default userId. private fun generateGroupTask( task1ComponentName: ComponentName, - task2ComponentName: ComponentName + task2ComponentName: ComponentName, ): GroupTask { val task1 = Task() var taskInfo = ActivityManager.RunningTaskInfo() @@ -642,7 +648,7 @@ class SplitSelectStateControllerTest { return GroupTask( task1, task2, - SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_2_50_50) + SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_2_50_50), ) } @@ -651,7 +657,7 @@ class SplitSelectStateControllerTest { task1ComponentName: ComponentName, userHandle1: UserHandle, task2ComponentName: ComponentName, - userHandle2: UserHandle + userHandle2: UserHandle, ): GroupTask { val task1 = Task() var taskInfo = ActivityManager.RunningTaskInfo() @@ -674,7 +680,7 @@ class SplitSelectStateControllerTest { return GroupTask( task1, task2, - SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_2_50_50) + SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_2_50_50), ) } }