diff options
17 files changed, 444 insertions, 40 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 8e98d89207f9..fa3e172d11f1 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -563,7 +563,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private boolean mHasLayoutedSinceDown; private float mUpdateFlingVelocity; private boolean mUpdateFlingOnLayout; - private boolean mClosing; private boolean mTouchSlopExceeded; private int mTrackingPointer; private int mTouchSlop; @@ -2934,10 +2933,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @VisibleForTesting void setClosing(boolean isClosing) { - if (mClosing != isClosing) { - mClosing = isClosing; - mShadeExpansionStateManager.notifyPanelCollapsingChanged(isClosing); - } + mShadeRepository.setLegacyIsClosing(isClosing); mAmbientState.setIsClosing(isClosing); } @@ -3468,7 +3464,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump ipw.print("mHasLayoutedSinceDown="); ipw.println(mHasLayoutedSinceDown); ipw.print("mUpdateFlingVelocity="); ipw.println(mUpdateFlingVelocity); ipw.print("mUpdateFlingOnLayout="); ipw.println(mUpdateFlingOnLayout); - ipw.print("mClosing="); ipw.println(mClosing); + ipw.print("isClosing()="); ipw.println(isClosing()); ipw.print("mTouchSlopExceeded="); ipw.println(mTouchSlopExceeded); ipw.print("mTrackingPointer="); ipw.println(mTrackingPointer); ipw.print("mTouchSlop="); ipw.println(mTouchSlop); @@ -3807,7 +3803,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private void endClosing() { - if (mClosing) { + if (isClosing()) { setClosing(false); onClosingFinished(); } @@ -3927,7 +3923,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mExpandedHeight = Math.min(h, maxPanelHeight); // If we are closing the panel and we are almost there due to a slow decelerating // interpolator, abort the animation. - if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) { + if (mExpandedHeight < 1f && mExpandedHeight != 0f && isClosing()) { mExpandedHeight = 0f; if (mHeightAnimator != null) { mHeightAnimator.end(); @@ -4002,7 +3998,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public boolean isCollapsing() { - return mClosing || mIsLaunchAnimationRunning; + return isClosing() || mIsLaunchAnimationRunning; } public boolean isTracking() { @@ -4011,7 +4007,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public boolean canBeCollapsed() { - return !isFullyCollapsed() && !isTracking() && !mClosing; + return !isFullyCollapsed() && !isTracking() && !isClosing(); } @Override @@ -4126,7 +4122,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @VisibleForTesting boolean isClosing() { - return mClosing; + return mShadeRepository.getLegacyIsClosing().getValue(); } @Override @@ -4839,11 +4835,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mAnimatingOnDown = mHeightAnimator != null && !mIsSpringBackAnimation; mMinExpandHeight = 0.0f; mDownTime = mSystemClock.uptimeMillis(); - if (mAnimatingOnDown && mClosing) { + if (mAnimatingOnDown && isClosing()) { cancelHeightAnimator(); mTouchSlopExceeded = true; mShadeLog.v("NotificationPanelViewController MotionEvent intercepted:" - + " mAnimatingOnDown: true, mClosing: true"); + + " mAnimatingOnDown: true, isClosing(): true"); return true; } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt index 53eccfdf70d5..832fefc33ae0 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt @@ -17,6 +17,8 @@ package com.android.systemui.shade import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor +import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorEmptyImpl import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.domain.interactor.ShadeInteractorEmptyImpl import dagger.Binds @@ -36,4 +38,10 @@ abstract class ShadeEmptyImplModule { @Binds @SysUISingleton abstract fun bindsShadeInteractor(si: ShadeInteractorEmptyImpl): ShadeInteractor + + @Binds + @SysUISingleton + abstract fun bindsShadeAnimationInteractor( + sai: ShadeAnimationInteractorEmptyImpl + ): ShadeAnimationInteractor } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt index e20534cc7840..d6db19e507a6 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt @@ -163,12 +163,6 @@ class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents { } } - fun notifyPanelCollapsingChanged(isCollapsing: Boolean) { - for (cb in shadeStateEventsListeners) { - cb.onPanelCollapsingChanged(isCollapsing) - } - } - private fun debugLog(msg: String) { if (!DEBUG) return Log.v(TAG, msg) diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt index 54467cffa401..d9b298d0dfa9 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt @@ -19,6 +19,9 @@ package com.android.systemui.shade import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.shade.domain.interactor.BaseShadeInteractor +import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor +import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl +import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorSceneContainerImpl import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl @@ -45,6 +48,20 @@ abstract class ShadeModule { sceneContainerOff.get() } } + + @Provides + @SysUISingleton + fun provideShadeAnimationInteractor( + sceneContainerFlags: SceneContainerFlags, + sceneContainerOn: Provider<ShadeAnimationInteractorSceneContainerImpl>, + sceneContainerOff: Provider<ShadeAnimationInteractorLegacyImpl> + ): ShadeAnimationInteractor { + return if (sceneContainerFlags.isEnabled()) { + sceneContainerOn.get() + } else { + sceneContainerOff.get() + } + } } @Binds diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt index c8511d76f5f0..ff96ca3caeea 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt @@ -27,10 +27,6 @@ interface ShadeStateEvents { /** Callbacks for certain notification panel events. */ interface ShadeStateEventsListener { - - /** Invoked when the notification panel starts or stops collapsing. */ - fun onPanelCollapsingChanged(isCollapsing: Boolean) {} - /** * Invoked when the notification panel starts or stops launching an [android.app.Activity]. */ diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt index 47b08fe037a4..e94a3eb5db22 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt @@ -105,6 +105,12 @@ interface ShadeRepository { /** True when QS is taking up the entire screen, i.e. fully expanded on a non-unfolded phone. */ @Deprecated("Use ShadeInteractor instead") val legacyQsFullscreen: StateFlow<Boolean> + /** NPVC.mClosing as a flow. */ + @Deprecated("Use ShadeAnimationInteractor instead") val legacyIsClosing: StateFlow<Boolean> + + /** Sets whether a closing animation is happening. */ + @Deprecated("Use ShadeAnimationInteractor instead") fun setLegacyIsClosing(isClosing: Boolean) + /** */ @Deprecated("Use ShadeInteractor instead") fun setLegacyQsFullscreen(legacyQsFullscreen: Boolean) @@ -261,6 +267,15 @@ constructor(shadeExpansionStateManager: ShadeExpansionStateManager) : ShadeRepos _legacyShadeTracking.value = tracking } + private val _legacyIsClosing = MutableStateFlow(false) + @Deprecated("Use ShadeInteractor instead") + override val legacyIsClosing: StateFlow<Boolean> = _legacyIsClosing.asStateFlow() + + @Deprecated("Use ShadeInteractor instead") + override fun setLegacyIsClosing(isClosing: Boolean) { + _legacyIsClosing.value = isClosing + } + @Deprecated("Should only be called by NPVC and tests") override fun setLegacyLockscreenShadeTracking(tracking: Boolean) { legacyLockscreenShadeTracking.value = tracking diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt new file mode 100644 index 000000000000..ff422b72c694 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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.shade.domain.interactor + +import kotlinx.coroutines.flow.Flow + +/** Business logic related to shade animations and transitions. */ +interface ShadeAnimationInteractor { + /** + * Whether a short animation to close the shade or QS is running. This will be false if the user + * is manually closing the shade or QS but true if they lift their finger and an animation + * completes the close. Important: if QS is collapsing back to shade, this will be false because + * that is not considered "closing". + */ + val isAnyCloseAnimationRunning: Flow<Boolean> +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt new file mode 100644 index 000000000000..b4a134fd1910 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 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.shade.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject +import kotlinx.coroutines.flow.flowOf + +/** Implementation of ShadeAnimationInteractor for shadeless SysUI variants. */ +@SysUISingleton +class ShadeAnimationInteractorEmptyImpl @Inject constructor() : ShadeAnimationInteractor { + override val isAnyCloseAnimationRunning = flowOf(false) +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorLegacyImpl.kt new file mode 100644 index 000000000000..d51409365014 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorLegacyImpl.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 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.shade.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.shade.data.repository.ShadeRepository +import javax.inject.Inject + +/** Implementation of ShadeAnimationInteractor compatible with NPVC. */ +@SysUISingleton +class ShadeAnimationInteractorLegacyImpl +@Inject +constructor( + shadeRepository: ShadeRepository, +) : ShadeAnimationInteractor { + override val isAnyCloseAnimationRunning = shadeRepository.legacyIsClosing +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt new file mode 100644 index 000000000000..7c0762d755de --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 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.shade.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.model.ObservableTransitionState +import com.android.systemui.scene.shared.model.SceneKey +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map + +/** Implementation of ShadeAnimationInteractor compatible with the scene container framework. */ +@SysUISingleton +class ShadeAnimationInteractorSceneContainerImpl +@Inject +constructor( + sceneInteractor: SceneInteractor, +) : ShadeAnimationInteractor { + @OptIn(ExperimentalCoroutinesApi::class) + override val isAnyCloseAnimationRunning = + sceneInteractor.transitionState + .flatMapLatest { state -> + when (state) { + is ObservableTransitionState.Idle -> flowOf(false) + is ObservableTransitionState.Transition -> + if ( + (state.fromScene == SceneKey.Shade && + state.toScene != SceneKey.QuickSettings) || + (state.fromScene == SceneKey.QuickSettings && + state.toScene != SceneKey.Shade) + ) { + state.isUserInputOngoing.map { !it } + } else { + flowOf(false) + } + } + } + .distinctUntilChanged() +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt index 7cff8ea7abd2..3fd070c7146e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt @@ -71,14 +71,11 @@ constructor( override val isQsBypassingShade: Flow<Boolean> = sceneInteractor.transitionState - .flatMapLatest { state -> + .map { state -> when (state) { - is ObservableTransitionState.Idle -> flowOf(false) + is ObservableTransitionState.Idle -> false is ObservableTransitionState.Transition -> - flowOf( - state.toScene == SceneKey.QuickSettings && - state.fromScene != SceneKey.Shade - ) + state.toScene == SceneKey.QuickSettings && state.fromScene != SceneKey.Shade } } .distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java index a2379b270f49..46e2391c87e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java @@ -29,6 +29,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.ShadeStateEvents; +import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor; import com.android.systemui.statusbar.notification.VisibilityLocationProvider; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; @@ -39,6 +40,7 @@ import com.android.systemui.statusbar.notification.collection.provider.VisualSta import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.Compile; import com.android.systemui.util.concurrency.DelayableExecutor; +import com.android.systemui.util.kotlin.JavaAdapter; import java.io.PrintWriter; import java.util.HashMap; @@ -62,7 +64,9 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable, private final DelayableExecutor mDelayableExecutor; private final HeadsUpManager mHeadsUpManager; private final ShadeStateEvents mShadeStateEvents; + private final ShadeAnimationInteractor mShadeAnimationInteractor; private final StatusBarStateController mStatusBarStateController; + private final JavaAdapter mJavaAdapter; private final VisibilityLocationProvider mVisibilityLocationProvider; private final VisualStabilityProvider mVisualStabilityProvider; private final WakefulnessLifecycle mWakefulnessLifecycle; @@ -95,11 +99,15 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable, DumpManager dumpManager, HeadsUpManager headsUpManager, ShadeStateEvents shadeStateEvents, + ShadeAnimationInteractor shadeAnimationInteractor, + JavaAdapter javaAdapter, StatusBarStateController statusBarStateController, VisibilityLocationProvider visibilityLocationProvider, VisualStabilityProvider visualStabilityProvider, WakefulnessLifecycle wakefulnessLifecycle) { mHeadsUpManager = headsUpManager; + mShadeAnimationInteractor = shadeAnimationInteractor; + mJavaAdapter = javaAdapter; mVisibilityLocationProvider = visibilityLocationProvider; mVisualStabilityProvider = visualStabilityProvider; mWakefulnessLifecycle = wakefulnessLifecycle; @@ -119,6 +127,8 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable, mStatusBarStateController.addCallback(mStatusBarStateControllerListener); mPulsing = mStatusBarStateController.isPulsing(); mShadeStateEvents.addShadeStateEventsListener(this); + mJavaAdapter.alwaysCollectFlow(mShadeAnimationInteractor.isAnyCloseAnimationRunning(), + this::onShadeOrQsClosingChanged); pipeline.setVisualStabilityManager(mNotifStabilityManager); } @@ -322,10 +332,9 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable, } } - @Override - public void onPanelCollapsingChanged(boolean isCollapsing) { - mNotifPanelCollapsing = isCollapsing; - updateAllowedStates("notifPanelCollapsing", isCollapsing); + private void onShadeOrQsClosingChanged(boolean isClosing) { + mNotifPanelCollapsing = isClosing; + updateAllowedStates("notifPanelCollapsing", isClosing); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt index 5f8777ddcbb6..f8aa359b569d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt @@ -225,4 +225,13 @@ class ShadeRepositoryImplTest : SysuiTestCase() { underTest.setLegacyQsFullscreen(true) assertThat(underTest.legacyQsFullscreen.value).isEqualTo(true) } + + @Test + fun updateLegacyIsClosing() = + testScope.runTest { + assertThat(underTest.legacyIsClosing.value).isEqualTo(false) + + underTest.setLegacyIsClosing(true) + assertThat(underTest.legacyIsClosing.value).isEqualTo(true) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt new file mode 100644 index 000000000000..40006ba8dd82 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2023 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.shade.domain.interactor + +import androidx.test.filters.SmallTest +import com.android.systemui.SysUITestComponent +import com.android.systemui.SysUITestModule +import com.android.systemui.SysuiTestCase +import com.android.systemui.TestMocksModule +import com.android.systemui.collectLastValue +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.flags.Flags +import com.android.systemui.runCurrent +import com.android.systemui.runTest +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.model.ObservableTransitionState +import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.statusbar.phone.DozeParameters +import com.android.systemui.user.domain.UserDomainLayerModule +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth +import dagger.BindsInstance +import dagger.Component +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf +import org.junit.Test + +@SmallTest +class ShadeAnimationInteractorSceneContainerImplTest : SysuiTestCase() { + + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + UserDomainLayerModule::class, + ] + ) + interface TestComponent : SysUITestComponent<ShadeAnimationInteractorSceneContainerImpl> { + val sceneInteractor: SceneInteractor + + @Component.Factory + interface Factory { + fun create( + @BindsInstance test: SysuiTestCase, + featureFlags: FakeFeatureFlagsClassicModule, + mocks: TestMocksModule, + ): TestComponent + } + } + + private val dozeParameters: DozeParameters = mock() + + private val testComponent: TestComponent = + DaggerShadeAnimationInteractorSceneContainerImplTest_TestComponent.factory() + .create( + test = this, + featureFlags = + FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }, + mocks = + TestMocksModule( + dozeParameters = dozeParameters, + ), + ) + + @Test + fun isAnyCloseAnimationRunning_qsToShade() = + testComponent.runTest() { + val actual by collectLastValue(underTest.isAnyCloseAnimationRunning) + + // WHEN transitioning from QS to Shade + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = SceneKey.QuickSettings, + toScene = SceneKey.Shade, + progress = MutableStateFlow(.1f), + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + runCurrent() + + // THEN qs is animating closed + Truth.assertThat(actual).isFalse() + } + + @Test + fun isAnyCloseAnimationRunning_qsToGone_userInputNotOngoing() = + testComponent.runTest() { + val actual by collectLastValue(underTest.isAnyCloseAnimationRunning) + + // WHEN transitioning from QS to Gone with no ongoing user input + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = SceneKey.QuickSettings, + toScene = SceneKey.Gone, + progress = MutableStateFlow(.1f), + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + runCurrent() + + // THEN qs is animating closed + Truth.assertThat(actual).isTrue() + } + + @Test + fun isAnyCloseAnimationRunning_qsToGone_userInputOngoing() = + testComponent.runTest() { + val actual by collectLastValue(underTest.isAnyCloseAnimationRunning) + + // WHEN transitioning from QS to Gone with user input ongoing + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = SceneKey.QuickSettings, + toScene = SceneKey.Gone, + progress = MutableStateFlow(.1f), + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(true), + ) + ) + sceneInteractor.setTransitionState(transitionState) + runCurrent() + + // THEN qs is not animating closed + Truth.assertThat(actual).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt index 565e20a034db..310b86f908c5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt @@ -127,22 +127,22 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { val actual by collectLastValue(underTest.qsExpansion) // WHEN split shade is enabled and QS is expanded - keyguardRepository.setStatusBarState(StatusBarState.SHADE) overrideResource(R.bool.config_use_split_notification_shade, true) configurationRepository.onAnyConfigurationChange() - val progress = MutableStateFlow(.3f) + runCurrent() val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( fromScene = SceneKey.QuickSettings, toScene = SceneKey.Shade, - progress = progress, + progress = MutableStateFlow(.3f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), ) ) sceneInteractor.setTransitionState(transitionState) runCurrent() + keyguardRepository.setStatusBarState(StatusBarState.SHADE) // THEN legacy shade expansion is passed through Truth.assertThat(actual).isEqualTo(.3f) @@ -157,6 +157,8 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { // WHEN split shade is not enabled and QS is expanded keyguardRepository.setStatusBarState(StatusBarState.SHADE) overrideResource(R.bool.config_use_split_notification_shade, false) + configurationRepository.onAnyConfigurationChange() + runCurrent() val progress = MutableStateFlow(.3f) val transitionState = MutableStateFlow<ObservableTransitionState>( @@ -182,13 +184,12 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { // WHEN scene transition active keyguardRepository.setStatusBarState(StatusBarState.SHADE) - val progress = MutableStateFlow(.3f) val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( fromScene = SceneKey.QuickSettings, toScene = SceneKey.Shade, - progress = progress, + progress = MutableStateFlow(.3f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), ) @@ -347,6 +348,52 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { Truth.assertThat(expansionAmount).isEqualTo(0f) } + fun isQsBypassingShade_goneToQs() = + testComponent.runTest() { + val actual by collectLastValue(underTest.isQsBypassingShade) + + // WHEN transitioning from QS directly to Gone + configurationRepository.onAnyConfigurationChange() + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = SceneKey.Gone, + toScene = SceneKey.QuickSettings, + progress = MutableStateFlow(.1f), + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + runCurrent() + + // THEN qs is bypassing shade + Truth.assertThat(actual).isTrue() + } + + fun isQsBypassingShade_shadeToQs() = + testComponent.runTest() { + val actual by collectLastValue(underTest.isQsBypassingShade) + + // WHEN transitioning from QS to Shade + configurationRepository.onAnyConfigurationChange() + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = SceneKey.Shade, + toScene = SceneKey.QuickSettings, + progress = MutableStateFlow(.1f), + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + runCurrent() + + // THEN qs is not bypassing shade + Truth.assertThat(actual).isFalse() + } + @Test fun lockscreenShadeExpansion_transitioning_toAndFromDifferentScenes() = testComponent.runTest() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java index e488f39bf27d..bd4647431ab9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java @@ -34,12 +34,14 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; +import com.android.keyguard.TestScopeProvider; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.ShadeStateEvents; import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener; +import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor; import com.android.systemui.statusbar.notification.VisibilityLocationProvider; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder; @@ -51,6 +53,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; @@ -62,6 +65,10 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.verification.VerificationMode; +import kotlinx.coroutines.flow.MutableStateFlow; +import kotlinx.coroutines.flow.StateFlowKt; +import kotlinx.coroutines.test.TestScope; + @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -78,6 +85,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { @Mock private ShadeStateEvents mShadeStateEvents; @Mock private VisibilityLocationProvider mVisibilityLocationProvider; @Mock private VisualStabilityProvider mVisualStabilityProvider; + @Mock private ShadeAnimationInteractor mShadeAnimationInteractor; @Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mSBStateListenerCaptor; @@ -86,6 +94,9 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { private FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock); + private final TestScope mTestScope = TestScopeProvider.getTestScope(); + private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope()); + private final MutableStateFlow<Boolean> mShadeClosing = StateFlowKt.MutableStateFlow(false); private WakefulnessLifecycle.Observer mWakefulnessObserver; private StatusBarStateController.StateListener mStatusBarStateListener; @@ -103,11 +114,13 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { mDumpManager, mHeadsUpManager, mShadeStateEvents, + mShadeAnimationInteractor, + mJavaAdapter, mStatusBarStateController, mVisibilityLocationProvider, mVisualStabilityProvider, mWakefulnessLifecycle); - + when(mShadeAnimationInteractor.isAnyCloseAnimationRunning()).thenReturn(mShadeClosing); mCoordinator.attach(mNotifPipeline); // capture arguments: @@ -549,7 +562,8 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { } private void setPanelCollapsing(boolean collapsing) { - mNotifPanelEventsCallback.onPanelCollapsingChanged(collapsing); + mShadeClosing.setValue(collapsing); + mTestScope.getTestScheduler().runCurrent(); } private void setPulsing(boolean pulsing) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt index a70b91da6145..9c108487e4c5 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt @@ -106,6 +106,14 @@ class FakeShadeRepository @Inject constructor() : ShadeRepository { _legacyQsFullscreen.value = legacyQsFullscreen } + private val _legacyIsClosing = MutableStateFlow(false) + @Deprecated("Use ShadeInteractor instead") override val legacyIsClosing = _legacyIsClosing + + @Deprecated("Use ShadeInteractor instead") + override fun setLegacyIsClosing(isClosing: Boolean) { + _legacyIsClosing.value = isClosing + } + fun setShadeModel(model: ShadeModel) { _shadeModel.value = model } |