Loading libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +24 −1 Original line number Original line Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.wm.shell.dagger; package com.android.wm.shell.dagger; import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS; import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS; import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY; import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT; import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT; import static android.window.DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS; import static android.window.DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS; Loading Loading @@ -94,6 +95,7 @@ import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator; import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler; import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler; import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler; import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler; import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository; import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository; import com.android.wm.shell.desktopmode.compatui.SystemModalsTransitionHandler; import com.android.wm.shell.desktopmode.education.AppHandleEducationController; import com.android.wm.shell.desktopmode.education.AppHandleEducationController; import com.android.wm.shell.desktopmode.education.AppHandleEducationFilter; import com.android.wm.shell.desktopmode.education.AppHandleEducationFilter; import com.android.wm.shell.desktopmode.education.AppToWebEducationController; import com.android.wm.shell.desktopmode.education.AppToWebEducationController; Loading Loading @@ -906,6 +908,26 @@ public abstract class WMShellModule { focusTransitionObserver, desktopModeEventLogger)); focusTransitionObserver, desktopModeEventLogger)); } } @WMSingleton @Provides static Optional<SystemModalsTransitionHandler> provideSystemModalsTransitionHandler( Context context, @ShellMainThread ShellExecutor mainExecutor, @ShellAnimationThread ShellExecutor animExecutor, ShellInit shellInit, Transitions transitions, @DynamicOverride DesktopRepository desktopRepository) { if (!DesktopModeStatus.canEnterDesktopMode(context) || !ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() || !Flags.enableDesktopSystemDialogsTransitions()) { return Optional.empty(); } return Optional.of( new SystemModalsTransitionHandler( context, mainExecutor, animExecutor, shellInit, transitions, desktopRepository)); } @WMSingleton @WMSingleton @Provides @Provides static EnterDesktopTaskTransitionHandler provideEnterDesktopModeTaskTransitionHandler( static EnterDesktopTaskTransitionHandler provideEnterDesktopModeTaskTransitionHandler( Loading Loading @@ -1262,7 +1284,8 @@ public abstract class WMShellModule { @NonNull LetterboxCommandHandler letterboxCommandHandler, @NonNull LetterboxCommandHandler letterboxCommandHandler, Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional, Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional, Optional<DesktopDisplayEventHandler> desktopDisplayEventHandler, Optional<DesktopDisplayEventHandler> desktopDisplayEventHandler, Optional<DesktopModeKeyGestureHandler> desktopModeKeyGestureHandler) { Optional<DesktopModeKeyGestureHandler> desktopModeKeyGestureHandler, Optional<SystemModalsTransitionHandler> systemModalsTransitionHandler) { return new Object(); return new Object(); } } Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt 0 → 100644 +174 −0 Original line number Original line Diff line number Diff line /* * 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.wm.shell.desktopmode.compatui import android.animation.ValueAnimator import android.content.Context import android.os.IBinder import android.view.Display.DEFAULT_DISPLAY import android.view.SurfaceControl import android.window.TransitionInfo import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction import androidx.core.animation.addListener import com.android.app.animation.Interpolators import com.android.internal.protolog.ProtoLog import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.TransitionUtil.isClosingMode import com.android.wm.shell.shared.TransitionUtil.isClosingType import com.android.wm.shell.shared.TransitionUtil.isOpeningMode import com.android.wm.shell.shared.TransitionUtil.isOpeningType import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TransitionHandler /** Handles transitions related to system modals, e.g. launch and close transitions. */ class SystemModalsTransitionHandler( private val context: Context, private val mainExecutor: ShellExecutor, private val animExecutor: ShellExecutor, private val shellInit: ShellInit, private val transitions: Transitions, private val desktopRepository: DesktopRepository, ) : TransitionHandler { private val showingSystemModalsIds = mutableSetOf<Int>() init { shellInit.addInitCallback({ transitions.addHandler(this) }, this) } override fun startAnimation( transition: IBinder, info: TransitionInfo, startTransaction: SurfaceControl.Transaction, finishTransaction: SurfaceControl.Transaction, finishCallback: Transitions.TransitionFinishCallback, ): Boolean { if (!isDesktopModeShowing(DEFAULT_DISPLAY)) return false if (isOpeningType(info.type)) { val launchChange = getLaunchingSystemModal(info) ?: return false val taskInfo = launchChange.taskInfo requireNotNull(taskInfo) logV("Animating system modal launch: taskId=%d", taskInfo.taskId) showingSystemModalsIds.add(taskInfo.taskId) animateSystemModal( launchChange.leash, startTransaction, finishTransaction, finishCallback, /* toShow= */ true, ) return true } if (isClosingType(info.type)) { val closeChange = getClosingSystemModal(info) ?: return false val taskInfo = closeChange.taskInfo requireNotNull(taskInfo) logV("Animating system modal close: taskId=%d", taskInfo.taskId) showingSystemModalsIds.remove(taskInfo.taskId) animateSystemModal( closeChange.leash, startTransaction, finishTransaction, finishCallback, /* toShow= */ false, ) return true } return false } private fun animateSystemModal( leash: SurfaceControl, startTransaction: SurfaceControl.Transaction, finishTransaction: SurfaceControl.Transaction, finishCallback: Transitions.TransitionFinishCallback, toShow: Boolean, // Whether to show or to hide the system modal ) { val startAlpha = if (toShow) 0f else 1f val endAlpha = if (toShow) 1f else 0f val animator = createAlphaAnimator(SurfaceControl.Transaction(), leash, startAlpha, endAlpha) animator.addListener( onEnd = { _ -> mainExecutor.execute { finishCallback.onTransitionFinished(/* wct= */ null) } } ) if (toShow) { finishTransaction.show(leash) } else { finishTransaction.hide(leash) } startTransaction.setAlpha(leash, startAlpha) startTransaction.apply() animExecutor.execute { animator.start() } } private fun getLaunchingSystemModal(info: TransitionInfo): TransitionInfo.Change? = info.changes.find { change -> if (!isOpeningMode(change.mode)) { return@find false } val taskInfo = change.taskInfo ?: return@find false return@find isTopActivityExemptFromDesktopWindowing(context, taskInfo) } private fun getClosingSystemModal(info: TransitionInfo): TransitionInfo.Change? = info.changes.find { change -> if (!isClosingMode(change.mode)) { return@find false } val taskInfo = change.taskInfo ?: return@find false return@find isTopActivityExemptFromDesktopWindowing(context, taskInfo) || showingSystemModalsIds.contains(taskInfo.taskId) } private fun createAlphaAnimator( transaction: SurfaceControl.Transaction, leash: SurfaceControl, startVal: Float, endVal: Float, ): ValueAnimator = ValueAnimator.ofFloat(startVal, endVal).apply { duration = LAUNCH_ANIM_ALPHA_DURATION_MS interpolator = Interpolators.LINEAR addUpdateListener { animation -> transaction.setAlpha(leash, animation.animatedValue as Float).apply() } } private fun isDesktopModeShowing(displayId: Int): Boolean = desktopRepository.getVisibleTaskCount(displayId) > 0 override fun handleRequest( transition: IBinder, request: TransitionRequestInfo, ): WindowContainerTransaction? = null private fun logV(msg: String, vararg arguments: Any?) { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } companion object { private const val TAG = "SystemModalsTransitionHandler" private const val LAUNCH_ANIM_ALPHA_DURATION_MS = 150L } } libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt +19 −3 Original line number Original line Diff line number Diff line Loading @@ -46,17 +46,23 @@ class DesktopTestHelpers { .build() .build() } } /** Create a task that has windowing mode set to [WINDOWING_MODE_FULLSCREEN] */ /** Create a task builder that has windowing mode set to [WINDOWING_MODE_FULLSCREEN] */ @JvmStatic @JvmStatic @JvmOverloads @JvmOverloads fun createFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { fun createFullscreenTaskBuilder(displayId: Int = DEFAULT_DISPLAY): TestRunningTaskInfoBuilder { return TestRunningTaskInfoBuilder() return TestRunningTaskInfoBuilder() .setDisplayId(displayId) .setDisplayId(displayId) .setToken(MockToken().token()) .setToken(MockToken().token()) .setActivityType(ACTIVITY_TYPE_STANDARD) .setActivityType(ACTIVITY_TYPE_STANDARD) .setWindowingMode(WINDOWING_MODE_FULLSCREEN) .setWindowingMode(WINDOWING_MODE_FULLSCREEN) .setLastActiveTime(100) .setLastActiveTime(100) .build() } /** Create a task that has windowing mode set to [WINDOWING_MODE_FULLSCREEN] */ @JvmStatic @JvmOverloads fun createFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { return createFullscreenTaskBuilder(displayId).build() } } /** Create a task that has windowing mode set to [WINDOWING_MODE_MULTI_WINDOW] */ /** Create a task that has windowing mode set to [WINDOWING_MODE_MULTI_WINDOW] */ Loading Loading @@ -84,5 +90,15 @@ class DesktopTestHelpers { .setLastActiveTime(100) .setLastActiveTime(100) .build() .build() } } /** Create a new System Modal task, i.e. a task with a single transparent activity. */ @JvmStatic @JvmOverloads fun createSystemModalTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { return createFullscreenTaskBuilder(displayId) .setTopActivityTransparent(true) .setNumActivities(1) .build() } } } } } No newline at end of file libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt 0 → 100644 +157 −0 Original line number Original line Diff line number Diff line /* * 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.wm.shell.desktopmode.compatui import android.os.Binder import android.testing.AndroidTestingRunner import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_OPEN import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTaskBuilder import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSystemModalTask import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.TransitionInfoBuilder import com.android.wm.shell.transition.Transitions import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidTestingRunner::class) class SystemModalsTransitionHandlerTest : ShellTestCase() { private val mainExecutor = mock<ShellExecutor>() private val animExecutor = mock<ShellExecutor>() private val shellInit = mock<ShellInit>() private val transitions = mock<Transitions>() private val desktopRepository = mock<DesktopRepository>() private val startT = mock<SurfaceControl.Transaction>() private val finishT = mock<SurfaceControl.Transaction>() private lateinit var transitionHandler: SystemModalsTransitionHandler @Before fun setUp() { // Simulate having one Desktop task so that we see Desktop Mode as active whenever(desktopRepository.getVisibleTaskCount(anyInt())).thenReturn(1) transitionHandler = createTransitionHandler() } private fun createTransitionHandler() = SystemModalsTransitionHandler( context, mainExecutor, animExecutor, shellInit, transitions, desktopRepository, ) @Test fun instantiate_addsInitCallback() { verify(shellInit).addInitCallback(any(), any<SystemModalsTransitionHandler>()) } @Test fun startAnimation_desktopNotActive_doesNotAnimate() { whenever(desktopRepository.getVisibleTaskCount(anyInt())).thenReturn(1) val info = TransitionInfoBuilder(TRANSIT_OPEN) .addChange(TRANSIT_OPEN, createSystemModalTask()) .build() assertThat(transitionHandler.startAnimation(Binder(), info, startT, finishT) {}).isTrue() } @Test fun startAnimation_launchingSystemModal_animates() { val info = TransitionInfoBuilder(TRANSIT_OPEN) .addChange(TRANSIT_OPEN, createSystemModalTask()) .build() assertThat(transitionHandler.startAnimation(Binder(), info, startT, finishT) {}).isTrue() } @Test fun startAnimation_nonLaunchingSystemModal_doesNotAnimate() { val info = TransitionInfoBuilder(TRANSIT_OPEN) .addChange(TRANSIT_CHANGE, createSystemModalTask()) .build() assertThat(transitionHandler.startAnimation(Binder(), info, startT, finishT) {}).isFalse() } @Test fun startAnimation_launchingFullscreenTask_doesNotAnimate() { val info = TransitionInfoBuilder(TRANSIT_OPEN) .addChange(TRANSIT_OPEN, createFullscreenTask()) .build() assertThat(transitionHandler.startAnimation(Binder(), info, startT, finishT) {}).isFalse() } @Test fun startAnimation_closingSystemModal_animates() { val info = TransitionInfoBuilder(TRANSIT_CLOSE) .addChange(TRANSIT_CLOSE, createSystemModalTask()) .build() assertThat(transitionHandler.startAnimation(Binder(), info, startT, finishT) {}).isTrue() } @Test fun startAnimation_closingFullscreenTask_doesNotAnimate() { val info = TransitionInfoBuilder(TRANSIT_CLOSE) .addChange(TRANSIT_CLOSE, createFullscreenTask()) .build() assertThat(transitionHandler.startAnimation(Binder(), info, startT, finishT) {}).isFalse() } @Test fun startAnimation_closingPreviouslyLaunchedSystemModal_animates() { val systemModalTask = createSystemModalTask() val nonModalSystemModalTask = createFullscreenTaskBuilder().setTaskId(systemModalTask.taskId).build() val launchInfo = TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_OPEN, systemModalTask).build() transitionHandler.startAnimation(Binder(), launchInfo, startT, finishT) {} val closeInfo = TransitionInfoBuilder(TRANSIT_CLOSE) .addChange(TRANSIT_CLOSE, nonModalSystemModalTask) .build() assertThat(transitionHandler.startAnimation(Binder(), closeInfo, startT, finishT) {}) .isTrue() } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +24 −1 Original line number Original line Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.wm.shell.dagger; package com.android.wm.shell.dagger; import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS; import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS; import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY; import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT; import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT; import static android.window.DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS; import static android.window.DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS; Loading Loading @@ -94,6 +95,7 @@ import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator; import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler; import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler; import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler; import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler; import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository; import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository; import com.android.wm.shell.desktopmode.compatui.SystemModalsTransitionHandler; import com.android.wm.shell.desktopmode.education.AppHandleEducationController; import com.android.wm.shell.desktopmode.education.AppHandleEducationController; import com.android.wm.shell.desktopmode.education.AppHandleEducationFilter; import com.android.wm.shell.desktopmode.education.AppHandleEducationFilter; import com.android.wm.shell.desktopmode.education.AppToWebEducationController; import com.android.wm.shell.desktopmode.education.AppToWebEducationController; Loading Loading @@ -906,6 +908,26 @@ public abstract class WMShellModule { focusTransitionObserver, desktopModeEventLogger)); focusTransitionObserver, desktopModeEventLogger)); } } @WMSingleton @Provides static Optional<SystemModalsTransitionHandler> provideSystemModalsTransitionHandler( Context context, @ShellMainThread ShellExecutor mainExecutor, @ShellAnimationThread ShellExecutor animExecutor, ShellInit shellInit, Transitions transitions, @DynamicOverride DesktopRepository desktopRepository) { if (!DesktopModeStatus.canEnterDesktopMode(context) || !ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() || !Flags.enableDesktopSystemDialogsTransitions()) { return Optional.empty(); } return Optional.of( new SystemModalsTransitionHandler( context, mainExecutor, animExecutor, shellInit, transitions, desktopRepository)); } @WMSingleton @WMSingleton @Provides @Provides static EnterDesktopTaskTransitionHandler provideEnterDesktopModeTaskTransitionHandler( static EnterDesktopTaskTransitionHandler provideEnterDesktopModeTaskTransitionHandler( Loading Loading @@ -1262,7 +1284,8 @@ public abstract class WMShellModule { @NonNull LetterboxCommandHandler letterboxCommandHandler, @NonNull LetterboxCommandHandler letterboxCommandHandler, Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional, Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional, Optional<DesktopDisplayEventHandler> desktopDisplayEventHandler, Optional<DesktopDisplayEventHandler> desktopDisplayEventHandler, Optional<DesktopModeKeyGestureHandler> desktopModeKeyGestureHandler) { Optional<DesktopModeKeyGestureHandler> desktopModeKeyGestureHandler, Optional<SystemModalsTransitionHandler> systemModalsTransitionHandler) { return new Object(); return new Object(); } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt 0 → 100644 +174 −0 Original line number Original line Diff line number Diff line /* * 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.wm.shell.desktopmode.compatui import android.animation.ValueAnimator import android.content.Context import android.os.IBinder import android.view.Display.DEFAULT_DISPLAY import android.view.SurfaceControl import android.window.TransitionInfo import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction import androidx.core.animation.addListener import com.android.app.animation.Interpolators import com.android.internal.protolog.ProtoLog import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.TransitionUtil.isClosingMode import com.android.wm.shell.shared.TransitionUtil.isClosingType import com.android.wm.shell.shared.TransitionUtil.isOpeningMode import com.android.wm.shell.shared.TransitionUtil.isOpeningType import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TransitionHandler /** Handles transitions related to system modals, e.g. launch and close transitions. */ class SystemModalsTransitionHandler( private val context: Context, private val mainExecutor: ShellExecutor, private val animExecutor: ShellExecutor, private val shellInit: ShellInit, private val transitions: Transitions, private val desktopRepository: DesktopRepository, ) : TransitionHandler { private val showingSystemModalsIds = mutableSetOf<Int>() init { shellInit.addInitCallback({ transitions.addHandler(this) }, this) } override fun startAnimation( transition: IBinder, info: TransitionInfo, startTransaction: SurfaceControl.Transaction, finishTransaction: SurfaceControl.Transaction, finishCallback: Transitions.TransitionFinishCallback, ): Boolean { if (!isDesktopModeShowing(DEFAULT_DISPLAY)) return false if (isOpeningType(info.type)) { val launchChange = getLaunchingSystemModal(info) ?: return false val taskInfo = launchChange.taskInfo requireNotNull(taskInfo) logV("Animating system modal launch: taskId=%d", taskInfo.taskId) showingSystemModalsIds.add(taskInfo.taskId) animateSystemModal( launchChange.leash, startTransaction, finishTransaction, finishCallback, /* toShow= */ true, ) return true } if (isClosingType(info.type)) { val closeChange = getClosingSystemModal(info) ?: return false val taskInfo = closeChange.taskInfo requireNotNull(taskInfo) logV("Animating system modal close: taskId=%d", taskInfo.taskId) showingSystemModalsIds.remove(taskInfo.taskId) animateSystemModal( closeChange.leash, startTransaction, finishTransaction, finishCallback, /* toShow= */ false, ) return true } return false } private fun animateSystemModal( leash: SurfaceControl, startTransaction: SurfaceControl.Transaction, finishTransaction: SurfaceControl.Transaction, finishCallback: Transitions.TransitionFinishCallback, toShow: Boolean, // Whether to show or to hide the system modal ) { val startAlpha = if (toShow) 0f else 1f val endAlpha = if (toShow) 1f else 0f val animator = createAlphaAnimator(SurfaceControl.Transaction(), leash, startAlpha, endAlpha) animator.addListener( onEnd = { _ -> mainExecutor.execute { finishCallback.onTransitionFinished(/* wct= */ null) } } ) if (toShow) { finishTransaction.show(leash) } else { finishTransaction.hide(leash) } startTransaction.setAlpha(leash, startAlpha) startTransaction.apply() animExecutor.execute { animator.start() } } private fun getLaunchingSystemModal(info: TransitionInfo): TransitionInfo.Change? = info.changes.find { change -> if (!isOpeningMode(change.mode)) { return@find false } val taskInfo = change.taskInfo ?: return@find false return@find isTopActivityExemptFromDesktopWindowing(context, taskInfo) } private fun getClosingSystemModal(info: TransitionInfo): TransitionInfo.Change? = info.changes.find { change -> if (!isClosingMode(change.mode)) { return@find false } val taskInfo = change.taskInfo ?: return@find false return@find isTopActivityExemptFromDesktopWindowing(context, taskInfo) || showingSystemModalsIds.contains(taskInfo.taskId) } private fun createAlphaAnimator( transaction: SurfaceControl.Transaction, leash: SurfaceControl, startVal: Float, endVal: Float, ): ValueAnimator = ValueAnimator.ofFloat(startVal, endVal).apply { duration = LAUNCH_ANIM_ALPHA_DURATION_MS interpolator = Interpolators.LINEAR addUpdateListener { animation -> transaction.setAlpha(leash, animation.animatedValue as Float).apply() } } private fun isDesktopModeShowing(displayId: Int): Boolean = desktopRepository.getVisibleTaskCount(displayId) > 0 override fun handleRequest( transition: IBinder, request: TransitionRequestInfo, ): WindowContainerTransaction? = null private fun logV(msg: String, vararg arguments: Any?) { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } companion object { private const val TAG = "SystemModalsTransitionHandler" private const val LAUNCH_ANIM_ALPHA_DURATION_MS = 150L } }
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt +19 −3 Original line number Original line Diff line number Diff line Loading @@ -46,17 +46,23 @@ class DesktopTestHelpers { .build() .build() } } /** Create a task that has windowing mode set to [WINDOWING_MODE_FULLSCREEN] */ /** Create a task builder that has windowing mode set to [WINDOWING_MODE_FULLSCREEN] */ @JvmStatic @JvmStatic @JvmOverloads @JvmOverloads fun createFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { fun createFullscreenTaskBuilder(displayId: Int = DEFAULT_DISPLAY): TestRunningTaskInfoBuilder { return TestRunningTaskInfoBuilder() return TestRunningTaskInfoBuilder() .setDisplayId(displayId) .setDisplayId(displayId) .setToken(MockToken().token()) .setToken(MockToken().token()) .setActivityType(ACTIVITY_TYPE_STANDARD) .setActivityType(ACTIVITY_TYPE_STANDARD) .setWindowingMode(WINDOWING_MODE_FULLSCREEN) .setWindowingMode(WINDOWING_MODE_FULLSCREEN) .setLastActiveTime(100) .setLastActiveTime(100) .build() } /** Create a task that has windowing mode set to [WINDOWING_MODE_FULLSCREEN] */ @JvmStatic @JvmOverloads fun createFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { return createFullscreenTaskBuilder(displayId).build() } } /** Create a task that has windowing mode set to [WINDOWING_MODE_MULTI_WINDOW] */ /** Create a task that has windowing mode set to [WINDOWING_MODE_MULTI_WINDOW] */ Loading Loading @@ -84,5 +90,15 @@ class DesktopTestHelpers { .setLastActiveTime(100) .setLastActiveTime(100) .build() .build() } } /** Create a new System Modal task, i.e. a task with a single transparent activity. */ @JvmStatic @JvmOverloads fun createSystemModalTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { return createFullscreenTaskBuilder(displayId) .setTopActivityTransparent(true) .setNumActivities(1) .build() } } } } } No newline at end of file
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt 0 → 100644 +157 −0 Original line number Original line Diff line number Diff line /* * 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.wm.shell.desktopmode.compatui import android.os.Binder import android.testing.AndroidTestingRunner import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_OPEN import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTaskBuilder import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSystemModalTask import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.TransitionInfoBuilder import com.android.wm.shell.transition.Transitions import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidTestingRunner::class) class SystemModalsTransitionHandlerTest : ShellTestCase() { private val mainExecutor = mock<ShellExecutor>() private val animExecutor = mock<ShellExecutor>() private val shellInit = mock<ShellInit>() private val transitions = mock<Transitions>() private val desktopRepository = mock<DesktopRepository>() private val startT = mock<SurfaceControl.Transaction>() private val finishT = mock<SurfaceControl.Transaction>() private lateinit var transitionHandler: SystemModalsTransitionHandler @Before fun setUp() { // Simulate having one Desktop task so that we see Desktop Mode as active whenever(desktopRepository.getVisibleTaskCount(anyInt())).thenReturn(1) transitionHandler = createTransitionHandler() } private fun createTransitionHandler() = SystemModalsTransitionHandler( context, mainExecutor, animExecutor, shellInit, transitions, desktopRepository, ) @Test fun instantiate_addsInitCallback() { verify(shellInit).addInitCallback(any(), any<SystemModalsTransitionHandler>()) } @Test fun startAnimation_desktopNotActive_doesNotAnimate() { whenever(desktopRepository.getVisibleTaskCount(anyInt())).thenReturn(1) val info = TransitionInfoBuilder(TRANSIT_OPEN) .addChange(TRANSIT_OPEN, createSystemModalTask()) .build() assertThat(transitionHandler.startAnimation(Binder(), info, startT, finishT) {}).isTrue() } @Test fun startAnimation_launchingSystemModal_animates() { val info = TransitionInfoBuilder(TRANSIT_OPEN) .addChange(TRANSIT_OPEN, createSystemModalTask()) .build() assertThat(transitionHandler.startAnimation(Binder(), info, startT, finishT) {}).isTrue() } @Test fun startAnimation_nonLaunchingSystemModal_doesNotAnimate() { val info = TransitionInfoBuilder(TRANSIT_OPEN) .addChange(TRANSIT_CHANGE, createSystemModalTask()) .build() assertThat(transitionHandler.startAnimation(Binder(), info, startT, finishT) {}).isFalse() } @Test fun startAnimation_launchingFullscreenTask_doesNotAnimate() { val info = TransitionInfoBuilder(TRANSIT_OPEN) .addChange(TRANSIT_OPEN, createFullscreenTask()) .build() assertThat(transitionHandler.startAnimation(Binder(), info, startT, finishT) {}).isFalse() } @Test fun startAnimation_closingSystemModal_animates() { val info = TransitionInfoBuilder(TRANSIT_CLOSE) .addChange(TRANSIT_CLOSE, createSystemModalTask()) .build() assertThat(transitionHandler.startAnimation(Binder(), info, startT, finishT) {}).isTrue() } @Test fun startAnimation_closingFullscreenTask_doesNotAnimate() { val info = TransitionInfoBuilder(TRANSIT_CLOSE) .addChange(TRANSIT_CLOSE, createFullscreenTask()) .build() assertThat(transitionHandler.startAnimation(Binder(), info, startT, finishT) {}).isFalse() } @Test fun startAnimation_closingPreviouslyLaunchedSystemModal_animates() { val systemModalTask = createSystemModalTask() val nonModalSystemModalTask = createFullscreenTaskBuilder().setTaskId(systemModalTask.taskId).build() val launchInfo = TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_OPEN, systemModalTask).build() transitionHandler.startAnimation(Binder(), launchInfo, startT, finishT) {} val closeInfo = TransitionInfoBuilder(TRANSIT_CLOSE) .addChange(TRANSIT_CLOSE, nonModalSystemModalTask) .build() assertThat(transitionHandler.startAnimation(Binder(), closeInfo, startT, finishT) {}) .isTrue() } }