diff options
8 files changed, 427 insertions, 12 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index 67243b6aed00..4ae2edcd007a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java @@ -53,6 +53,7 @@ import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger; import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; +import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.util.CarrierConfigTracker; @@ -156,6 +157,7 @@ public abstract class StatusBarViewModule { FeatureFlags featureFlags, StatusBarIconController statusBarIconController, StatusBarIconController.DarkIconManager.Factory darkIconManagerFactory, + CollapsedStatusBarViewModel collapsedStatusBarViewModel, StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, KeyguardStateController keyguardStateController, ShadeViewController shadeViewController, @@ -179,6 +181,7 @@ public abstract class StatusBarViewModule { featureFlags, statusBarIconController, darkIconManagerFactory, + collapsedStatusBarViewModel, statusBarHideIconsForBouncerManager, keyguardStateController, shadeViewController, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 5af8932859c6..0651a7b2363f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -68,6 +68,8 @@ import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentCom import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent.Startable; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener; +import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder; +import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.statusbar.window.StatusBarWindowStateListener; @@ -128,6 +130,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private final ShadeExpansionStateManager mShadeExpansionStateManager; private final StatusBarIconController mStatusBarIconController; private final CarrierConfigTracker mCarrierConfigTracker; + private final CollapsedStatusBarViewModel mCollapsedStatusBarViewModel; private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; private final StatusBarIconController.DarkIconManager.Factory mDarkIconManagerFactory; private final SecureSettings mSecureSettings; @@ -197,6 +200,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue FeatureFlags featureFlags, StatusBarIconController statusBarIconController, StatusBarIconController.DarkIconManager.Factory darkIconManagerFactory, + CollapsedStatusBarViewModel collapsedStatusBarViewModel, StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, KeyguardStateController keyguardStateController, ShadeViewController shadeViewController, @@ -219,6 +223,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mShadeExpansionStateManager = shadeExpansionStateManager; mFeatureFlags = featureFlags; mStatusBarIconController = statusBarIconController; + mCollapsedStatusBarViewModel = collapsedStatusBarViewModel; mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager; mDarkIconManagerFactory = darkIconManagerFactory; mKeyguardStateController = keyguardStateController; @@ -290,6 +295,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue new StatusBarSystemEventAnimator(mEndSideContent, getResources()); mCarrierConfigTracker.addCallback(mCarrierConfigCallback); mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener); + + CollapsedStatusBarViewBinder.bind( + mStatusBar, mCollapsedStatusBarViewModel, this::updateStatusBarVisibilities); } @Override @@ -415,6 +423,10 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue updateStatusBarVisibilities(animate); } + private void updateStatusBarVisibilities() { + updateStatusBarVisibilities(/* animate= */ true); + } + private void updateStatusBarVisibilities(boolean animate) { StatusBarVisibilityModel previousModel = mLastModifiedVisibility; StatusBarVisibilityModel newModel = calculateInternalModel(mLastSystemVisibility); @@ -457,7 +469,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue if (!mKeyguardStateController.isLaunchTransitionFadingAway() && !mKeyguardStateController.isKeyguardFadingAway() - && shouldHideNotificationIcons() + && shouldHideStatusBar() && !(mStatusBarStateController.getState() == StatusBarState.KEYGUARD && headsUpVisible)) { // Hide everything @@ -505,7 +517,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mOngoingCallController.notifyChipVisibilityChanged(showOngoingCallChip); } - private boolean shouldHideNotificationIcons() { + private boolean shouldHideStatusBar() { if (!mShadeExpansionStateManager.isClosed() && mShadeViewController.shouldHideStatusBarIconsWhenExpanded()) { return true; @@ -521,6 +533,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue // icons don't remain hidden somehow) we double check that the camera is still showing, the // status bar window isn't hidden, and we're still occluded as well, though these checks // are typically unnecessary. + // + // TODO(b/273314977): Can this be deleted now that we have the + // [isTransitioningFromLockscreenToOccluded] check below? final boolean hideIconsForSecureCamera = (mWaitingForWindowStateChangeAfterCameraLaunch || !mStatusBarWindowStateController.windowIsShowing()) && @@ -531,6 +546,16 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue return true; } + // While the status bar is transitioning from lockscreen to an occluded, we don't yet know + // if the occluding activity is fullscreen or not. If it *is* fullscreen, we don't want to + // briefly show the status bar just to immediately hide it again. So, we wait for the + // transition to occluding to finish before allowing us to potentially show the status bar + // again. (This status bar is always hidden on keyguard, so it's safe to continue hiding it + // during this transition.) See b/273314977. + if (mCollapsedStatusBarViewModel.isTransitioningFromLockscreenToOccluded().getValue()) { + return true; + } + return mStatusBarHideIconsForBouncerManager.getShouldHideStatusBarIconsForBouncer(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index 19c77e0b0de3..7aa90336e2bf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -23,6 +23,8 @@ import com.android.systemui.log.LogBufferFactory import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBufferFactory import com.android.systemui.log.LogBuffer +import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel +import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModelImpl import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel @@ -100,6 +102,11 @@ abstract class StatusBarPipelineModule { @ClassKey(CarrierConfigCoreStartable::class) abstract fun bindCarrierConfigStartable(impl: CarrierConfigCoreStartable): CoreStartable + @Binds + abstract fun collapsedStatusBarViewModel( + impl: CollapsedStatusBarViewModelImpl + ): CollapsedStatusBarViewModel + companion object { @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt new file mode 100644 index 000000000000..9a59851a230a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt @@ -0,0 +1,52 @@ +/* + * 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.statusbar.pipeline.shared.ui.binder + +import android.view.View +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel + +object CollapsedStatusBarViewBinder { + /** + * Binds the view to the view-model. [listener] will be notified whenever an event that may + * change the status bar visibility occurs. + */ + @JvmStatic + fun bind( + view: View, + viewModel: CollapsedStatusBarViewModel, + listener: StatusBarVisibilityChangeListener, + ) { + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.CREATED) { + viewModel.isTransitioningFromLockscreenToOccluded.collect { + listener.onStatusBarVisibilityMaybeChanged() + } + } + } + } +} + +/** + * Listener to be notified when the status bar visibility might have changed due to the device + * moving to a different state. + */ +fun interface StatusBarVisibilityChangeListener { + fun onStatusBarVisibilityMaybeChanged() +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt new file mode 100644 index 000000000000..edb7e4daca1b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt @@ -0,0 +1,62 @@ +/* + * 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.statusbar.pipeline.shared.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.TransitionState +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +/** + * A view model that manages the visibility of the [CollapsedStatusBarFragment] based on the device + * state. + * + * Right now, most of the status bar visibility management is actually in + * [CollapsedStatusBarFragment.calculateInternalModel], which uses + * [CollapsedStatusBarFragment.shouldHideNotificationIcons] and + * [StatusBarHideIconsForBouncerManager]. We should move those pieces of logic to this class instead + * so that it's all in one place and easily testable outside of the fragment. + */ +interface CollapsedStatusBarViewModel { + /** + * True if the device is currently transitioning from lockscreen to occluded and false + * otherwise. + */ + val isTransitioningFromLockscreenToOccluded: StateFlow<Boolean> +} + +@SysUISingleton +class CollapsedStatusBarViewModelImpl +@Inject +constructor( + keyguardTransitionInteractor: KeyguardTransitionInteractor, + @Application coroutineScope: CoroutineScope, +) : CollapsedStatusBarViewModel { + override val isTransitioningFromLockscreenToOccluded: StateFlow<Boolean> = + keyguardTransitionInteractor.lockscreenToOccludedTransition + .map { + it.transitionState == TransitionState.STARTED || + it.transitionState == TransitionState.RUNNING + } + .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = false) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 21769922c899..10efcd41019c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -28,10 +28,13 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Fragment; @@ -71,6 +74,7 @@ import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarLocationPublisher; import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; +import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewModel; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.statusbar.window.StatusBarWindowStateListener; @@ -83,7 +87,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.ArrayList; @@ -125,6 +128,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { private StatusBarIconController.DarkIconManager.Factory mIconManagerFactory; @Mock private StatusBarIconController.DarkIconManager mIconManager; + private FakeCollapsedStatusBarViewModel mCollapsedStatusBarViewModel; @Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; @Mock @@ -280,15 +284,15 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false); - Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE)); + verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE)); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); - Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE)); + verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE)); fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false); - Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE)); + verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE)); } @Test @@ -320,7 +324,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // THEN all views are hidden assertEquals(View.INVISIBLE, getClockView().getVisibility()); - Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE)); + verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE)); assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); } @@ -336,7 +340,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // THEN all views are shown assertEquals(View.VISIBLE, getClockView().getVisibility()); - Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE)); + verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE)); assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); } @@ -353,7 +357,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // THEN all views are hidden assertEquals(View.INVISIBLE, getClockView().getVisibility()); - Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE)); + verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE)); assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); // WHEN the shade is updated to no longer be open @@ -364,7 +368,58 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // THEN all views are shown assertEquals(View.VISIBLE, getClockView().getVisibility()); - Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE)); + verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE)); + assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); + } + + @Test + public void disable_notTransitioningToOccluded_everythingShown() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + + mCollapsedStatusBarViewModel.isTransitioningFromLockscreenToOccluded().setValue(false); + + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + // THEN all views are shown + assertEquals(View.VISIBLE, getClockView().getVisibility()); + verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE)); + assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); + } + + @Test + public void disable_isTransitioningToOccluded_everythingHidden() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + + mCollapsedStatusBarViewModel.isTransitioningFromLockscreenToOccluded().setValue(true); + + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + // THEN all views are hidden + assertEquals(View.GONE, getClockView().getVisibility()); + verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE)); + assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); + } + + @Test + public void disable_wasTransitioningToOccluded_transitionFinished_everythingShown() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + + // WHEN the transition is occurring + mCollapsedStatusBarViewModel.isTransitioningFromLockscreenToOccluded().setValue(true); + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + // THEN all views are hidden + assertEquals(View.GONE, getClockView().getVisibility()); + verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE)); + assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); + + // WHEN the transition has finished + mCollapsedStatusBarViewModel.isTransitioningFromLockscreenToOccluded().setValue(false); + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + // THEN all views are shown + assertEquals(View.VISIBLE, getClockView().getVisibility()); + verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE)); assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); } @@ -397,7 +452,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.VISIBLE, mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility()); - Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE)); + verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE)); } @Test @@ -575,6 +630,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mSecureSettings = mock(SecureSettings.class); mShadeExpansionStateManager = new ShadeExpansionStateManager(); + mCollapsedStatusBarViewModel = new FakeCollapsedStatusBarViewModel(); setUpNotificationIconAreaController(); return new CollapsedStatusBarFragment( @@ -587,6 +643,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mock(FeatureFlags.class), mStatusBarIconController, mIconManagerFactory, + mCollapsedStatusBarViewModel, mStatusBarHideIconsForBouncerManager, mKeyguardStateController, mShadeViewController, @@ -619,7 +676,14 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { when(mNotificationAreaInner.getLayoutParams()).thenReturn( new FrameLayout.LayoutParams(100, 100)); - when(mNotificationAreaInner.animate()).thenReturn(mock(ViewPropertyAnimator.class)); + // We should probably start using a real view so that we don't need to mock these methods. + ViewPropertyAnimator viewPropertyAnimator = mock(ViewPropertyAnimator.class); + when(mNotificationAreaInner.animate()).thenReturn(viewPropertyAnimator); + when(viewPropertyAnimator.alpha(anyFloat())).thenReturn(viewPropertyAnimator); + when(viewPropertyAnimator.setDuration(anyLong())).thenReturn(viewPropertyAnimator); + when(viewPropertyAnimator.setInterpolator(any())).thenReturn(viewPropertyAnimator); + when(viewPropertyAnimator.setStartDelay(anyLong())).thenReturn(viewPropertyAnimator); + when(viewPropertyAnimator.withEndAction(any())).thenReturn(viewPropertyAnimator); when(mMockNotificationAreaController.getNotificationInnerAreaView()).thenReturn( mNotificationAreaInner); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt new file mode 100644 index 000000000000..5faed9d952c2 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt @@ -0,0 +1,179 @@ +/* + * 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.statusbar.pipeline.shared.ui.viewmodel + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { + + private lateinit var underTest: CollapsedStatusBarViewModel + + private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository + private lateinit var testScope: TestScope + + @Before + fun setUp() { + testScope = TestScope(UnconfinedTestDispatcher()) + + keyguardTransitionRepository = FakeKeyguardTransitionRepository() + val interactor = KeyguardTransitionInteractor(keyguardTransitionRepository) + underTest = CollapsedStatusBarViewModelImpl(interactor, testScope.backgroundScope) + } + + @Test + fun isTransitioningFromLockscreenToOccluded_started_isTrue() = + testScope.runTest { + val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + KeyguardState.LOCKSCREEN, + KeyguardState.OCCLUDED, + value = 0f, + TransitionState.STARTED, + ) + ) + + assertThat(underTest.isTransitioningFromLockscreenToOccluded.value).isTrue() + + job.cancel() + } + + @Test + fun isTransitioningFromLockscreenToOccluded_running_isTrue() = + testScope.runTest { + val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + KeyguardState.LOCKSCREEN, + KeyguardState.OCCLUDED, + value = 0f, + TransitionState.RUNNING, + ) + ) + + assertThat(underTest.isTransitioningFromLockscreenToOccluded.value).isTrue() + + job.cancel() + } + + @Test + fun isTransitioningFromLockscreenToOccluded_finished_isFalse() = + testScope.runTest { + val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + KeyguardState.LOCKSCREEN, + KeyguardState.OCCLUDED, + value = 0f, + TransitionState.FINISHED, + ) + ) + + assertThat(underTest.isTransitioningFromLockscreenToOccluded.value).isFalse() + + job.cancel() + } + + @Test + fun isTransitioningFromLockscreenToOccluded_canceled_isFalse() = + testScope.runTest { + val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + KeyguardState.LOCKSCREEN, + KeyguardState.OCCLUDED, + value = 0f, + TransitionState.CANCELED, + ) + ) + + assertThat(underTest.isTransitioningFromLockscreenToOccluded.value).isFalse() + + job.cancel() + } + + @Test + fun isTransitioningFromLockscreenToOccluded_irrelevantTransition_isFalse() = + testScope.runTest { + val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + KeyguardState.AOD, + KeyguardState.LOCKSCREEN, + value = 0f, + TransitionState.RUNNING, + ) + ) + + assertThat(underTest.isTransitioningFromLockscreenToOccluded.value).isFalse() + + job.cancel() + } + + @Test + fun isTransitioningFromLockscreenToOccluded_followsRepoUpdates() = + testScope.runTest { + val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + KeyguardState.LOCKSCREEN, + KeyguardState.OCCLUDED, + value = 0f, + TransitionState.RUNNING, + ) + ) + + assertThat(underTest.isTransitioningFromLockscreenToOccluded.value).isTrue() + + // WHEN the repo updates the transition to finished + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + KeyguardState.LOCKSCREEN, + KeyguardState.OCCLUDED, + value = 0f, + TransitionState.FINISHED, + ) + ) + + // THEN our manager also updates + assertThat(underTest.isTransitioningFromLockscreenToOccluded.value).isFalse() + + job.cancel() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt new file mode 100644 index 000000000000..cbf6637ad46b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt @@ -0,0 +1,23 @@ +/* + * 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.statusbar.pipeline.shared.ui.viewmodel + +import kotlinx.coroutines.flow.MutableStateFlow + +class FakeCollapsedStatusBarViewModel : CollapsedStatusBarViewModel { + override val isTransitioningFromLockscreenToOccluded = MutableStateFlow(false) +} |