diff options
| author | 2024-12-12 17:28:50 -0800 | |
|---|---|---|
| committer | 2024-12-12 17:28:50 -0800 | |
| commit | 75d3ee72e32f0ff678c12f42035b5a25cacc3a82 (patch) | |
| tree | 3e056a621bb05f48cd298b8bf215a270e3418889 | |
| parent | 744a3f5da9a4ac348a80863a280a2985cae32e1b (diff) | |
| parent | 171d08801f011863d556fccb821940fb833a295e (diff) | |
Merge "Add support for swiping up on the status bar in OngoingCallInteractor" into main
5 files changed, 221 insertions, 51 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt index b19645fadbdf..8fb95e843ec1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository +import com.android.systemui.statusbar.gesture.swipeStatusBarAwayGestureHandler import com.android.systemui.statusbar.notification.data.model.activeNotificationModel import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository @@ -40,9 +41,14 @@ import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.clearInvocations import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify @SmallTest @RunWith(AndroidJUnit4::class) @@ -51,6 +57,11 @@ class OngoingCallInteractorTest : SysuiTestCase() { private val repository = kosmos.activeNotificationListRepository private val underTest = kosmos.ongoingCallInteractor + @Before + fun setUp() { + underTest.start() + } + @Test fun noNotification_emitsNoCall() = runTest { val state by collectLastValue(underTest.ongoingCallState) @@ -210,8 +221,7 @@ class OngoingCallInteractorTest : SysuiTestCase() { @Test fun ongoingCallNotification_setsRequiresStatusBarVisibleTrue() = kosmos.runTest { - val ongoingCallState by collectLastValue(underTest.ongoingCallState) - + val isStatusBarRequired by collectLastValue(underTest.isStatusBarRequiredForOngoingCall) val requiresStatusBarVisibleInRepository by collectLastValue( kosmos.fakeStatusBarModeRepository.defaultDisplay @@ -222,21 +232,9 @@ class OngoingCallInteractorTest : SysuiTestCase() { kosmos.fakeStatusBarWindowControllerStore.defaultDisplay .ongoingProcessRequiresStatusBarVisible ) - repository.activeNotifications.value = - ActiveNotificationsStore.Builder() - .apply { - addIndividualNotif( - activeNotificationModel( - key = "notif1", - whenTime = 1000L, - callType = CallType.Ongoing, - uid = UID, - ) - ) - } - .build() + postOngoingCallNotification() - assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.InCall::class.java) + assertThat(isStatusBarRequired).isTrue() assertThat(requiresStatusBarVisibleInRepository).isTrue() assertThat(requiresStatusBarVisibleInWindowController).isTrue() } @@ -244,8 +242,7 @@ class OngoingCallInteractorTest : SysuiTestCase() { @Test fun notificationRemoved_setsRequiresStatusBarVisibleFalse() = kosmos.runTest { - val ongoingCallState by collectLastValue(underTest.ongoingCallState) - + val isStatusBarRequired by collectLastValue(underTest.isStatusBarRequiredForOngoingCall) val requiresStatusBarVisibleInRepository by collectLastValue( kosmos.fakeStatusBarModeRepository.defaultDisplay @@ -257,23 +254,11 @@ class OngoingCallInteractorTest : SysuiTestCase() { .ongoingProcessRequiresStatusBarVisible ) - repository.activeNotifications.value = - ActiveNotificationsStore.Builder() - .apply { - addIndividualNotif( - activeNotificationModel( - key = "notif1", - whenTime = 1000L, - callType = CallType.Ongoing, - uid = UID, - ) - ) - } - .build() + postOngoingCallNotification() repository.activeNotifications.value = ActiveNotificationsStore() - assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.NoCall::class.java) + assertThat(isStatusBarRequired).isFalse() assertThat(requiresStatusBarVisibleInRepository).isFalse() assertThat(requiresStatusBarVisibleInWindowController).isFalse() } @@ -295,19 +280,8 @@ class OngoingCallInteractorTest : SysuiTestCase() { ) kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = false - repository.activeNotifications.value = - ActiveNotificationsStore.Builder() - .apply { - addIndividualNotif( - activeNotificationModel( - key = "notif1", - whenTime = 1000L, - callType = CallType.Ongoing, - uid = UID, - ) - ) - } - .build() + + postOngoingCallNotification() assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.InCall::class.java) assertThat(requiresStatusBarVisibleInRepository).isTrue() @@ -321,6 +295,99 @@ class OngoingCallInteractorTest : SysuiTestCase() { assertThat(requiresStatusBarVisibleInWindowController).isFalse() } + @Test + fun gestureHandler_inCall_notFullscreen_doesNotListen() = + kosmos.runTest { + val ongoingCallState by collectLastValue(underTest.ongoingCallState) + + clearInvocations(kosmos.swipeStatusBarAwayGestureHandler) + // Set up notification but not in fullscreen + kosmos.fakeStatusBarModeRepository.defaultDisplay.isInFullscreenMode.value = false + postOngoingCallNotification() + + assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.InCall::class.java) + verify(kosmos.swipeStatusBarAwayGestureHandler, never()) + .addOnGestureDetectedCallback(any(), any()) + } + + @Test + fun gestureHandler_inCall_fullscreen_addsListener() = + kosmos.runTest { + val isGestureListeningEnabled by collectLastValue(underTest.isGestureListeningEnabled) + + // Set up notification and fullscreen mode + kosmos.fakeStatusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true + postOngoingCallNotification() + + assertThat(isGestureListeningEnabled).isTrue() + verify(kosmos.swipeStatusBarAwayGestureHandler) + .addOnGestureDetectedCallback(any(), any()) + } + + @Test + fun gestureHandler_inCall_fullscreen_chipSwiped_removesListener() = + kosmos.runTest { + val swipeAwayState by collectLastValue(underTest.isChipSwipedAway) + + // Set up notification and fullscreen mode + kosmos.fakeStatusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true + postOngoingCallNotification() + + clearInvocations(kosmos.swipeStatusBarAwayGestureHandler) + + underTest.onStatusBarSwiped() + + assertThat(swipeAwayState).isTrue() + verify(kosmos.swipeStatusBarAwayGestureHandler).removeOnGestureDetectedCallback(any()) + } + + @Test + fun chipSwipedAway_setsRequiresStatusBarVisibleFalse() = + kosmos.runTest { + val isStatusBarRequiredForOngoingCall by + collectLastValue(underTest.isStatusBarRequiredForOngoingCall) + val requiresStatusBarVisibleInRepository by + collectLastValue( + kosmos.fakeStatusBarModeRepository.defaultDisplay + .ongoingProcessRequiresStatusBarVisible + ) + val requiresStatusBarVisibleInWindowController by + collectLastValue( + kosmos.fakeStatusBarWindowControllerStore.defaultDisplay + .ongoingProcessRequiresStatusBarVisible + ) + + // Start with an ongoing call (which should set status bar required) + postOngoingCallNotification() + + assertThat(isStatusBarRequiredForOngoingCall).isTrue() + assertThat(requiresStatusBarVisibleInRepository).isTrue() + assertThat(requiresStatusBarVisibleInWindowController).isTrue() + + // Swipe away the chip + underTest.onStatusBarSwiped() + + // Verify status bar is no longer required + assertThat(requiresStatusBarVisibleInRepository).isFalse() + assertThat(requiresStatusBarVisibleInWindowController).isFalse() + } + + private fun postOngoingCallNotification() { + repository.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { + addIndividualNotif( + activeNotificationModel( + key = "notif1", + whenTime = 1000L, + callType = CallType.Ongoing, + uid = UID, + ) + ) + } + .build() + } + companion object { private const val UID = 885 } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt index 2588c7ae2363..46c84fbc19aa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt @@ -37,6 +37,7 @@ import com.android.systemui.statusbar.phone.StatusBarSignalPolicy import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization +import com.android.systemui.statusbar.phone.ongoingcall.domain.interactor.OngoingCallInteractor import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl import com.android.systemui.statusbar.window.MultiDisplayStatusBarWindowControllerStore @@ -101,6 +102,19 @@ interface StatusBarModule { @Provides @SysUISingleton + @IntoMap + @ClassKey(OngoingCallInteractor::class) + fun ongoingCallInteractor( + interactor: OngoingCallInteractor + ): CoreStartable = + if (StatusBarChipsModernization.isEnabled) { + interactor + } else { + CoreStartable.NOP + } + + @Provides + @SysUISingleton fun lightBarController(store: LightBarControllerStore): LightBarController { return store.defaultDisplay } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt index b7ccfa01c92c..2f7b24393ae0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt @@ -16,12 +16,15 @@ package com.android.systemui.statusbar.phone.ongoingcall.domain.interactor +import androidx.annotation.VisibleForTesting +import com.android.systemui.CoreStartable import com.android.systemui.activity.data.repository.ActivityManagerRepository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore +import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog @@ -31,11 +34,15 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn @@ -55,12 +62,19 @@ class OngoingCallInteractor @Inject constructor( private val activityManagerRepository: ActivityManagerRepository, private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore, private val statusBarWindowControllerStore: StatusBarWindowControllerStore, + private val swipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler, activeNotificationsInteractor: ActiveNotificationsInteractor, @OngoingCallLog private val logBuffer: LogBuffer, -) { +) : CoreStartable { private val logger = Logger(logBuffer, TAG) /** + * Tracks whether the call chip has been swiped away. + */ + private val _isChipSwipedAway = MutableStateFlow(false) + val isChipSwipedAway: StateFlow<Boolean> = _isChipSwipedAway.asStateFlow() + + /** * The current state of ongoing calls. */ val ongoingCallState: StateFlow<OngoingCallModel> = @@ -70,15 +84,29 @@ class OngoingCallInteractor @Inject constructor( notification = notification ) } - .onEach { state -> - setStatusBarRequiredForOngoingCall(state) - } .stateIn( scope = scope, started = SharingStarted.WhileSubscribed(), initialValue = OngoingCallModel.NoCall ) + @VisibleForTesting + val isStatusBarRequiredForOngoingCall = combine( + ongoingCallState, + isChipSwipedAway + ) { callState, chipSwipedAway -> + callState is OngoingCallModel.InCall && !chipSwipedAway + } + + @VisibleForTesting + val isGestureListeningEnabled = combine( + ongoingCallState, + statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode, + isChipSwipedAway + ) { callState, isFullscreen, chipSwipedAway -> + callState is OngoingCallModel.InCall && !chipSwipedAway && isFullscreen + } + private fun createOngoingCallStateFlow( notification: ActiveNotificationModel? ): Flow<OngoingCallModel> { @@ -99,6 +127,31 @@ class OngoingCallInteractor @Inject constructor( } } + override fun start() { + ongoingCallState + .filterIsInstance<OngoingCallModel.NoCall>() + .onEach { + _isChipSwipedAway.value = false + }.launchIn(scope) + + isStatusBarRequiredForOngoingCall.onEach { statusBarRequired -> + setStatusBarRequiredForOngoingCall(statusBarRequired) + }.launchIn(scope) + + isGestureListeningEnabled.onEach { isEnabled -> + updateGestureListening(isEnabled) + }.launchIn(scope) + } + + /** + * Callback that must run when the status bar is swiped while gesture listening is active. + */ + @VisibleForTesting + fun onStatusBarSwiped() { + logger.d("Status bar chip swiped away") + _isChipSwipedAway.value = true + } + private fun deriveOngoingCallState( model: ActiveNotificationModel, isVisible: Boolean @@ -126,8 +179,7 @@ class OngoingCallInteractor @Inject constructor( } } - private fun setStatusBarRequiredForOngoingCall(state: OngoingCallModel) { - val statusBarRequired = state is OngoingCallModel.InCall + private fun setStatusBarRequiredForOngoingCall(statusBarRequired: Boolean) { // TODO(b/382808183): Create a single repository that can be utilized in // `statusBarModeRepositoryStore` and `statusBarWindowControllerStore` so we do not need // two separate calls to force the status bar to stay visible. @@ -138,6 +190,16 @@ class OngoingCallInteractor @Inject constructor( .setOngoingProcessRequiresStatusBarVisible(statusBarRequired) } + private fun updateGestureListening(isEnabled: Boolean) { + if (isEnabled) { + swipeStatusBarAwayGestureHandler.addOnGestureDetectedCallback(TAG) { _ -> + onStatusBarSwiped() + } + } else { + swipeStatusBarAwayGestureHandler.removeOnGestureDetectedCallback(TAG) + } + } + companion object { private val TAG = "OngoingCall" } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandlerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandlerKosmos.kt new file mode 100644 index 000000000000..72165c95fc55 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandlerKosmos.kt @@ -0,0 +1,25 @@ +/* + * 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.gesture + +import com.android.systemui.kosmos.Kosmos +import org.mockito.kotlin.mock + +val Kosmos.swipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler by +Kosmos.Fixture { + mock<SwipeStatusBarAwayGestureHandler>() +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt index 9090e02b22b6..40d91017eeef 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt @@ -21,6 +21,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.log.logcatLogBuffer import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository +import com.android.systemui.statusbar.gesture.swipeStatusBarAwayGestureHandler import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore @@ -32,6 +33,7 @@ val Kosmos.ongoingCallInteractor: OngoingCallInteractor by activityManagerRepository = activityManagerRepository, statusBarModeRepositoryStore = fakeStatusBarModeRepository, statusBarWindowControllerStore = fakeStatusBarWindowControllerStore, + swipeStatusBarAwayGestureHandler = swipeStatusBarAwayGestureHandler, logBuffer = logcatLogBuffer("OngoingCallInteractorKosmos"), ) } |