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), ) } }