Loading packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +4 −0 Original line number Diff line number Diff line Loading @@ -167,6 +167,10 @@ constructor( initialValue = isVisibleInternal() ) /** Whether there's an ongoing remotely-initiated user interaction. */ val isRemoteUserInteractionOngoing: StateFlow<Boolean> = repository.isRemoteUserInteractionOngoing /** * The amount of transition into or out of the given [scene]. * Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java +16 −2 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; import android.view.WindowInsets; import androidx.annotation.VisibleForTesting; import com.android.compose.animation.scene.ObservableTransitionState; import com.android.internal.policy.SystemBarUtils; import com.android.systemui.Dumpable; Loading Loading @@ -69,6 +70,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { private boolean mIsStatusBarExpanded = false; private boolean mIsIdleOnGone = true; private boolean mIsRemoteUserInteractionOngoing = false; private boolean mShouldAdjustInsets = false; private View mNotificationShadeWindowView; private View mNotificationPanelView; Loading Loading @@ -133,6 +135,9 @@ public final class StatusBarTouchableRegionManager implements Dumpable { javaAdapter.alwaysCollectFlow( sceneInteractor.get().getTransitionState(), this::onSceneChanged); javaAdapter.alwaysCollectFlow( sceneInteractor.get().isRemoteUserInteractionOngoing(), this::onRemoteUserInteractionOngoingChanged); } else { javaAdapter.alwaysCollectFlow( shadeInteractor.isAnyExpanded(), Loading Loading @@ -179,6 +184,13 @@ public final class StatusBarTouchableRegionManager implements Dumpable { } } private void onRemoteUserInteractionOngoingChanged(Boolean ongoing) { if (ongoing != mIsRemoteUserInteractionOngoing) { mIsRemoteUserInteractionOngoing = ongoing; updateTouchableRegion(); } } /** * Calculates the touch region needed for heads up notifications, taking into consideration * any existing display cutouts (notch) Loading Loading @@ -276,13 +288,15 @@ public final class StatusBarTouchableRegionManager implements Dumpable { * Helper to let us know when calculating the region is not needed because we know the entire * screen needs to be touchable. */ private boolean shouldMakeEntireScreenTouchable() { @VisibleForTesting boolean shouldMakeEntireScreenTouchable() { // The touchable region is always the full area when expanded, whether we're showing the // shade or the bouncer. It's also fully touchable when the screen off animation is playing // since we don't want stray touches to go through the light reveal scrim to whatever is // underneath. return mIsStatusBarExpanded || (SceneContainerFlag.isEnabled() && !mIsIdleOnGone) || (SceneContainerFlag.isEnabled() && (!mIsIdleOnGone || mIsRemoteUserInteractionOngoing)) || mPrimaryBouncerInteractor.isShowing().getValue() || mAlternateBouncerInteractor.isVisibleState() || mUnlockedScreenOffAnimationController.isAnimationPlaying(); Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt 0 → 100644 +116 −0 Original line number 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.systemui.statusbar.phone import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase import com.android.systemui.flags.DisableSceneContainer import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.testScope import com.android.systemui.scene.data.repository.sceneContainerRepository import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.android.systemui.util.kotlin.getValue import com.google.common.truth.Truth.assertThat import dagger.Lazy import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) @OptIn(ExperimentalCoroutinesApi::class) class StatusBarTouchableRegionManagerTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val sceneRepository = kosmos.sceneContainerRepository private val underTest by Lazy { kosmos.statusBarTouchableRegionManager } @Test @EnableSceneContainer fun entireScreenTouchable_sceneContainerEnabled_isRemoteUserInteractionOngoing() = testScope.runTest { sceneRepository.setTransitionState( flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Gone)) ) runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() sceneRepository.isRemoteUserInteractionOngoing.value = true runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isTrue() sceneRepository.isRemoteUserInteractionOngoing.value = false runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() } @Test @DisableSceneContainer fun entireScreenTouchable_sceneContainerDisabled_isRemoteUserInteractionOngoing() = testScope.runTest { assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() sceneRepository.isRemoteUserInteractionOngoing.value = true runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() } @Test @EnableSceneContainer fun entireScreenTouchable_sceneContainerEnabled_isIdleOnGone() = testScope.runTest { sceneRepository.setTransitionState( flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Gone)) ) runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() sceneRepository.setTransitionState( flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Shade)) ) runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isTrue() sceneRepository.setTransitionState( flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Gone)) ) runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() } @Test @DisableSceneContainer fun entireScreenTouchable_sceneContainerDisabled_isIdleOnGone() = testScope.runTest { assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() sceneRepository.setTransitionState( flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Shade)) ) runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() } } packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerKosmos.kt 0 → 100644 +47 −0 Original line number 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.systemui.statusbar.phone import android.content.applicationContext import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.notificationShadeWindowController import com.android.systemui.statusbar.policy.configurationController import com.android.systemui.statusbar.policy.headsUpManager import com.android.systemui.util.kotlin.JavaAdapter import com.android.systemui.util.mockito.mock import org.mockito.Mockito.mock var Kosmos.statusBarTouchableRegionManager by Kosmos.Fixture { StatusBarTouchableRegionManager( applicationContext, notificationShadeWindowController, configurationController, headsUpManager, shadeInteractor, { sceneInteractor }, JavaAdapter(testScope.backgroundScope), mock<UnlockedScreenOffAnimationController>(), primaryBouncerInteractor, alternateBouncerInteractor, ) } Loading
packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +4 −0 Original line number Diff line number Diff line Loading @@ -167,6 +167,10 @@ constructor( initialValue = isVisibleInternal() ) /** Whether there's an ongoing remotely-initiated user interaction. */ val isRemoteUserInteractionOngoing: StateFlow<Boolean> = repository.isRemoteUserInteractionOngoing /** * The amount of transition into or out of the given [scene]. * Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java +16 −2 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; import android.view.WindowInsets; import androidx.annotation.VisibleForTesting; import com.android.compose.animation.scene.ObservableTransitionState; import com.android.internal.policy.SystemBarUtils; import com.android.systemui.Dumpable; Loading Loading @@ -69,6 +70,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { private boolean mIsStatusBarExpanded = false; private boolean mIsIdleOnGone = true; private boolean mIsRemoteUserInteractionOngoing = false; private boolean mShouldAdjustInsets = false; private View mNotificationShadeWindowView; private View mNotificationPanelView; Loading Loading @@ -133,6 +135,9 @@ public final class StatusBarTouchableRegionManager implements Dumpable { javaAdapter.alwaysCollectFlow( sceneInteractor.get().getTransitionState(), this::onSceneChanged); javaAdapter.alwaysCollectFlow( sceneInteractor.get().isRemoteUserInteractionOngoing(), this::onRemoteUserInteractionOngoingChanged); } else { javaAdapter.alwaysCollectFlow( shadeInteractor.isAnyExpanded(), Loading Loading @@ -179,6 +184,13 @@ public final class StatusBarTouchableRegionManager implements Dumpable { } } private void onRemoteUserInteractionOngoingChanged(Boolean ongoing) { if (ongoing != mIsRemoteUserInteractionOngoing) { mIsRemoteUserInteractionOngoing = ongoing; updateTouchableRegion(); } } /** * Calculates the touch region needed for heads up notifications, taking into consideration * any existing display cutouts (notch) Loading Loading @@ -276,13 +288,15 @@ public final class StatusBarTouchableRegionManager implements Dumpable { * Helper to let us know when calculating the region is not needed because we know the entire * screen needs to be touchable. */ private boolean shouldMakeEntireScreenTouchable() { @VisibleForTesting boolean shouldMakeEntireScreenTouchable() { // The touchable region is always the full area when expanded, whether we're showing the // shade or the bouncer. It's also fully touchable when the screen off animation is playing // since we don't want stray touches to go through the light reveal scrim to whatever is // underneath. return mIsStatusBarExpanded || (SceneContainerFlag.isEnabled() && !mIsIdleOnGone) || (SceneContainerFlag.isEnabled() && (!mIsIdleOnGone || mIsRemoteUserInteractionOngoing)) || mPrimaryBouncerInteractor.isShowing().getValue() || mAlternateBouncerInteractor.isVisibleState() || mUnlockedScreenOffAnimationController.isAnimationPlaying(); Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt 0 → 100644 +116 −0 Original line number 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.systemui.statusbar.phone import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase import com.android.systemui.flags.DisableSceneContainer import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.testScope import com.android.systemui.scene.data.repository.sceneContainerRepository import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.android.systemui.util.kotlin.getValue import com.google.common.truth.Truth.assertThat import dagger.Lazy import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) @OptIn(ExperimentalCoroutinesApi::class) class StatusBarTouchableRegionManagerTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val sceneRepository = kosmos.sceneContainerRepository private val underTest by Lazy { kosmos.statusBarTouchableRegionManager } @Test @EnableSceneContainer fun entireScreenTouchable_sceneContainerEnabled_isRemoteUserInteractionOngoing() = testScope.runTest { sceneRepository.setTransitionState( flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Gone)) ) runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() sceneRepository.isRemoteUserInteractionOngoing.value = true runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isTrue() sceneRepository.isRemoteUserInteractionOngoing.value = false runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() } @Test @DisableSceneContainer fun entireScreenTouchable_sceneContainerDisabled_isRemoteUserInteractionOngoing() = testScope.runTest { assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() sceneRepository.isRemoteUserInteractionOngoing.value = true runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() } @Test @EnableSceneContainer fun entireScreenTouchable_sceneContainerEnabled_isIdleOnGone() = testScope.runTest { sceneRepository.setTransitionState( flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Gone)) ) runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() sceneRepository.setTransitionState( flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Shade)) ) runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isTrue() sceneRepository.setTransitionState( flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Gone)) ) runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() } @Test @DisableSceneContainer fun entireScreenTouchable_sceneContainerDisabled_isIdleOnGone() = testScope.runTest { assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() sceneRepository.setTransitionState( flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Shade)) ) runCurrent() assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() } }
packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerKosmos.kt 0 → 100644 +47 −0 Original line number 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.systemui.statusbar.phone import android.content.applicationContext import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.notificationShadeWindowController import com.android.systemui.statusbar.policy.configurationController import com.android.systemui.statusbar.policy.headsUpManager import com.android.systemui.util.kotlin.JavaAdapter import com.android.systemui.util.mockito.mock import org.mockito.Mockito.mock var Kosmos.statusBarTouchableRegionManager by Kosmos.Fixture { StatusBarTouchableRegionManager( applicationContext, notificationShadeWindowController, configurationController, headsUpManager, shadeInteractor, { sceneInteractor }, JavaAdapter(testScope.backgroundScope), mock<UnlockedScreenOffAnimationController>(), primaryBouncerInteractor, alternateBouncerInteractor, ) }