Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9d7512ab authored by Fabián Kozynski's avatar Fabián Kozynski
Browse files

Support Brightness mirror

Convert some viewmodels to activatables.

In particular, viewmodels that
use the BrightnessViewModel can specify if they want it to be mirrored
(in which case BrightnessMirrorViewModel will be notified) and the
composable will be put in an overlay while it's being dragged.

Bug: 374333334
Flag: com.android.systemui.qs_ui_refactor_compose_fragment
Test: manual
Test: atest BrightnessSliderViewModelTest
Test: atest SwipeBrightness
Test: atest NotificationShadeWindowViewControllerTest
Change-Id: I929cbe2ce4ba519088a0e712438a8d6359e0e7f0
parent 194dc2cd
Loading
Loading
Loading
Loading
+53 −12
Original line number Diff line number Diff line
@@ -30,16 +30,20 @@ import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.res.R
import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class BrightnessSliderViewModelTest : SysuiTestCase() {
@@ -49,15 +53,17 @@ class BrightnessSliderViewModelTest : SysuiTestCase() {

    private val kosmos = testKosmos()

    private val underTest =
    private val underTest by lazy {
        with(kosmos) {
            BrightnessSliderViewModel(
                screenBrightnessInteractor,
                brightnessPolicyEnforcementInteractor,
                applicationCoroutineScope,
                sliderHapticsViewModelFactory,
                brightnessMirrorShowingInteractor,
                supportsMirroring = true,
            )
        }
    }

    @Before
    fun setUp() {
@@ -65,18 +71,18 @@ class BrightnessSliderViewModelTest : SysuiTestCase() {
            LinearBrightness(minBrightness),
            LinearBrightness(maxBrightness),
        )
        underTest.activateIn(kosmos.testScope)
    }

    @Test
    fun brightnessChangeInRepository_changeInFlow() =
        with(kosmos) {
            testScope.runTest {
                val gammaBrightness by collectLastValue(underTest.currentBrightness)

                var brightness = 0.6f
                fakeScreenBrightnessRepository.setBrightness(LinearBrightness(brightness))
                runCurrent()

                assertThat(gammaBrightness!!.value)
                assertThat(underTest.currentBrightness.value)
                    .isEqualTo(
                        BrightnessUtils.convertLinearToGammaFloat(
                            brightness,
@@ -87,8 +93,9 @@ class BrightnessSliderViewModelTest : SysuiTestCase() {

                brightness = 0.2f
                fakeScreenBrightnessRepository.setBrightness(LinearBrightness(brightness))
                runCurrent()

                assertThat(gammaBrightness!!.value)
                assertThat(underTest.currentBrightness.value)
                    .isEqualTo(
                        BrightnessUtils.convertLinearToGammaFloat(
                            brightness,
@@ -117,7 +124,6 @@ class BrightnessSliderViewModelTest : SysuiTestCase() {
            testScope.runTest {
                val temporaryBrightness by
                    collectLastValue(fakeScreenBrightnessRepository.temporaryBrightness)
                val brightness by collectLastValue(underTest.currentBrightness)

                val newBrightness = underTest.maxBrightness.value / 3
                val expectedTemporaryBrightness =
@@ -133,7 +139,7 @@ class BrightnessSliderViewModelTest : SysuiTestCase() {
                assertThat(temporaryBrightness!!.floatValue)
                    .isWithin(1e-5f)
                    .of(expectedTemporaryBrightness)
                assertThat(brightness!!.value).isNotEqualTo(newBrightness)
                assertThat(underTest.currentBrightness.value).isNotEqualTo(newBrightness)
            }
        }

@@ -141,14 +147,13 @@ class BrightnessSliderViewModelTest : SysuiTestCase() {
    fun draggingStopped_currentBrightnessChanges() =
        with(kosmos) {
            testScope.runTest {
                val brightness by collectLastValue(underTest.currentBrightness)

                val newBrightness = underTest.maxBrightness.value / 3
                val drag = Drag.Stopped(GammaBrightness(newBrightness))

                underTest.onDrag(drag)
                runCurrent()

                assertThat(brightness!!.value).isEqualTo(newBrightness)
                assertThat(underTest.currentBrightness.value).isEqualTo(newBrightness)
            }
        }

@@ -168,4 +173,40 @@ class BrightnessSliderViewModelTest : SysuiTestCase() {
                )
            )
    }

    @Test
    fun supportedMirror_mirrorShowingWhenDragging() =
        with(kosmos) {
            testScope.runTest {
                val mirrorInInteractor by
                    collectLastValue(brightnessMirrorShowingInteractor.isShowing)

                underTest.setIsDragging(true)
                assertThat(mirrorInInteractor).isEqualTo(true)
                assertThat(underTest.showMirror).isEqualTo(true)

                underTest.setIsDragging(false)
                assertThat(mirrorInInteractor).isEqualTo(false)
                assertThat(underTest.showMirror).isEqualTo(false)
            }
        }

    @Test
    fun unsupportedMirror_mirrorNeverShowing() =
        with(kosmos) {
            testScope.runTest {
                val mirrorInInteractor by
                    collectLastValue(brightnessMirrorShowingInteractor.isShowing)

                val noMirrorViewModel = brightnessSliderViewModelFactory.create(false)

                noMirrorViewModel.setIsDragging(true)
                assertThat(mirrorInInteractor).isEqualTo(false)
                assertThat(noMirrorViewModel.showMirror).isEqualTo(false)

                noMirrorViewModel.setIsDragging(false)
                assertThat(mirrorInInteractor).isEqualTo(false)
                assertThat(noMirrorViewModel.showMirror).isEqualTo(false)
            }
        }
}
+3 −3
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge
import com.android.systemui.testKosmos
@@ -62,8 +63,7 @@ class QuickSettingsShadeOverlayActionsViewModelTest : SysuiTestCase() {
    fun back_notEditing_hidesShade() =
        testScope.runTest {
            val actions by collectLastValue(underTest.actions)
            val isEditing by
                collectLastValue(kosmos.quickSettingsContainerViewModel.editModeViewModel.isEditing)
            val isEditing by collectLastValue(kosmos.editModeViewModel.isEditing)
            underTest.activateIn(this)
            assertThat(isEditing).isFalse()

@@ -77,7 +77,7 @@ class QuickSettingsShadeOverlayActionsViewModelTest : SysuiTestCase() {
            val actions by collectLastValue(underTest.actions)
            underTest.activateIn(this)

            kosmos.quickSettingsContainerViewModel.editModeViewModel.startEditing()
            kosmos.editModeViewModel.startEditing()

            assertThat(actions?.get(Back)).isNull()
        }
+0 −183
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.qs.ui.viewmodel

import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@EnableSceneContainer
class QuickSettingsShadeUserActionsViewModelTest : SysuiTestCase() {

    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val sceneInteractor = kosmos.sceneInteractor
    private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor

    private val underTest by lazy { kosmos.quickSettingsShadeUserActionsViewModel }

    @Test
    fun upTransitionSceneKey_deviceLocked_lockscreen() =
        testScope.runTest {
            underTest.activateIn(this)
            val actions by collectLastValue(underTest.actions)
            val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
            lockDevice()

            assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
                .isEqualTo(SceneFamilies.Home)
            assertThat(actions?.get(Swipe.Down)).isNull()
            assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
        }

    @Test
    fun upTransitionSceneKey_deviceLocked_keyguardDisabled_gone() =
        testScope.runTest {
            underTest.activateIn(this)
            val actions by collectLastValue(underTest.actions)
            val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
            lockDevice()
            kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)

            assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
                .isEqualTo(SceneFamilies.Home)
            assertThat(homeScene).isEqualTo(Scenes.Gone)
        }

    @Test
    fun upTransitionSceneKey_deviceUnlocked_gone() =
        testScope.runTest {
            underTest.activateIn(this)
            val actions by collectLastValue(underTest.actions)
            val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
            lockDevice()
            unlockDevice()

            assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
                .isEqualTo(SceneFamilies.Home)
            assertThat(actions?.get(Swipe.Down)).isNull()
            assertThat(homeScene).isEqualTo(Scenes.Gone)
        }

    @Test
    fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
        testScope.runTest {
            underTest.activateIn(this)
            val actions by collectLastValue(underTest.actions)
            val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                AuthenticationMethodModel.None
            )
            sceneInteractor.changeScene(Scenes.Lockscreen, "reason")

            assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
                .isEqualTo(SceneFamilies.Home)
            assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
        }

    @Test
    fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
        testScope.runTest {
            underTest.activateIn(this)
            val actions by collectLastValue(underTest.actions)
            val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                AuthenticationMethodModel.None
            )
            runCurrent()
            sceneInteractor.changeScene(Scenes.Gone, "reason")

            assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
                .isEqualTo(SceneFamilies.Home)
            assertThat(homeScene).isEqualTo(Scenes.Gone)
        }

    @Test
    fun backTransitionSceneKey_notEditing_Home() =
        testScope.runTest {
            underTest.activateIn(this)
            val actions by collectLastValue(underTest.actions)

            assertThat((actions?.get(Back) as? UserActionResult.ChangeScene)?.toScene)
                .isEqualTo(SceneFamilies.Home)
        }

    @Test
    fun backTransition_editing_noDestination() =
        testScope.runTest {
            underTest.activateIn(this)
            val actions by collectLastValue(underTest.actions)
            kosmos.editModeViewModel.startEditing()

            assertThat(actions!!).isNotEmpty()
            assertThat(actions?.get(Back)).isNull()
        }

    private fun TestScope.lockDevice() {
        val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)

        kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
        assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
        sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
        runCurrent()
    }

    private fun TestScope.unlockDevice() {
        val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)

        kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
            SuccessFingerprintAuthenticationStatus(0, true)
        )
        assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
        sceneInteractor.changeScene(Scenes.Gone, "reason")
        runCurrent()
    }
}
+6 −1
Original line number Diff line number Diff line
@@ -128,6 +128,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.qs.QSFragmentLegacy;
import com.android.systemui.res.R;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.data.repository.ShadeAnimationRepository;
import com.android.systemui.shade.data.repository.ShadeRepository;
@@ -373,6 +374,9 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
    protected ShadeRepository mShadeRepository;
    protected FakeMSDLPlayer mMSDLPlayer = mKosmos.getMsdlPlayer();

    protected BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor =
            mKosmos.getBrightnessMirrorShowingInteractor();

    protected final FalsingManagerFake mFalsingManager = new FalsingManagerFake();
    protected final Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
    protected final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
@@ -752,7 +756,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
                mPowerInteractor,
                mKeyguardClockPositionAlgorithm,
                mNaturalScrollingSettingObserver,
                mMSDLPlayer);
                mMSDLPlayer,
                mBrightnessMirrorShowingInteractor);
        mNotificationPanelViewController.initDependencies(
                mCentralSurfaces,
                null,
+53 −1
Original line number Diff line number Diff line
@@ -51,7 +51,12 @@ import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.flags.QSComposeFragment
import com.android.systemui.res.R
import com.android.systemui.settings.brightness.data.repository.BrightnessMirrorShowingRepository
import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.statusbar.DragDownHelper
@@ -70,6 +75,7 @@ import com.android.systemui.statusbar.phone.DozeScrimController
import com.android.systemui.statusbar.phone.DozeServiceHost
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
import com.android.systemui.statusbar.window.StatusBarWindowStateController
import com.android.systemui.testKosmos
import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -78,11 +84,15 @@ import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
@@ -98,6 +108,7 @@ import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.clearInvocations
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters

@@ -107,6 +118,8 @@ import platform.test.runner.parameterized.Parameters
@RunWithLooper(setAsMainLooper = true)
class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : SysuiTestCase() {

    val kosmos = testKosmos()

    @Mock private lateinit var view: NotificationShadeWindowView
    @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
    @Mock private lateinit var centralSurfaces: CentralSurfaces
@@ -148,6 +161,10 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) :
    private val notificationLaunchAnimationInteractor =
        NotificationLaunchAnimationInteractor(notificationLaunchAnimationRepository)

    private val brightnessMirrorShowingRepository = BrightnessMirrorShowingRepository()
    private val brightnessMirrorShowingInteractor =
        BrightnessMirrorShowingInteractor(brightnessMirrorShowingRepository)

    private lateinit var falsingCollector: FalsingCollectorFake
    private lateinit var fakeClock: FakeSystemClock
    private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
@@ -181,8 +198,9 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) :
        featureFlagsClassic.set(SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
        mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)

        testScope = TestScope()
        testScope = kosmos.testScope
        testableLooper = TestableLooper.get(this)

        falsingCollector = FalsingCollectorFake()
        fakeClock = FakeSystemClock()
        underTest =
@@ -221,6 +239,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) :
                alternateBouncerInteractor,
                mock(BouncerViewBinder::class.java),
                mock(ConfigurationForwarder::class.java),
                brightnessMirrorShowingInteractor,
            )
        underTest.setupExpandedStatusBar()
        underTest.setDragDownHelper(dragDownHelper)
@@ -597,6 +616,39 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) :
        verify(dragDownHelper).stopDragging()
    }

    @Test
    @EnableFlags(QSComposeFragment.FLAG_NAME)
    fun mirrorShowing_depthControllerSet() =
        testScope.runTest {
            try {
                Dispatchers.setMain(kosmos.testDispatcher)

                // Simulate attaching the view so flow collection starts.
                whenever(view.viewTreeObserver).thenReturn(mock(ViewTreeObserver::class.java))
                val onAttachStateChangeListenerArgumentCaptor =
                    ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
                verify(view, atLeast(1))
                    .addOnAttachStateChangeListener(
                        onAttachStateChangeListenerArgumentCaptor.capture()
                    )
                for (listener in onAttachStateChangeListenerArgumentCaptor.allValues) {
                    listener.onViewAttachedToWindow(view)
                }
                testableLooper.processAllMessages()
                clearInvocations(notificationShadeDepthController)

                brightnessMirrorShowingInteractor.setMirrorShowing(true)
                runCurrent()
                verify(notificationShadeDepthController).brightnessMirrorVisible = true

                brightnessMirrorShowingInteractor.setMirrorShowing(false)
                runCurrent()
                verify(notificationShadeDepthController).brightnessMirrorVisible = false
            } finally {
                Dispatchers.resetMain()
            }
        }

    companion object {
        private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
        private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
Loading