diff options
| author | 2023-09-18 21:08:37 +0000 | |
|---|---|---|
| committer | 2023-09-18 21:08:37 +0000 | |
| commit | 1290bd665e6badb6093d19c99b0354db04a6b74d (patch) | |
| tree | 8b27ee6cdbf815e202b6c0957dd9faae42203800 | |
| parent | aef7b742ede29e9c5b83616cbc43ccfdf80a13f5 (diff) | |
| parent | e4d7972071692563393cb566c94158f369be33dc (diff) | |
Merge "Add flows for whether user is interacting with the shade" into main
3 files changed, 291 insertions, 9 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt index 97b06170e770..054bd082edb7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt @@ -25,7 +25,7 @@ constructor( shadeExpansionCollectorJob = scope.launch { // wait for it to emit true once - shadeInteractorLazy.get().anyExpanding.first { it } + shadeInteractorLazy.get().isAnyExpanding.first { it } onShadeInteraction.run() } shadeExpansionCollectorJob?.invokeOnCompletion { shadeExpansionCollectorJob = null } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt index 95a072c0d4c9..b77b9e488d7e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt @@ -35,15 +35,20 @@ import javax.inject.Inject import javax.inject.Provider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.isActive import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.isActive /** Business logic for shade interactions. */ @OptIn(ExperimentalCoroutinesApi::class) @@ -119,18 +124,41 @@ constructor( repository.qsExpansion } - /** The amount [0-1] either QS or the shade has been opened */ + /** The amount [0-1] either QS or the shade has been opened. */ val anyExpansion: StateFlow<Float> = combine(shadeExpansion, qsExpansion) { shadeExp, qsExp -> maxOf(shadeExp, qsExp) } .stateIn(scope, SharingStarted.Eagerly, 0f) /** Whether either the shade or QS is expanding from a fully collapsed state. */ - val anyExpanding = + val isAnyExpanding = anyExpansion .pairwise(1f) .map { (prev, curr) -> curr > 0f && curr < 1f && prev < 1f } .distinctUntilChanged() + /** + * Whether the user is expanding or collapsing the shade with user input. This will be true even + * if the user's input gesture has ended but a transition they initiated is animating. + */ + val isUserInteractingWithShade: Flow<Boolean> = + userInteractingFlow(repository.legacyShadeTracking, repository.legacyShadeExpansion) + + /** + * Whether the user is expanding or collapsing quick settings with user input. This will be true + * even if the user's input gesture has ended but a transition they initiated is still + * animating. + */ + val isUserInteractingWithQs: Flow<Boolean> = + userInteractingFlow(repository.legacyQsTracking, repository.qsExpansion) + + /** + * Whether the user is expanding or collapsing either the shade or quick settings with user + * input (i.e. dragging a pointer). This will be true even if the user's input gesture had ended + * but a transition they initiated is still animating. + */ + val isUserInteracting: Flow<Boolean> = + combine(isUserInteractingWithShade, isUserInteractingWithShade) { shade, qs -> shade || qs } + /** Emits true if the shade can be expanded from QQS to QS and false otherwise. */ val isExpandToQsEnabled: Flow<Boolean> = combine( @@ -169,4 +197,30 @@ constructor( } } .distinctUntilChanged() + + /** + * Return a flow for whether a user is interacting with an expandable shade component using + * tracking and expansion flows. NOTE: expansion must be a `StateFlow` to guarantee that + * [expansion.first] checks the current value of the flow. + */ + private fun userInteractingFlow( + tracking: Flow<Boolean>, + expansion: StateFlow<Float> + ): Flow<Boolean> { + return flow { + // initial value is false + emit(false) + while (currentCoroutineContext().isActive) { + // wait for tracking to become true + tracking.first { it } + emit(true) + // wait for tracking to become false + tracking.first { !it } + // wait for expansion to complete in either direction + expansion.first { it <= 0f || it >= 1f } + // interaction complete + emit(false) + } + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt index 9275ccb6d473..d4b69fad6097 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt @@ -402,6 +402,7 @@ class ShadeInteractorTest : SysuiTestCase() { assertThat(actual).isEqualTo(0.8f) } + @Test fun shadeExpansionWhenInSplitShadeAndQsExpanded() = testScope.runTest { val actual by collectLastValue(underTest.shadeExpansion) @@ -410,27 +411,31 @@ class ShadeInteractorTest : SysuiTestCase() { keyguardRepository.setStatusBarState(StatusBarState.SHADE) overrideResource(R.bool.config_use_split_notification_shade, true) configurationRepository.onAnyConfigurationChange() - runCurrent() shadeRepository.setQsExpansion(.5f) shadeRepository.setLegacyShadeExpansion(.7f) + runCurrent() // THEN legacy shade expansion is passed through assertThat(actual).isEqualTo(.7f) } + @Test fun shadeExpansionWhenNotInSplitShadeAndQsExpanded() = testScope.runTest { val actual by collectLastValue(underTest.shadeExpansion) // WHEN split shade is not enabled and QS is expanded keyguardRepository.setStatusBarState(StatusBarState.SHADE) + overrideResource(R.bool.config_use_split_notification_shade, false) shadeRepository.setQsExpansion(.5f) shadeRepository.setLegacyShadeExpansion(1f) + runCurrent() // THEN shade expansion is zero assertThat(actual).isEqualTo(0f) } + @Test fun shadeExpansionWhenNotInSplitShadeAndQsCollapsed() = testScope.runTest { val actual by collectLastValue(underTest.shadeExpansion) @@ -471,7 +476,7 @@ class ShadeInteractorTest : SysuiTestCase() { @Test fun expanding_shadeDraggedDown_expandingTrue() = testScope.runTest() { - val actual by collectLastValue(underTest.anyExpanding) + val actual by collectLastValue(underTest.isAnyExpanding) // GIVEN shade and QS collapsed shadeRepository.setLegacyShadeExpansion(0f) @@ -489,7 +494,7 @@ class ShadeInteractorTest : SysuiTestCase() { @Test fun expanding_qsDraggedDown_expandingTrue() = testScope.runTest() { - val actual by collectLastValue(underTest.anyExpanding) + val actual by collectLastValue(underTest.isAnyExpanding) // GIVEN shade and QS collapsed shadeRepository.setLegacyShadeExpansion(0f) @@ -507,7 +512,7 @@ class ShadeInteractorTest : SysuiTestCase() { @Test fun expanding_shadeDraggedUpAndDown() = testScope.runTest() { - val actual by collectLastValue(underTest.anyExpanding) + val actual by collectLastValue(underTest.isAnyExpanding) // WHEN shade starts collapsed then partially expanded shadeRepository.setLegacyShadeExpansion(0f) @@ -532,7 +537,7 @@ class ShadeInteractorTest : SysuiTestCase() { // THEN anyExpanding is still true assertThat(actual).isTrue() - // WHEN shade fully shadeExpanded + // WHEN shade fully expanded shadeRepository.setLegacyShadeExpansion(1f) runCurrent() @@ -550,7 +555,7 @@ class ShadeInteractorTest : SysuiTestCase() { @Test fun expanding_shadeDraggedDownThenUp_expandingFalse() = testScope.runTest() { - val actual by collectLastValue(underTest.anyExpanding) + val actual by collectLastValue(underTest.isAnyExpanding) // GIVEN shade starts collapsed shadeRepository.setLegacyShadeExpansion(0f) @@ -708,4 +713,227 @@ class ShadeInteractorTest : SysuiTestCase() { // THEN expansion is still 0 assertThat(expansionAmount).isEqualTo(0f) } + + @Test + fun userInteractingWithShade_shadeDraggedUpAndDown() = + testScope.runTest() { + val actual by collectLastValue(underTest.isUserInteractingWithShade) + // GIVEN shade collapsed and not tracking input + shadeRepository.setLegacyShadeExpansion(0f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + + // WHEN shade tracking starts + shadeRepository.setLegacyShadeTracking(true) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade dragged down halfway + shadeRepository.setLegacyShadeExpansion(.5f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade fully expanded but tracking is not stopped + shadeRepository.setLegacyShadeExpansion(1f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade fully collapsed but tracking is not stopped + shadeRepository.setLegacyShadeExpansion(0f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade dragged halfway and tracking is stopped + shadeRepository.setLegacyShadeExpansion(.6f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade completes expansion stopped + shadeRepository.setLegacyShadeExpansion(1f) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + } + + @Test + fun userInteractingWithShade_shadeExpanded() = + testScope.runTest() { + val actual by collectLastValue(underTest.isUserInteractingWithShade) + // GIVEN shade collapsed and not tracking input + shadeRepository.setLegacyShadeExpansion(0f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + + // WHEN shade tracking starts + shadeRepository.setLegacyShadeTracking(true) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade dragged down halfway + shadeRepository.setLegacyShadeExpansion(.5f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade fully expanded and tracking is stopped + shadeRepository.setLegacyShadeExpansion(1f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + } + + @Test + fun userInteractingWithShade_shadePartiallyExpanded() = + testScope.runTest() { + val actual by collectLastValue(underTest.isUserInteractingWithShade) + // GIVEN shade collapsed and not tracking input + shadeRepository.setLegacyShadeExpansion(0f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + + // WHEN shade tracking starts + shadeRepository.setLegacyShadeTracking(true) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade partially expanded + shadeRepository.setLegacyShadeExpansion(.4f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN tracking is stopped + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade goes back to collapsed + shadeRepository.setLegacyShadeExpansion(0f) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + } + + @Test + fun userInteractingWithShade_shadeCollapsed() = + testScope.runTest() { + val actual by collectLastValue(underTest.isUserInteractingWithShade) + // GIVEN shade expanded and not tracking input + shadeRepository.setLegacyShadeExpansion(1f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + + // WHEN shade tracking starts + shadeRepository.setLegacyShadeTracking(true) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade dragged up halfway + shadeRepository.setLegacyShadeExpansion(.5f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade fully collapsed and tracking is stopped + shadeRepository.setLegacyShadeExpansion(0f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + } + + @Test + fun userInteractingWithQs_qsDraggedUpAndDown() = + testScope.runTest() { + val actual by collectLastValue(underTest.isUserInteractingWithQs) + // GIVEN qs collapsed and not tracking input + shadeRepository.setQsExpansion(0f) + shadeRepository.setLegacyQsTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + + // WHEN qs tracking starts + shadeRepository.setLegacyQsTracking(true) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN qs dragged down halfway + shadeRepository.setQsExpansion(.5f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN qs fully expanded but tracking is not stopped + shadeRepository.setQsExpansion(1f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN qs fully collapsed but tracking is not stopped + shadeRepository.setQsExpansion(0f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN qs dragged halfway and tracking is stopped + shadeRepository.setQsExpansion(.6f) + shadeRepository.setLegacyQsTracking(false) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN qs completes expansion stopped + shadeRepository.setQsExpansion(1f) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + } } |