Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt +143 −4 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ import com.android.systemui.animation.LaunchableView import com.android.systemui.assist.AssistManager import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.desktop.DesktopFirstRepository import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.kosmos.testScope Loading Loading @@ -118,11 +119,14 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { @Mock private lateinit var perDisplaySysUiStateRepository: PerDisplayRepository<SysUiState> @Mock private lateinit var sysUIState: SysUiState @Mock private lateinit var remoteAnimationAdapter: RemoteAnimationAdapter @Mock private lateinit var mDesktopFirstRepository: DesktopFirstRepository private lateinit var underTest: LegacyActivityStarterInternalImpl private val kosmos = testKosmos() private val mainExecutor = FakeExecutor(FakeSystemClock()) private val shadeAnimationInteractor = ShadeAnimationInteractorLegacyImpl(ShadeAnimationRepository(), FakeShadeRepository()) private val desktopFirstStateForDefaultDisplay = mutableMapOf(DISPLAY_ID to true) private val nonDesktopFirstStateForDefaultDisplay = mutableMapOf(DISPLAY_ID to false) @Before fun setUp() { Loading Loading @@ -158,12 +162,15 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { communalSceneInteractor = communalSceneInteractor, communalSettingsInteractor = communalSettingsInteractor, perDisplaySysUiStateRepository = perDisplaySysUiStateRepository, desktopFirstRepository = mDesktopFirstRepository, ) `when`(userTracker.userHandle).thenReturn(UserHandle.OWNER) `when`(communalSceneInteractor.isCommunalVisible).thenReturn(MutableStateFlow(false)) `when`(communalSceneInteractor.isIdleOnCommunal).thenReturn(MutableStateFlow(false)) `when`(communalSceneInteractor.isLaunchingWidget).thenReturn(MutableStateFlow(false)) `when`(shadeDialogContextInteractor.context).thenReturn(context) `when`(mDesktopFirstRepository.isDisplayDesktopFirst) .thenReturn(nonDesktopFirstStateForDefaultDisplay) } @Test Loading Loading @@ -1003,8 +1010,10 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { Flags.FLAG_SHADE_APP_LAUNCH_ANIMATION_SKIP_IN_DESKTOP, ) @Test fun startActivity_skipAnimInDesktop_flagEnabled_inDesktop_noAnimate() { fun startActivity_skipAnimInDesktop_flagEnabled_inDesktop_desktopFirst_noAnimate() { setupDesktopMode(enabled = true) `when`(mDesktopFirstRepository.isDisplayDesktopFirst) .thenReturn(desktopFirstStateForDefaultDisplay) val (controller, pendingIntent) = setupLaunchWithOccludedKeyguard() underTest.startPendingIntentDismissingKeyguard( Loading @@ -1028,11 +1037,75 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { ) } @EnableFlags( Flags.FLAG_ANIMATION_LIBRARY_SHELL_MIGRATION, Flags.FLAG_SHADE_APP_LAUNCH_ANIMATION_SKIP_IN_DESKTOP, ) @Test fun startActivity_skipAnimInDesktop_flagEnabled_inDesktop_notDesktopFirst_noAnimate() { setupDesktopMode(enabled = true) `when`(mDesktopFirstRepository.isDisplayDesktopFirst) .thenReturn(nonDesktopFirstStateForDefaultDisplay) val (controller, pendingIntent) = setupLaunchWithOccludedKeyguard() underTest.startPendingIntentDismissingKeyguard( intent = pendingIntent, dismissShade = true, animationController = controller, showOverLockscreen = true, skipLockscreenChecks = true, ) mainExecutor.runAllReady() // Verify animate parameter is false verify(activityTransitionAnimator) .startPendingIntentWithAnimation( nullable(ActivityTransitionAnimator.Controller::class.java), eq(kosmos.testScope), /* animate */ eq(false), eq(false), eq(true), any(), ) } @EnableFlags(Flags.FLAG_ANIMATION_LIBRARY_SHELL_MIGRATION) @DisableFlags(Flags.FLAG_SHADE_APP_LAUNCH_ANIMATION_SKIP_IN_DESKTOP) @Test fun startActivity_skipAnimInDesktop_flagDisabled_inDesktop_desktopFirst_doAnimate() { setupDesktopMode(enabled = true) `when`(mDesktopFirstRepository.isDisplayDesktopFirst) .thenReturn(desktopFirstStateForDefaultDisplay) val (controller, pendingIntent) = setupLaunchWithOccludedKeyguard() underTest.startPendingIntentDismissingKeyguard( intent = pendingIntent, dismissShade = true, animationController = controller, showOverLockscreen = true, skipLockscreenChecks = true, ) mainExecutor.runAllReady() // Verify animate parameter is true verify(activityTransitionAnimator) .startPendingIntentWithAnimation( nullable(ActivityTransitionAnimator.Controller::class.java), eq(kosmos.testScope), /* animate */ eq(true), eq(false), eq(true), any(), ) } @EnableFlags(Flags.FLAG_ANIMATION_LIBRARY_SHELL_MIGRATION) @DisableFlags(Flags.FLAG_SHADE_APP_LAUNCH_ANIMATION_SKIP_IN_DESKTOP) @Test fun startActivity_skipAnimInDesktop_flagDisabled_inDesktop_doAnimate() { fun startActivity_skipAnimInDesktop_flagDisabled_inDesktop_noDesktopFirst_doAnimate() { setupDesktopMode(enabled = true) `when`(mDesktopFirstRepository.isDisplayDesktopFirst) .thenReturn(nonDesktopFirstStateForDefaultDisplay) val (controller, pendingIntent) = setupLaunchWithOccludedKeyguard() underTest.startPendingIntentDismissingKeyguard( Loading Loading @@ -1061,8 +1134,72 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { Flags.FLAG_SHADE_APP_LAUNCH_ANIMATION_SKIP_IN_DESKTOP, ) @Test fun startActivity_skipAnimInDesktop_flagEnabled_notInDesktop_doAnimate() { fun startActivity_skipAnimInDesktop_flagEnabled_notInDesktop_desktopFirst_notAnimate() { setupDesktopMode(enabled = false) `when`(mDesktopFirstRepository.isDisplayDesktopFirst) .thenReturn(desktopFirstStateForDefaultDisplay) val (controller, pendingIntent) = setupLaunchWithOccludedKeyguard() underTest.startPendingIntentDismissingKeyguard( intent = pendingIntent, dismissShade = true, animationController = controller, showOverLockscreen = true, skipLockscreenChecks = true, ) mainExecutor.runAllReady() // Verify animate parameter is false verify(activityTransitionAnimator) .startPendingIntentWithAnimation( nullable(ActivityTransitionAnimator.Controller::class.java), eq(kosmos.testScope), /* animate */ eq(false), eq(false), eq(true), any(), ) } @EnableFlags( Flags.FLAG_ANIMATION_LIBRARY_SHELL_MIGRATION, Flags.FLAG_SHADE_APP_LAUNCH_ANIMATION_SKIP_IN_DESKTOP, ) @Test fun startActivity_skipAnimInDesktop_flagEnabled_notInDesktop_notDesktopFirst_doAnimate() { setupDesktopMode(enabled = false) `when`(mDesktopFirstRepository.isDisplayDesktopFirst) .thenReturn(nonDesktopFirstStateForDefaultDisplay) val (controller, pendingIntent) = setupLaunchWithOccludedKeyguard() underTest.startPendingIntentDismissingKeyguard( intent = pendingIntent, dismissShade = true, animationController = controller, showOverLockscreen = true, skipLockscreenChecks = true, ) mainExecutor.runAllReady() // Verify animate parameter is true verify(activityTransitionAnimator) .startPendingIntentWithAnimation( nullable(ActivityTransitionAnimator.Controller::class.java), eq(kosmos.testScope), /* animate */ eq(true), eq(false), eq(true), any(), ) } @EnableFlags(Flags.FLAG_ANIMATION_LIBRARY_SHELL_MIGRATION) @DisableFlags(Flags.FLAG_SHADE_APP_LAUNCH_ANIMATION_SKIP_IN_DESKTOP) @Test fun startActivity_skipAnimInDesktop_flagDisabled_notInDesktop_desktopFirst_doAnimate() { setupDesktopMode(enabled = false) `when`(mDesktopFirstRepository.isDisplayDesktopFirst) .thenReturn(desktopFirstStateForDefaultDisplay) val (controller, pendingIntent) = setupLaunchWithOccludedKeyguard() underTest.startPendingIntentDismissingKeyguard( Loading @@ -1089,8 +1226,10 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { @EnableFlags(Flags.FLAG_ANIMATION_LIBRARY_SHELL_MIGRATION) @DisableFlags(Flags.FLAG_SHADE_APP_LAUNCH_ANIMATION_SKIP_IN_DESKTOP) @Test fun startActivity_skipAnimInDesktop_flagDisabled_notInDesktop_doAnimate() { fun startActivity_skipAnimInDesktop_flagDisabled_notInDesktop_notDesktopFirst_doAnimate() { setupDesktopMode(enabled = false) `when`(mDesktopFirstRepository.isDisplayDesktopFirst) .thenReturn(nonDesktopFirstStateForDefaultDisplay) val (controller, pendingIntent) = setupLaunchWithOccludedKeyguard() underTest.startPendingIntentDismissingKeyguard( Loading packages/SystemUI/src/com/android/systemui/desktop/DesktopFirstRepository.kt 0 → 100644 +48 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.desktop import com.android.systemui.dagger.SysUISingleton import com.android.wm.shell.desktopmode.DesktopMode import com.android.wm.shell.shared.desktopmode.DesktopFirstListener import java.util.Optional import javax.inject.Inject /** * Repository that tracks whether a display is in "Desktop First" mode. * * "Desktop First" mode for a display means that newly opened applications on that particular * display will launch in [WindowConfiguration.WINDOWING_MODE_FREEFORM]. * * This repository listens to changes from [DesktopMode] and maintains the state for each display * ID. */ @SysUISingleton class DesktopFirstRepository @Inject constructor(desktopMode: Optional<DesktopMode>) : DesktopFirstListener { private val _isDisplayDesktopFirst: MutableMap<Int, Boolean> = mutableMapOf() val isDisplayDesktopFirst: Map<Int, Boolean> = _isDisplayDesktopFirst.toMap() init { desktopMode.ifPresent { desktopMode.get().registerDesktopFirstListener(this) } } override fun onStateChanged(displayId: Int, isDesktopFirstEnabled: Boolean) { _isDisplayDesktopFirst[displayId] = isDesktopFirstEnabled } } packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt +31 −21 Original line number Diff line number Diff line Loading @@ -46,6 +46,7 @@ import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.desktop.DesktopFirstRepository import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor Loading Loading @@ -107,20 +108,22 @@ constructor( private val commandQueue: CommandQueue, private val lockScreenUserManager: NotificationLockscreenUserManager, private val perDisplaySysUiStateRepository: PerDisplayRepository<SysUiState>, private val desktopFirstRepository: DesktopFirstRepository, ) : ActivityStarterInternal { private val centralSurfaces: CentralSurfaces? get() = centralSurfacesOptLazy.get().getOrNull() private val context: Context private val currentShadeContext: Context get() = contextInteractor.context private val displayId: Int get() = context.displayId private val currentShadeDisplayId: Int get() = currentShadeContext.displayId private val isInDesktopMode: Boolean get() = ((perDisplaySysUiStateRepository[displayId]?.flags ?: 0) and SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0L private val shadeSysUiState: Long get() = perDisplaySysUiStateRepository[currentShadeDisplayId]?.flags ?: 0 private val isInDesktopModeOnCurrentShadeDisplay: Boolean get() = (shadeSysUiState and SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0L override fun registerTransition( cookie: ActivityTransitionAnimator.TransitionCookie, Loading Loading @@ -228,7 +231,7 @@ constructor( ) intent.sendAndReturnResult( context, currentShadeContext, 0, fillInIntent, null, Loading @@ -253,7 +256,7 @@ constructor( ): Int { return startIntent( createActivityOptions( displayId, currentShadeDisplayId, transition, controllerWithCookie?.transitionCookie, ) Loading @@ -272,7 +275,10 @@ constructor( animationAdapter: RemoteAnimationAdapter? ): Int { return startIntent( CentralSurfaces.getActivityOptions(displayId, animationAdapter) CentralSurfaces.getActivityOptions( currentShadeDisplayId, animationAdapter, ) ) } }, Loading Loading @@ -421,10 +427,10 @@ constructor( result[0] = activityTaskManager.startActivityAsUser( null, context.basePackageName, context.attributionTag, currentShadeContext.basePackageName, currentShadeContext.attributionTag, intent, intent.resolveTypeIfNeeded(context.contentResolver), intent.resolveTypeIfNeeded(currentShadeContext.contentResolver), null, null, 0, Loading @@ -448,7 +454,7 @@ constructor( ) { transition: RemoteTransition? -> startIntent( createActivityOptions( displayId, currentShadeDisplayId, transition, controllerWithCookie?.transitionCookie, ) Loading @@ -460,7 +466,7 @@ constructor( animate, intent.getPackage(), ) { adapter: RemoteAnimationAdapter? -> startIntent(CentralSurfaces.getActivityOptions(displayId, adapter)) startIntent(CentralSurfaces.getActivityOptions(currentShadeDisplayId, adapter)) } } Loading Loading @@ -545,11 +551,11 @@ constructor( animate = animate, showOverLockscreen = showOverLockscreenWhenLocked, ) { transition: RemoteTransition? -> TaskStackBuilder.create(context) TaskStackBuilder.create(currentShadeContext) .addNextIntent(intent) .startActivities( createActivityOptions( displayId, currentShadeDisplayId, transition, controllerWithCookie?.transitionCookie, ), Loading @@ -563,10 +569,10 @@ constructor( intent.getPackage(), showOverLockscreenWhenLocked, ) { adapter: RemoteAnimationAdapter? -> TaskStackBuilder.create(context) TaskStackBuilder.create(currentShadeContext) .addNextIntent(intent) .startActivities( CentralSurfaces.getActivityOptions(displayId, adapter), CentralSurfaces.getActivityOptions(currentShadeDisplayId, adapter), userHandle, ) } Loading Loading @@ -656,7 +662,11 @@ constructor( return false } if (shadeAppLaunchAnimationSkipInDesktop() && isInDesktopMode) { if ( shadeAppLaunchAnimationSkipInDesktop() && (isInDesktopModeOnCurrentShadeDisplay || desktopFirstRepository.isDisplayDesktopFirst[currentShadeDisplayId] == true) ) { return false } Loading Loading @@ -732,7 +742,7 @@ constructor( shadeControllerLazy.get(), notifShadeWindowControllerLazy.get(), commandQueue, displayId, currentShadeDisplayId, isLaunchForActivity, ) } Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt +33 −22 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ import com.android.systemui.communal.domain.interactor.CommunalSettingsInteracto import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.desktop.DesktopFirstRepository import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.model.SysUiState Loading Loading @@ -103,20 +104,22 @@ constructor( private val communalSceneInteractor: CommunalSceneInteractor, private val communalSettingsInteractor: CommunalSettingsInteractor, private val perDisplaySysUiStateRepository: PerDisplayRepository<SysUiState>, private val desktopFirstRepository: DesktopFirstRepository, ) : ActivityStarterInternal { private val centralSurfaces: CentralSurfaces? get() = centralSurfacesOptLazy.get().getOrNull() private val context: Context private val currentShadeContext: Context get() = contextInteractor.context private val displayId: Int get() = context.displayId private val currentShadeDisplayId: Int get() = currentShadeContext.displayId private val isInDesktopMode: Boolean get() = ((perDisplaySysUiStateRepository[displayId]?.flags ?: 0) and SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0L private val shadeSysUiState: Long get() = perDisplaySysUiStateRepository[currentShadeDisplayId]?.flags ?: 0 private val isInDesktopModeOnCurrentShadeDisplay: Boolean get() = (shadeSysUiState and SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0L override fun registerTransition( cookie: ActivityTransitionAnimator.TransitionCookie, Loading Loading @@ -260,10 +263,10 @@ constructor( result[0] = activityTaskManager.startActivityAsUser( null, context.basePackageName, context.attributionTag, currentShadeContext.basePackageName, currentShadeContext.attributionTag, intent, intent.resolveTypeIfNeeded(context.contentResolver), intent.resolveTypeIfNeeded(currentShadeContext.contentResolver), null, null, 0, Loading @@ -287,7 +290,7 @@ constructor( ) { transition: RemoteTransition? -> startIntent( createActivityOptions( displayId, currentShadeDisplayId, transition, controllerWithCookie?.transitionCookie, ) Loading @@ -299,7 +302,7 @@ constructor( animate, intent.getPackage(), ) { adapter: RemoteAnimationAdapter? -> startIntent(CentralSurfaces.getActivityOptions(displayId, adapter)) startIntent(CentralSurfaces.getActivityOptions(currentShadeDisplayId, adapter)) } } Loading Loading @@ -399,7 +402,7 @@ constructor( ) intent.sendAndReturnResult( context, currentShadeContext, 0, fillInIntent, null, Loading @@ -424,7 +427,7 @@ constructor( ): Int { return startIntent( createActivityOptions( displayId, currentShadeDisplayId, transition, controllerWithCookie?.transitionCookie, ) Loading @@ -443,7 +446,10 @@ constructor( animationAdapter: RemoteAnimationAdapter? ): Int { return startIntent( CentralSurfaces.getActivityOptions(displayId, animationAdapter) CentralSurfaces.getActivityOptions( currentShadeDisplayId, animationAdapter, ) ) } }, Loading Loading @@ -540,11 +546,11 @@ constructor( animate = animate, showOverLockscreen = showOverLockscreenWhenLocked, ) { transition: RemoteTransition? -> TaskStackBuilder.create(context) TaskStackBuilder.create(currentShadeContext) .addNextIntent(intent) .startActivities( createActivityOptions( displayId, currentShadeDisplayId, transition, controllerWithCookie?.transitionCookie, ), Loading @@ -558,10 +564,10 @@ constructor( intent.getPackage(), showOverLockscreenWhenLocked, ) { adapter: RemoteAnimationAdapter? -> TaskStackBuilder.create(context) TaskStackBuilder.create(currentShadeContext) .addNextIntent(intent) .startActivities( CentralSurfaces.getActivityOptions(displayId, adapter), CentralSurfaces.getActivityOptions(currentShadeDisplayId, adapter), userHandle, ) } Loading Loading @@ -690,7 +696,7 @@ constructor( shadeControllerLazy.get(), notifShadeWindowControllerLazy.get(), commandQueue, displayId, currentShadeDisplayId, isLaunchForActivity, ) } Loading Loading @@ -767,7 +773,8 @@ constructor( /** Retrieves the current user handle to start the Activity. */ private fun getActivityUserHandle(intent: Intent): UserHandle { val packages: Array<String> = context.resources.getStringArray(R.array.system_ui_packages) val packages: Array<String> = currentShadeContext.resources.getStringArray(R.array.system_ui_packages) for (pkg in packages) { val componentName = intent.component ?: break if (pkg == componentName.packageName) { Loading @@ -792,7 +799,11 @@ constructor( return false } if (shadeAppLaunchAnimationSkipInDesktop() && isInDesktopMode) { if ( shadeAppLaunchAnimationSkipInDesktop() && (isInDesktopModeOnCurrentShadeDisplay || desktopFirstRepository.isDisplayDesktopFirst[currentShadeDisplayId] == true) ) { return false } Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt +143 −4 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ import com.android.systemui.animation.LaunchableView import com.android.systemui.assist.AssistManager import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.desktop.DesktopFirstRepository import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.kosmos.testScope Loading Loading @@ -118,11 +119,14 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { @Mock private lateinit var perDisplaySysUiStateRepository: PerDisplayRepository<SysUiState> @Mock private lateinit var sysUIState: SysUiState @Mock private lateinit var remoteAnimationAdapter: RemoteAnimationAdapter @Mock private lateinit var mDesktopFirstRepository: DesktopFirstRepository private lateinit var underTest: LegacyActivityStarterInternalImpl private val kosmos = testKosmos() private val mainExecutor = FakeExecutor(FakeSystemClock()) private val shadeAnimationInteractor = ShadeAnimationInteractorLegacyImpl(ShadeAnimationRepository(), FakeShadeRepository()) private val desktopFirstStateForDefaultDisplay = mutableMapOf(DISPLAY_ID to true) private val nonDesktopFirstStateForDefaultDisplay = mutableMapOf(DISPLAY_ID to false) @Before fun setUp() { Loading Loading @@ -158,12 +162,15 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { communalSceneInteractor = communalSceneInteractor, communalSettingsInteractor = communalSettingsInteractor, perDisplaySysUiStateRepository = perDisplaySysUiStateRepository, desktopFirstRepository = mDesktopFirstRepository, ) `when`(userTracker.userHandle).thenReturn(UserHandle.OWNER) `when`(communalSceneInteractor.isCommunalVisible).thenReturn(MutableStateFlow(false)) `when`(communalSceneInteractor.isIdleOnCommunal).thenReturn(MutableStateFlow(false)) `when`(communalSceneInteractor.isLaunchingWidget).thenReturn(MutableStateFlow(false)) `when`(shadeDialogContextInteractor.context).thenReturn(context) `when`(mDesktopFirstRepository.isDisplayDesktopFirst) .thenReturn(nonDesktopFirstStateForDefaultDisplay) } @Test Loading Loading @@ -1003,8 +1010,10 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { Flags.FLAG_SHADE_APP_LAUNCH_ANIMATION_SKIP_IN_DESKTOP, ) @Test fun startActivity_skipAnimInDesktop_flagEnabled_inDesktop_noAnimate() { fun startActivity_skipAnimInDesktop_flagEnabled_inDesktop_desktopFirst_noAnimate() { setupDesktopMode(enabled = true) `when`(mDesktopFirstRepository.isDisplayDesktopFirst) .thenReturn(desktopFirstStateForDefaultDisplay) val (controller, pendingIntent) = setupLaunchWithOccludedKeyguard() underTest.startPendingIntentDismissingKeyguard( Loading @@ -1028,11 +1037,75 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { ) } @EnableFlags( Flags.FLAG_ANIMATION_LIBRARY_SHELL_MIGRATION, Flags.FLAG_SHADE_APP_LAUNCH_ANIMATION_SKIP_IN_DESKTOP, ) @Test fun startActivity_skipAnimInDesktop_flagEnabled_inDesktop_notDesktopFirst_noAnimate() { setupDesktopMode(enabled = true) `when`(mDesktopFirstRepository.isDisplayDesktopFirst) .thenReturn(nonDesktopFirstStateForDefaultDisplay) val (controller, pendingIntent) = setupLaunchWithOccludedKeyguard() underTest.startPendingIntentDismissingKeyguard( intent = pendingIntent, dismissShade = true, animationController = controller, showOverLockscreen = true, skipLockscreenChecks = true, ) mainExecutor.runAllReady() // Verify animate parameter is false verify(activityTransitionAnimator) .startPendingIntentWithAnimation( nullable(ActivityTransitionAnimator.Controller::class.java), eq(kosmos.testScope), /* animate */ eq(false), eq(false), eq(true), any(), ) } @EnableFlags(Flags.FLAG_ANIMATION_LIBRARY_SHELL_MIGRATION) @DisableFlags(Flags.FLAG_SHADE_APP_LAUNCH_ANIMATION_SKIP_IN_DESKTOP) @Test fun startActivity_skipAnimInDesktop_flagDisabled_inDesktop_desktopFirst_doAnimate() { setupDesktopMode(enabled = true) `when`(mDesktopFirstRepository.isDisplayDesktopFirst) .thenReturn(desktopFirstStateForDefaultDisplay) val (controller, pendingIntent) = setupLaunchWithOccludedKeyguard() underTest.startPendingIntentDismissingKeyguard( intent = pendingIntent, dismissShade = true, animationController = controller, showOverLockscreen = true, skipLockscreenChecks = true, ) mainExecutor.runAllReady() // Verify animate parameter is true verify(activityTransitionAnimator) .startPendingIntentWithAnimation( nullable(ActivityTransitionAnimator.Controller::class.java), eq(kosmos.testScope), /* animate */ eq(true), eq(false), eq(true), any(), ) } @EnableFlags(Flags.FLAG_ANIMATION_LIBRARY_SHELL_MIGRATION) @DisableFlags(Flags.FLAG_SHADE_APP_LAUNCH_ANIMATION_SKIP_IN_DESKTOP) @Test fun startActivity_skipAnimInDesktop_flagDisabled_inDesktop_doAnimate() { fun startActivity_skipAnimInDesktop_flagDisabled_inDesktop_noDesktopFirst_doAnimate() { setupDesktopMode(enabled = true) `when`(mDesktopFirstRepository.isDisplayDesktopFirst) .thenReturn(nonDesktopFirstStateForDefaultDisplay) val (controller, pendingIntent) = setupLaunchWithOccludedKeyguard() underTest.startPendingIntentDismissingKeyguard( Loading Loading @@ -1061,8 +1134,72 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { Flags.FLAG_SHADE_APP_LAUNCH_ANIMATION_SKIP_IN_DESKTOP, ) @Test fun startActivity_skipAnimInDesktop_flagEnabled_notInDesktop_doAnimate() { fun startActivity_skipAnimInDesktop_flagEnabled_notInDesktop_desktopFirst_notAnimate() { setupDesktopMode(enabled = false) `when`(mDesktopFirstRepository.isDisplayDesktopFirst) .thenReturn(desktopFirstStateForDefaultDisplay) val (controller, pendingIntent) = setupLaunchWithOccludedKeyguard() underTest.startPendingIntentDismissingKeyguard( intent = pendingIntent, dismissShade = true, animationController = controller, showOverLockscreen = true, skipLockscreenChecks = true, ) mainExecutor.runAllReady() // Verify animate parameter is false verify(activityTransitionAnimator) .startPendingIntentWithAnimation( nullable(ActivityTransitionAnimator.Controller::class.java), eq(kosmos.testScope), /* animate */ eq(false), eq(false), eq(true), any(), ) } @EnableFlags( Flags.FLAG_ANIMATION_LIBRARY_SHELL_MIGRATION, Flags.FLAG_SHADE_APP_LAUNCH_ANIMATION_SKIP_IN_DESKTOP, ) @Test fun startActivity_skipAnimInDesktop_flagEnabled_notInDesktop_notDesktopFirst_doAnimate() { setupDesktopMode(enabled = false) `when`(mDesktopFirstRepository.isDisplayDesktopFirst) .thenReturn(nonDesktopFirstStateForDefaultDisplay) val (controller, pendingIntent) = setupLaunchWithOccludedKeyguard() underTest.startPendingIntentDismissingKeyguard( intent = pendingIntent, dismissShade = true, animationController = controller, showOverLockscreen = true, skipLockscreenChecks = true, ) mainExecutor.runAllReady() // Verify animate parameter is true verify(activityTransitionAnimator) .startPendingIntentWithAnimation( nullable(ActivityTransitionAnimator.Controller::class.java), eq(kosmos.testScope), /* animate */ eq(true), eq(false), eq(true), any(), ) } @EnableFlags(Flags.FLAG_ANIMATION_LIBRARY_SHELL_MIGRATION) @DisableFlags(Flags.FLAG_SHADE_APP_LAUNCH_ANIMATION_SKIP_IN_DESKTOP) @Test fun startActivity_skipAnimInDesktop_flagDisabled_notInDesktop_desktopFirst_doAnimate() { setupDesktopMode(enabled = false) `when`(mDesktopFirstRepository.isDisplayDesktopFirst) .thenReturn(desktopFirstStateForDefaultDisplay) val (controller, pendingIntent) = setupLaunchWithOccludedKeyguard() underTest.startPendingIntentDismissingKeyguard( Loading @@ -1089,8 +1226,10 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { @EnableFlags(Flags.FLAG_ANIMATION_LIBRARY_SHELL_MIGRATION) @DisableFlags(Flags.FLAG_SHADE_APP_LAUNCH_ANIMATION_SKIP_IN_DESKTOP) @Test fun startActivity_skipAnimInDesktop_flagDisabled_notInDesktop_doAnimate() { fun startActivity_skipAnimInDesktop_flagDisabled_notInDesktop_notDesktopFirst_doAnimate() { setupDesktopMode(enabled = false) `when`(mDesktopFirstRepository.isDisplayDesktopFirst) .thenReturn(nonDesktopFirstStateForDefaultDisplay) val (controller, pendingIntent) = setupLaunchWithOccludedKeyguard() underTest.startPendingIntentDismissingKeyguard( Loading
packages/SystemUI/src/com/android/systemui/desktop/DesktopFirstRepository.kt 0 → 100644 +48 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.desktop import com.android.systemui.dagger.SysUISingleton import com.android.wm.shell.desktopmode.DesktopMode import com.android.wm.shell.shared.desktopmode.DesktopFirstListener import java.util.Optional import javax.inject.Inject /** * Repository that tracks whether a display is in "Desktop First" mode. * * "Desktop First" mode for a display means that newly opened applications on that particular * display will launch in [WindowConfiguration.WINDOWING_MODE_FREEFORM]. * * This repository listens to changes from [DesktopMode] and maintains the state for each display * ID. */ @SysUISingleton class DesktopFirstRepository @Inject constructor(desktopMode: Optional<DesktopMode>) : DesktopFirstListener { private val _isDisplayDesktopFirst: MutableMap<Int, Boolean> = mutableMapOf() val isDisplayDesktopFirst: Map<Int, Boolean> = _isDisplayDesktopFirst.toMap() init { desktopMode.ifPresent { desktopMode.get().registerDesktopFirstListener(this) } } override fun onStateChanged(displayId: Int, isDesktopFirstEnabled: Boolean) { _isDisplayDesktopFirst[displayId] = isDesktopFirstEnabled } }
packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt +31 −21 Original line number Diff line number Diff line Loading @@ -46,6 +46,7 @@ import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.desktop.DesktopFirstRepository import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor Loading Loading @@ -107,20 +108,22 @@ constructor( private val commandQueue: CommandQueue, private val lockScreenUserManager: NotificationLockscreenUserManager, private val perDisplaySysUiStateRepository: PerDisplayRepository<SysUiState>, private val desktopFirstRepository: DesktopFirstRepository, ) : ActivityStarterInternal { private val centralSurfaces: CentralSurfaces? get() = centralSurfacesOptLazy.get().getOrNull() private val context: Context private val currentShadeContext: Context get() = contextInteractor.context private val displayId: Int get() = context.displayId private val currentShadeDisplayId: Int get() = currentShadeContext.displayId private val isInDesktopMode: Boolean get() = ((perDisplaySysUiStateRepository[displayId]?.flags ?: 0) and SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0L private val shadeSysUiState: Long get() = perDisplaySysUiStateRepository[currentShadeDisplayId]?.flags ?: 0 private val isInDesktopModeOnCurrentShadeDisplay: Boolean get() = (shadeSysUiState and SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0L override fun registerTransition( cookie: ActivityTransitionAnimator.TransitionCookie, Loading Loading @@ -228,7 +231,7 @@ constructor( ) intent.sendAndReturnResult( context, currentShadeContext, 0, fillInIntent, null, Loading @@ -253,7 +256,7 @@ constructor( ): Int { return startIntent( createActivityOptions( displayId, currentShadeDisplayId, transition, controllerWithCookie?.transitionCookie, ) Loading @@ -272,7 +275,10 @@ constructor( animationAdapter: RemoteAnimationAdapter? ): Int { return startIntent( CentralSurfaces.getActivityOptions(displayId, animationAdapter) CentralSurfaces.getActivityOptions( currentShadeDisplayId, animationAdapter, ) ) } }, Loading Loading @@ -421,10 +427,10 @@ constructor( result[0] = activityTaskManager.startActivityAsUser( null, context.basePackageName, context.attributionTag, currentShadeContext.basePackageName, currentShadeContext.attributionTag, intent, intent.resolveTypeIfNeeded(context.contentResolver), intent.resolveTypeIfNeeded(currentShadeContext.contentResolver), null, null, 0, Loading @@ -448,7 +454,7 @@ constructor( ) { transition: RemoteTransition? -> startIntent( createActivityOptions( displayId, currentShadeDisplayId, transition, controllerWithCookie?.transitionCookie, ) Loading @@ -460,7 +466,7 @@ constructor( animate, intent.getPackage(), ) { adapter: RemoteAnimationAdapter? -> startIntent(CentralSurfaces.getActivityOptions(displayId, adapter)) startIntent(CentralSurfaces.getActivityOptions(currentShadeDisplayId, adapter)) } } Loading Loading @@ -545,11 +551,11 @@ constructor( animate = animate, showOverLockscreen = showOverLockscreenWhenLocked, ) { transition: RemoteTransition? -> TaskStackBuilder.create(context) TaskStackBuilder.create(currentShadeContext) .addNextIntent(intent) .startActivities( createActivityOptions( displayId, currentShadeDisplayId, transition, controllerWithCookie?.transitionCookie, ), Loading @@ -563,10 +569,10 @@ constructor( intent.getPackage(), showOverLockscreenWhenLocked, ) { adapter: RemoteAnimationAdapter? -> TaskStackBuilder.create(context) TaskStackBuilder.create(currentShadeContext) .addNextIntent(intent) .startActivities( CentralSurfaces.getActivityOptions(displayId, adapter), CentralSurfaces.getActivityOptions(currentShadeDisplayId, adapter), userHandle, ) } Loading Loading @@ -656,7 +662,11 @@ constructor( return false } if (shadeAppLaunchAnimationSkipInDesktop() && isInDesktopMode) { if ( shadeAppLaunchAnimationSkipInDesktop() && (isInDesktopModeOnCurrentShadeDisplay || desktopFirstRepository.isDisplayDesktopFirst[currentShadeDisplayId] == true) ) { return false } Loading Loading @@ -732,7 +742,7 @@ constructor( shadeControllerLazy.get(), notifShadeWindowControllerLazy.get(), commandQueue, displayId, currentShadeDisplayId, isLaunchForActivity, ) } Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt +33 −22 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ import com.android.systemui.communal.domain.interactor.CommunalSettingsInteracto import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.desktop.DesktopFirstRepository import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.model.SysUiState Loading Loading @@ -103,20 +104,22 @@ constructor( private val communalSceneInteractor: CommunalSceneInteractor, private val communalSettingsInteractor: CommunalSettingsInteractor, private val perDisplaySysUiStateRepository: PerDisplayRepository<SysUiState>, private val desktopFirstRepository: DesktopFirstRepository, ) : ActivityStarterInternal { private val centralSurfaces: CentralSurfaces? get() = centralSurfacesOptLazy.get().getOrNull() private val context: Context private val currentShadeContext: Context get() = contextInteractor.context private val displayId: Int get() = context.displayId private val currentShadeDisplayId: Int get() = currentShadeContext.displayId private val isInDesktopMode: Boolean get() = ((perDisplaySysUiStateRepository[displayId]?.flags ?: 0) and SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0L private val shadeSysUiState: Long get() = perDisplaySysUiStateRepository[currentShadeDisplayId]?.flags ?: 0 private val isInDesktopModeOnCurrentShadeDisplay: Boolean get() = (shadeSysUiState and SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0L override fun registerTransition( cookie: ActivityTransitionAnimator.TransitionCookie, Loading Loading @@ -260,10 +263,10 @@ constructor( result[0] = activityTaskManager.startActivityAsUser( null, context.basePackageName, context.attributionTag, currentShadeContext.basePackageName, currentShadeContext.attributionTag, intent, intent.resolveTypeIfNeeded(context.contentResolver), intent.resolveTypeIfNeeded(currentShadeContext.contentResolver), null, null, 0, Loading @@ -287,7 +290,7 @@ constructor( ) { transition: RemoteTransition? -> startIntent( createActivityOptions( displayId, currentShadeDisplayId, transition, controllerWithCookie?.transitionCookie, ) Loading @@ -299,7 +302,7 @@ constructor( animate, intent.getPackage(), ) { adapter: RemoteAnimationAdapter? -> startIntent(CentralSurfaces.getActivityOptions(displayId, adapter)) startIntent(CentralSurfaces.getActivityOptions(currentShadeDisplayId, adapter)) } } Loading Loading @@ -399,7 +402,7 @@ constructor( ) intent.sendAndReturnResult( context, currentShadeContext, 0, fillInIntent, null, Loading @@ -424,7 +427,7 @@ constructor( ): Int { return startIntent( createActivityOptions( displayId, currentShadeDisplayId, transition, controllerWithCookie?.transitionCookie, ) Loading @@ -443,7 +446,10 @@ constructor( animationAdapter: RemoteAnimationAdapter? ): Int { return startIntent( CentralSurfaces.getActivityOptions(displayId, animationAdapter) CentralSurfaces.getActivityOptions( currentShadeDisplayId, animationAdapter, ) ) } }, Loading Loading @@ -540,11 +546,11 @@ constructor( animate = animate, showOverLockscreen = showOverLockscreenWhenLocked, ) { transition: RemoteTransition? -> TaskStackBuilder.create(context) TaskStackBuilder.create(currentShadeContext) .addNextIntent(intent) .startActivities( createActivityOptions( displayId, currentShadeDisplayId, transition, controllerWithCookie?.transitionCookie, ), Loading @@ -558,10 +564,10 @@ constructor( intent.getPackage(), showOverLockscreenWhenLocked, ) { adapter: RemoteAnimationAdapter? -> TaskStackBuilder.create(context) TaskStackBuilder.create(currentShadeContext) .addNextIntent(intent) .startActivities( CentralSurfaces.getActivityOptions(displayId, adapter), CentralSurfaces.getActivityOptions(currentShadeDisplayId, adapter), userHandle, ) } Loading Loading @@ -690,7 +696,7 @@ constructor( shadeControllerLazy.get(), notifShadeWindowControllerLazy.get(), commandQueue, displayId, currentShadeDisplayId, isLaunchForActivity, ) } Loading Loading @@ -767,7 +773,8 @@ constructor( /** Retrieves the current user handle to start the Activity. */ private fun getActivityUserHandle(intent: Intent): UserHandle { val packages: Array<String> = context.resources.getStringArray(R.array.system_ui_packages) val packages: Array<String> = currentShadeContext.resources.getStringArray(R.array.system_ui_packages) for (pkg in packages) { val componentName = intent.component ?: break if (pkg == componentName.packageName) { Loading @@ -792,7 +799,11 @@ constructor( return false } if (shadeAppLaunchAnimationSkipInDesktop() && isInDesktopMode) { if ( shadeAppLaunchAnimationSkipInDesktop() && (isInDesktopModeOnCurrentShadeDisplay || desktopFirstRepository.isDisplayDesktopFirst[currentShadeDisplayId] == true) ) { return false } Loading