diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index 8990065015f141e4e266712d812dfbd588945912..1917bd28cb2c9d2a24f4b31f2d870eaa6b3c9602 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -18,10 +18,15 @@ package com.android.wm.shell.splitscreen; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.graphics.Rect; +import android.os.Bundle; +import android.window.RemoteTransition; import com.android.wm.shell.common.annotations.ExternalThread; +import com.android.internal.logging.InstanceId; +import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import java.util.concurrent.Executor; @@ -74,6 +79,12 @@ public interface SplitScreen { } } + /** Launches a pair of tasks into splitscreen */ + void startTasks(int taskId1, @Nullable Bundle options1, int taskId2, + @Nullable Bundle options2, @SplitPosition int splitPosition, + @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, + InstanceId instanceId); + /** Registers listener that gets split screen callback. */ void registerSplitScreenListener(@NonNull SplitScreenListener listener, @NonNull Executor executor); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 79bc24edf961fe1616c24e9cba3b4bee4ab51698..c27b514cffc12dc3d7beb66686917772826cc2f5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -494,6 +494,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return mStageCoordinator.getActivateSplitPosition(taskInfo); } + /** Start two tasks in parallel as a splitscreen pair. */ + public void startTasks(int taskId1, @Nullable Bundle options1, int taskId2, + @Nullable Bundle options2, @SplitPosition int splitPosition, + @PersistentSnapPosition int snapPosition, + @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + mStageCoordinator.startTasks(taskId1, options1, taskId2, options2, splitPosition, + snapPosition, remoteTransition, instanceId); + } + /** * Move a task to split select * @param taskInfo the task being moved to split select @@ -1105,6 +1114,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } }; + @Override + public void startTasks(int taskId1, @Nullable Bundle options1, int taskId2, + @Nullable Bundle options2, int splitPosition, int snapPosition, + @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + mMainExecutor.execute(() -> SplitScreenController.this.startTasks( + taskId1, options1, taskId2, options2, splitPosition, snapPosition, + remoteTransition, instanceId)); + } + @Override public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) { if (mExecutors.containsKey(listener)) return; diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 9e0f78ce0900098c874035ae674b9c14685fbca3..321ff68421fd3110e18da706f025eb2c3cbc050c 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -115,7 +115,17 @@ flag { } flag { - name: "notifications_background_media_icons" + name: "pss_app_selector_recents_split_screen" + namespace: "systemui" + description: "Allows recent apps selected for partial screenshare to be launched in split screen mode" + bug: "320449039" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "notifications_background_icons" namespace: "systemui" description: "Updates icons for media notifications in the background." bug: "315143160" diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt index e9b458271ef7ca22bff61a403362f505cbe3174c..1fc97e94e250e2feb704bf28efbd1fbba4659e88 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt @@ -18,7 +18,9 @@ package com.android.systemui.mediaprojection.appselector.data import android.annotation.ColorInt import android.annotation.UserIdInt +import android.app.ActivityManager.RecentTaskInfo import android.content.ComponentName +import com.android.wm.shell.util.SplitBounds data class RecentTask( val taskId: Int, @@ -29,3 +31,30 @@ data class RecentTask( @ColorInt val colorBackground: Int?, val isForegroundTask: Boolean, ) + val userType: UserType, + val splitBounds: SplitBounds?, +) { + constructor( + taskInfo: RecentTaskInfo, + isForegroundTask: Boolean, + userType: UserType, + splitBounds: SplitBounds? = null + ) : this( + taskInfo.taskId, + taskInfo.displayId, + taskInfo.userId, + taskInfo.topActivity, + taskInfo.baseIntent?.component, + taskInfo.taskDescription?.backgroundColor, + isForegroundTask, + userType, + splitBounds + ) + + enum class UserType { + STANDARD, + WORK, + PRIVATE, + CLONED + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt index 5dde14bf0867e131ccecb27cc000cc21705a6ecd..f6b762b44db025de032637a797691c8f7b66d130 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt @@ -55,9 +55,8 @@ constructor( val foregroundTaskId1 = foregroundGroup?.taskInfo1?.taskId val foregroundTaskId2 = foregroundGroup?.taskInfo2?.taskId val foregroundTaskIds = listOfNotNull(foregroundTaskId1, foregroundTaskId2) - groupedTasks - .flatMap { listOfNotNull(it.taskInfo1, it.taskInfo2) } - .map { + groupedTasks.flatMap { + val task1 = RecentTask( it.taskId, it.displayId, @@ -66,8 +65,24 @@ constructor( it.baseIntent?.component, it.taskDescription?.backgroundColor, isForegroundTask = it.taskId in foregroundTaskIds && it.isVisible + it.taskInfo1, + it.taskInfo1.taskId in foregroundTaskIds && it.taskInfo1.isVisible, + userManager.getUserInfo(it.taskInfo1.userId).toUserType(), + it.splitBounds ) - } + + val task2 = + if (it.taskInfo2 != null) { + RecentTask( + it.taskInfo2!!, + it.taskInfo2!!.taskId in foregroundTaskIds && it.taskInfo2!!.isVisible, + userManager.getUserInfo(it.taskInfo2!!.userId).toUserType(), + it.splitBounds + ) + } else null + + listOfNotNull(task1, task2) + } } private suspend fun RecentTasks.getTasks(): List<GroupedRecentTaskInfo> = diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt index 7c7efd0be8ed000282c02310cca2b5573ba47d50..9549ab1cab3e18a3a61f1e50f86470afac3129b9 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt @@ -24,9 +24,12 @@ import android.graphics.Rect import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.window.RemoteTransition import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.android.systemui.Flags.pssAppSelectorAbruptExitFix +import com.android.systemui.Flags.pssAppSelectorRecentsSplitScreen +import com.android.systemui.display.naturalBounds import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope import com.android.systemui.mediaprojection.appselector.data.RecentTask @@ -34,6 +37,11 @@ import com.android.systemui.mediaprojection.appselector.view.RecentTasksAdapter. import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener import com.android.systemui.res.R import com.android.systemui.util.recycler.HorizontalSpacerItemDecoration +import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT +import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT +import com.android.wm.shell.splitscreen.SplitScreen +import com.android.wm.shell.util.SplitBounds +import java.util.Optional import javax.inject.Inject /** @@ -48,6 +56,7 @@ constructor( private val taskViewSizeProvider: TaskPreviewSizeProvider, private val activityTaskManager: IActivityTaskManager, private val resultHandler: MediaProjectionAppSelectorResultHandler, + private val splitScreen: Optional<SplitScreen>, ) : RecentTaskClickListener, TaskPreviewSizeListener { private var views: Views? = null @@ -63,11 +72,11 @@ constructor( fun createView(parent: ViewGroup): ViewGroup = views?.root ?: createRecentViews(parent) - .also { - views = it - lastBoundData?.let { recents -> bind(recents) } - } - .root + .also { + views = it + lastBoundData?.let { recents -> bind(recents) } + } + .root fun bind(recentTasks: List<RecentTask>) { views?.apply { @@ -93,8 +102,10 @@ constructor( private fun createRecentViews(parent: ViewGroup): Views { val recentsRoot = LayoutInflater.from(parent.context) - .inflate(R.layout.media_projection_recent_tasks, parent, /* attachToRoot= */ false) - as ViewGroup + .inflate(R.layout.media_projection_recent_tasks, + parent, /* attachToRoot= */ + false) + as ViewGroup val container = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_container) @@ -121,18 +132,34 @@ constructor( return Views(recentsRoot, container, progress, recycler) } + private fun RecentTask.isLaunchingInSplitScreen(): Boolean { + return splitScreen.isPresent && splitBounds != null + } + override fun onRecentAppClicked(task: RecentTask, view: View) { val launchCookie = LaunchCookie() val activityOptions = createAnimation(task, view) activityOptions.pendingIntentBackgroundActivityStartMode = MODE_BACKGROUND_ACTIVITY_START_ALLOWED - activityOptions.setLaunchCookie(launchCookie) activityOptions.launchDisplayId = task.displayId + activityOptions.setLaunchCookie(launchCookie) + + val handleResult: () -> Unit = { resultHandler.returnSelectedApp(launchCookie)} + + val taskId = task.taskId + val splitBounds = task.splitBounds - activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle()) - resultHandler.returnSelectedApp(launchCookie) + if (pssAppSelectorRecentsSplitScreen() && + task.isLaunchingInSplitScreen() && + !task.isForegroundTask) { + startSplitScreenTask(view, taskId, splitBounds!!, handleResult, activityOptions) + } else { + activityTaskManager.startActivityFromRecents(taskId, activityOptions.toBundle()) + handleResult() + } } + private fun createAnimation(task: RecentTask, view: View): ActivityOptions = if (pssAppSelectorAbruptExitFix() && task.isForegroundTask) { // When the selected task is in the foreground, the scale up animation doesn't work. @@ -145,7 +172,14 @@ constructor( /* startedListener = */ null, /* finishedListener = */ null ) + } else if (task.isLaunchingInSplitScreen()) { + // When the selected task isn't in the foreground, but is launching in split screen, + // then we don't need to specify an animation, since we'll already be passing a + // manually built remote animation to SplitScreenController + ActivityOptions.makeBasic() } else { + // The default case is a selected task not in the foreground and launching fullscreen, + // so for this we can use the default ActivityOptions animation ActivityOptions.makeScaleUpAnimation( view, /* startX= */ 0, @@ -155,6 +189,29 @@ constructor( ) } + private fun startSplitScreenTask( + view: View, + taskId: Int, + splitBounds: SplitBounds, + handleResult: () -> Unit, + activityOptions: ActivityOptions, + ) { + val isLeftTopTask = taskId == splitBounds.leftTopTaskId + val task2Id = + if (isLeftTopTask) splitBounds.rightBottomTaskId else splitBounds.leftTopTaskId + val splitPosition = + if (isLeftTopTask) SPLIT_POSITION_TOP_OR_LEFT else SPLIT_POSITION_BOTTOM_OR_RIGHT + + val animationRunner = RemoteRecentSplitTaskTransitionRunner(taskId, task2Id, + view.locationOnScreen, view.context.display.naturalBounds, handleResult) + val remoteTransition = RemoteTransition(animationRunner, + view.context.iApplicationThread, "startSplitScreenTask") + + splitScreen.get().startTasks(taskId, activityOptions.toBundle(), task2Id, null, + splitPosition, splitBounds.snapPosition, remoteTransition, null) + } + + override fun onTaskSizeChanged(size: Rect) { views?.recentsContainer?.setTaskHeightSize() } @@ -163,12 +220,12 @@ constructor( val thumbnailHeight = taskViewSizeProvider.size.height() val itemHeight = thumbnailHeight + - context.resources.getDimensionPixelSize( - R.dimen.media_projection_app_selector_task_icon_size - ) + - context.resources.getDimensionPixelSize( - R.dimen.media_projection_app_selector_task_icon_margin - ) * 2 + context.resources.getDimensionPixelSize( + R.dimen.media_projection_app_selector_task_icon_size + ) + + context.resources.getDimensionPixelSize( + R.dimen.media_projection_app_selector_task_icon_margin + ) * 2 layoutParams = layoutParams.apply { height = itemHeight } } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RemoteRecentSplitTaskTransitionRunner.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RemoteRecentSplitTaskTransitionRunner.kt new file mode 100644 index 0000000000000000000000000000000000000000..9514c4ab8f2ddb195b0e1b6d43aeada449ffaa47 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RemoteRecentSplitTaskTransitionRunner.kt @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.mediaprojection.appselector.view + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.AnimatorSet +import android.animation.ValueAnimator +import android.annotation.UiThread +import android.graphics.Rect +import android.os.IBinder +import android.os.RemoteException +import android.util.Log +import android.view.SurfaceControl +import android.view.animation.DecelerateInterpolator +import android.window.IRemoteTransition +import android.window.IRemoteTransitionFinishedCallback +import android.window.TransitionInfo +import android.window.WindowContainerToken +import com.android.app.viewcapture.ViewCapture +import com.android.internal.policy.TransitionAnimation +import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity.Companion.TAG + +class RemoteRecentSplitTaskTransitionRunner( + private val firstTaskId: Int, + private val secondTaskId: Int, + private val viewPosition: IntArray, + private val screenBounds: Rect, + private val handleResult: () -> Unit, +) : IRemoteTransition.Stub() { + override fun startAnimation( + transition: IBinder?, + info: TransitionInfo?, + t: SurfaceControl.Transaction?, + finishedCallback: IRemoteTransitionFinishedCallback + ) { + val launchAnimation = AnimatorSet() + var rootCandidate = + info!!.changes.firstOrNull { + it.taskInfo?.taskId == firstTaskId || it.taskInfo?.taskId == secondTaskId + } + + // If we could not find a proper root candidate, something went wrong. + check(rootCandidate != null) { "Could not find a split root candidate" } + + // Recurse up the tree until parent is null, then we've found our root. + var parentToken: WindowContainerToken? = rootCandidate.parent + while (parentToken != null) { + rootCandidate = info.getChange(parentToken) ?: break + parentToken = rootCandidate.parent + } + + // Make sure nothing weird happened, like getChange() returning null. + check(rootCandidate != null) { "Failed to find a root leash" } + + // Ending position is the full device screen. + val startingScale = 0.25f + + val startX = viewPosition[0] + val startY = viewPosition[1] + val endX = screenBounds.left + val endY = screenBounds.top + + ViewCapture.MAIN_EXECUTOR.execute { + val progressUpdater = ValueAnimator.ofFloat(0f, 1f) + with(progressUpdater) { + interpolator = DecelerateInterpolator(1.5f) + setDuration(TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION.toLong()) + + addUpdateListener { valueAnimator -> + val progress = valueAnimator.animatedFraction + + val x = startX + ((endX - startX) * progress) + val y = startY + ((endY - startY) * progress) + val scale = startingScale + ((1 - startingScale) * progress) + + t!! + .setPosition(rootCandidate.leash, x, y) + .setScale(rootCandidate.leash, scale, scale) + .setAlpha(rootCandidate.leash, progress) + .apply() + } + + addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + try { + onTransitionFinished() + finishedCallback.onTransitionFinished(null, null) + } catch (e: RemoteException) { + Log.e(TAG, "Failed to call transition finished callback", e) + } + } + } + ) + } + + launchAnimation.play(progressUpdater) + launchAnimation.start() + } + } + + override fun mergeAnimation( + transition: IBinder?, + info: TransitionInfo?, + t: SurfaceControl.Transaction?, + mergeTarget: IBinder?, + finishedCallback: IRemoteTransitionFinishedCallback? + ) {} + + @Throws(RemoteException::class) + override fun onTransitionConsumed(transition: IBinder, aborted: Boolean) { + Log.w(TAG, "unexpected consumption of app selector transition: aborted=$aborted") + } + + @UiThread + private fun onTransitionFinished() { + // After finished transition, then invoke callback to close the app selector, so that + // finish animation of app selector does not override the launch animation of the split + // tasks + handleResult() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt index 44798ea99bee2c1de15b1c8153a35da10a6c7c7e..253607846e0fefd7628f5e8afacf92343b175606 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt @@ -257,6 +257,8 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { userId = userId, colorBackground = 0, isForegroundTask = isForegroundTask, + userType = RecentTask.UserType.STANDARD, + splitBounds = null, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt index b593def283ae5c2f537ca0fae3e763626b217f16..c2289c4757f4e1cf2b600a920fc3fb20b364c142 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt @@ -164,6 +164,8 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { baseIntentComponent = null, colorBackground = null, isForegroundTask = false, + userType = userType, + splitBounds = null ) private fun createSingleTask(taskId: Int, isVisible: Boolean = false): GroupedRecentTaskInfo = diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt index ac4107359dbcbc4050b7d556a4d3a53895283743..a50922aa03c05ebc98abd399ea0a9afa2e688332 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt @@ -18,22 +18,28 @@ package com.android.systemui.mediaprojection.appselector.view import android.app.ActivityOptions import android.app.IActivityTaskManager +import android.graphics.Rect import android.os.Bundle import android.view.View import android.view.ViewGroup import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX +import com.android.systemui.Flags.FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN import com.android.systemui.SysuiTestCase import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler import com.android.systemui.mediaprojection.appselector.data.RecentTask import com.android.systemui.util.mockito.mock +import com.android.wm.shell.splitscreen.SplitScreen +import com.android.wm.shell.util.SplitBounds import com.google.common.truth.Expect import com.google.common.truth.Truth.assertThat +import java.util.Optional import org.junit.Rule import org.junit.Test import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.eq import org.mockito.Mockito.any +import org.mockito.Mockito.anyInt import org.mockito.Mockito.verify @SmallTest @@ -46,9 +52,10 @@ class MediaProjectionRecentsViewControllerTest : SysuiTestCase() { private val taskViewSizeProvider = mock<TaskPreviewSizeProvider>() private val activityTaskManager = mock<IActivityTaskManager>() private val resultHandler = mock<MediaProjectionAppSelectorResultHandler>() + private val splitScreen = Optional.of(mock<SplitScreen>()) private val bundleCaptor = ArgumentCaptor.forClass(Bundle::class.java) - private val task = + private val fullScreenTask = RecentTask( taskId = 123, displayId = 456, @@ -57,6 +64,22 @@ class MediaProjectionRecentsViewControllerTest : SysuiTestCase() { baseIntentComponent = null, colorBackground = null, isForegroundTask = false + isForegroundTask = false, + userType = RecentTask.UserType.STANDARD, + splitBounds = null + ) + + private val splitScreenTask = + RecentTask( + taskId = 123, + displayId = 456, + userId = 789, + topActivityComponent = null, + baseIntentComponent = null, + colorBackground = null, + isForegroundTask = false, + userType = RecentTask.UserType.STANDARD, + splitBounds = SplitBounds(Rect(), Rect(), 0, 0, 0) ) private val taskView = @@ -69,61 +92,97 @@ class MediaProjectionRecentsViewControllerTest : SysuiTestCase() { tasksAdapterFactory, taskViewSizeProvider, activityTaskManager, - resultHandler + resultHandler, + splitScreen, ) @Test - fun onRecentAppClicked_taskWithSameIdIsStartedFromRecents() { - controller.onRecentAppClicked(task, taskView) + fun onRecentAppClicked_fullScreenTaskWithSameIdIsStartedFromRecents() { + controller.onRecentAppClicked(fullScreenTask, taskView) - verify(activityTaskManager).startActivityFromRecents(eq(task.taskId), any()) + verify(activityTaskManager).startActivityFromRecents(eq(fullScreenTask.taskId), any()) + } + + @Test + fun onRecentAppClicked_splitScreenTaskWithSameIdIsStartedFromRecents() { + mSetFlagsRule.enableFlags(FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN) + controller.onRecentAppClicked(splitScreenTask, taskView) + + verify(splitScreen.get()) + .startTasks( + eq(splitScreenTask.taskId), + any(), + anyInt(), + any(), + anyInt(), + anyInt(), + any(), + any() + ) } @Test fun onRecentAppClicked_launchDisplayIdIsSet() { - controller.onRecentAppClicked(task, taskView) + controller.onRecentAppClicked(fullScreenTask, taskView) - assertThat(getStartedTaskActivityOptions().launchDisplayId).isEqualTo(task.displayId) + assertThat(getStartedTaskActivityOptions(fullScreenTask.taskId).launchDisplayId) + .isEqualTo(fullScreenTask.displayId) } @Test - fun onRecentAppClicked_taskNotInForeground_usesScaleUpAnimation() { - controller.onRecentAppClicked(task, taskView) + fun onRecentAppClicked_fullScreenTaskNotInForeground_usesScaleUpAnimation() { + assertThat(fullScreenTask.isForegroundTask).isFalse() + controller.onRecentAppClicked(fullScreenTask, taskView) - assertThat(getStartedTaskActivityOptions().animationType) + assertThat(getStartedTaskActivityOptions(fullScreenTask.taskId).animationType) .isEqualTo(ActivityOptions.ANIM_SCALE_UP) } @Test - fun onRecentAppClicked_taskInForeground_flagOff_usesScaleUpAnimation() { + fun onRecentAppClicked_fullScreenTaskInForeground_flagOff_usesScaleUpAnimation() { mSetFlagsRule.disableFlags(FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX) - controller.onRecentAppClicked(task, taskView) + controller.onRecentAppClicked(fullScreenTask, taskView) - assertThat(getStartedTaskActivityOptions().animationType) + assertThat(getStartedTaskActivityOptions(fullScreenTask.taskId).animationType) .isEqualTo(ActivityOptions.ANIM_SCALE_UP) } @Test - fun onRecentAppClicked_taskInForeground_flagOn_usesDefaultAnimation() { + fun onRecentAppClicked_fullScreenTaskInForeground_flagOn_usesDefaultAnimation() { mSetFlagsRule.enableFlags(FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX) - val foregroundTask = task.copy(isForegroundTask = true) + assertForegroundTaskUsesDefaultCloseAnimation(fullScreenTask) + } + + @Test + fun onRecentAppClicked_splitScreenTaskInForeground_flagOn_usesDefaultAnimation() { + mSetFlagsRule.enableFlags( + FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX, + FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN + ) + assertForegroundTaskUsesDefaultCloseAnimation(splitScreenTask) + } + private fun assertForegroundTaskUsesDefaultCloseAnimation(task: RecentTask) { + val foregroundTask = task.copy(isForegroundTask = true) controller.onRecentAppClicked(foregroundTask, taskView) expect - .that(getStartedTaskActivityOptions().animationType) + .that(getStartedTaskActivityOptions(foregroundTask.taskId).animationType) .isEqualTo(ActivityOptions.ANIM_CUSTOM) - expect.that(getStartedTaskActivityOptions().overrideTaskTransition).isTrue() expect - .that(getStartedTaskActivityOptions().customExitResId) + .that(getStartedTaskActivityOptions(foregroundTask.taskId).overrideTaskTransition) + .isTrue() + expect + .that(getStartedTaskActivityOptions(foregroundTask.taskId).customExitResId) .isEqualTo(com.android.internal.R.anim.resolver_close_anim) - expect.that(getStartedTaskActivityOptions().customEnterResId).isEqualTo(0) + expect + .that(getStartedTaskActivityOptions(foregroundTask.taskId).customEnterResId) + .isEqualTo(0) } - private fun getStartedTaskActivityOptions(): ActivityOptions { - verify(activityTaskManager) - .startActivityFromRecents(eq(task.taskId), bundleCaptor.capture()) + private fun getStartedTaskActivityOptions(taskId: Int): ActivityOptions { + verify(activityTaskManager).startActivityFromRecents(eq(taskId), bundleCaptor.capture()) return ActivityOptions.fromBundle(bundleCaptor.value) } }