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

Commit a45a9293 authored by Michael Mikhail's avatar Michael Mikhail
Browse files

Add media composable to keyguard

Flag: com.android.systemui.media_controls_in_compose
Fixes: 434231767
Test: atest SystemUiRoboTests:MediaRepositoryTest
Test: atest SystemUiRoboTests:MediaCarouselInteractorTest
Test: atest SystemUiRoboTests:KeyguardMediaControllerTest
Test: atest SystemUiRoboTests:QSFragmentComposeViewModelTest
Change-Id: I25a35bafb367342131ee084c5fb511acd2867e74
parent bfbd5668
Loading
Loading
Loading
Loading
+205 −3
Original line number Diff line number Diff line
@@ -16,34 +16,57 @@

package com.android.systemui.media.controls.domain.interactor

import android.os.UserHandle
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.remedia.data.repository.MediaPipelineRepository
import com.android.systemui.media.remedia.data.repository.mediaPipelineRepository
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
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 MediaCarouselInteractorTest : SysuiTestCase() {

    private val kosmos = testKosmos()
    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val testScope = kosmos.testScope
    private val mediaPipelineRepository = kosmos.mediaPipelineRepository
    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
    private lateinit var mediaPipelineRepository: MediaPipelineRepository

    private val underTest: MediaCarouselInteractor = kosmos.mediaCarouselInteractor
    private lateinit var underTest: MediaCarouselInteractor

    @Before
    fun setUp() {
        mediaPipelineRepository = kosmos.mediaPipelineRepository
        underTest = kosmos.mediaCarouselInteractor
        underTest.start()
    }

@@ -96,4 +119,183 @@ class MediaCarouselInteractorTest : SysuiTestCase() {
    @Test
    fun hasActiveMedia_noMediaSet_returnsFalse() =
        testScope.runTest { assertThat(underTest.hasActiveMedia()).isFalse() }

    @Test
    fun onLockscreen_mediaAllowed_lockedAndHidden_returnsFalse() =
        testScope.runTest {
            val isLockedAndHidden by collectLastValue(underTest.isLockedAndHidden)

            transitionRepository.sendTransitionSteps(
                from = KeyguardState.GONE,
                to = KeyguardState.LOCKSCREEN,
                this,
            )

            assertThat(isLockedAndHidden).isFalse()
        }

    @Test
    fun onLockscreen_mediaNotAllowed_lockedAndHidden_returnsTrue() =
        testScope.runTest {
            val isLockedAndHidden by collectLastValue(underTest.isLockedAndHidden)

            kosmos.fakeSettings.putBoolForUser(
                Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
                false,
                UserHandle.USER_CURRENT,
            )
            transitionRepository.sendTransitionSteps(
                from = KeyguardState.GONE,
                to = KeyguardState.LOCKSCREEN,
                this,
            )

            assertThat(isLockedAndHidden).isTrue()
        }

    @Test
    fun onKeyguardGone_mediaAllowed_lockedAndHidden_returnsFalse() =
        testScope.runTest {
            val isLockedAndHidden by collectLastValue(underTest.isLockedAndHidden)

            transitionRepository.sendTransitionSteps(
                from = KeyguardState.LOCKSCREEN,
                to = KeyguardState.GONE,
                this,
            )

            assertThat(isLockedAndHidden).isFalse()
        }

    @Test
    fun onKeyguardGone_mediaNotAllowed_lockedAndHidden_returnsFalse() =
        testScope.runTest {
            val isLockedAndHidden by collectLastValue(underTest.isLockedAndHidden)

            kosmos.fakeSettings.putBoolForUser(
                Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
                false,
                UserHandle.USER_CURRENT,
            )
            transitionRepository.sendTransitionSteps(
                from = KeyguardState.LOCKSCREEN,
                to = KeyguardState.GONE,
                this,
            )

            assertThat(isLockedAndHidden).isFalse()
        }

    @Test
    fun goingToDozing_mediaAllowed_lockedAndHidden_returnsFalse() =
        testScope.runTest {
            val isLockedAndHidden by collectLastValue(underTest.isLockedAndHidden)

            transitionRepository.sendTransitionSteps(
                from = KeyguardState.GONE,
                to = KeyguardState.DOZING,
                this,
            )

            assertThat(isLockedAndHidden).isFalse()
        }

    @Test
    fun goingToDozing_mediaNotAllowed_lockedAndHidden_returnsTrue() =
        testScope.runTest {
            val isLockedAndHidden by collectLastValue(underTest.isLockedAndHidden)

            kosmos.fakeSettings.putBoolForUser(
                Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
                false,
                UserHandle.USER_CURRENT,
            )
            transitionRepository.sendTransitionSteps(
                from = KeyguardState.GONE,
                to = KeyguardState.DOZING,
                this,
            )

            assertThat(isLockedAndHidden).isTrue()
        }

    @EnableSceneContainer
    @Test
    fun deviceNotEntered_mediaNotAllowed_lockedAndHidden() =
        testScope.runTest {
            val isLockedAndHidden by collectLastValue(underTest.isLockedAndHidden)

            kosmos.fakeSettings.putBoolForUser(
                Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
                false,
                UserHandle.USER_CURRENT,
            )
            setDeviceEntered(false)

            assertThat(isLockedAndHidden).isTrue()
        }

    @EnableSceneContainer
    @Test
    fun deviceNotEntered_mediaAllowed_notLockedAndHidden() =
        testScope.runTest {
            val isLockedAndHidden by collectLastValue(underTest.isLockedAndHidden)

            setDeviceEntered(false)

            assertThat(isLockedAndHidden).isFalse()
        }

    @EnableSceneContainer
    @Test
    fun deviceEntered_mediaNotAllowed_notLockedAndHidden() =
        testScope.runTest {
            val isLockedAndHidden by collectLastValue(underTest.isLockedAndHidden)

            kosmos.fakeSettings.putBoolForUser(
                Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
                false,
                UserHandle.USER_CURRENT,
            )
            setDeviceEntered(true)

            assertThat(isLockedAndHidden).isFalse()
        }

    @EnableSceneContainer
    @Test
    fun deviceEntered_mediaAllowed_notLockedAndHidden() =
        testScope.runTest {
            val isLockedAndHidden by collectLastValue(underTest.isLockedAndHidden)

            setDeviceEntered(true)

            assertThat(isLockedAndHidden).isFalse()
        }

    private fun TestScope.setDeviceEntered(isEntered: Boolean) {
        if (isEntered) {
            // Unlock the device, marking the device as entered
            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                SuccessFingerprintAuthenticationStatus(0, true)
            )
            runCurrent()
        }
        setScene(
            if (isEntered) {
                Scenes.Gone
            } else {
                Scenes.Lockscreen
            }
        )
        assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isEqualTo(isEntered)
    }

    private fun TestScope.setScene(key: SceneKey) {
        kosmos.sceneInteractor.changeScene(key, "test")
        kosmos.sceneInteractor.setTransitionState(
            MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
        )
        runCurrent()
    }
}
+75 −7
Original line number Diff line number Diff line
@@ -16,16 +16,29 @@

package com.android.systemui.media.controls.ui.controller

import android.os.UserHandle
import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import android.testing.TestableLooper
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.FrameLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.media.remedia.data.repository.setHasMedia
import com.android.systemui.media.remedia.shared.flag.MediaControlsInComposeFlag
import com.android.systemui.media.remedia.ui.viewmodel.factory.mediaViewModelFactory
import com.android.systemui.media.remedia.ui.viewmodel.mediaFalsingSystem
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -33,11 +46,14 @@ import com.android.systemui.statusbar.notification.stack.MediaContainerView
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.testKosmos
import com.android.systemui.util.animation.UniqueObjectHostView
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertTrue
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -50,7 +66,7 @@ import org.mockito.junit.MockitoJUnit

@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class KeyguardMediaControllerTest : SysuiTestCase() {

    @Mock private lateinit var mediaHost: MediaHost
@@ -60,6 +76,11 @@ class KeyguardMediaControllerTest : SysuiTestCase() {

    @JvmField @Rule val mockito = MockitoJUnit.rule()

    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val mediaFalsingSystem = kosmos.mediaFalsingSystem
    private val mediaViewModelFactory = kosmos.mediaViewModelFactory
    private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
    private val mediaContainerView: MediaContainerView = MediaContainerView(context, null)
    private val hostView = UniqueObjectHostView(context)
    private lateinit var keyguardMediaController: KeyguardMediaController
@@ -78,9 +99,15 @@ class KeyguardMediaControllerTest : SysuiTestCase() {
        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
        whenever(mediaHost.hostView).thenReturn(hostView)
        hostView.layoutParams = FrameLayout.LayoutParams(100, 100)
        kosmos.fakeSettings.putBoolForUser(
            Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
            true,
            UserHandle.USER_CURRENT,
        )
        keyguardMediaController =
            KeyguardMediaController(
                mediaHost,
                kosmos.applicationCoroutineScope,
                bypassController,
                statusBarStateController,
                context,
@@ -88,20 +115,66 @@ class KeyguardMediaControllerTest : SysuiTestCase() {
                ResourcesSplitShadeStateController(),
                mock<KeyguardMediaControllerLogger>(),
                mock<DumpManager>(),
                mediaViewModelFactory,
                kosmos.mediaCarouselInteractor,
                mediaFalsingSystem,
            )
        keyguardMediaController.attachSinglePaneContainer(mediaContainerView)
        keyguardMediaController.useSplitShade = false

        if (MediaControlsInComposeFlag.isEnabled) {
            kosmos.setHasMedia(visible = true, active = true)
        } else {
            verify(mediaHost).expansion = MediaHostState.EXPANDED
        }
    }

    @Test
    fun testHiddenWhenHostIsHidden() {
        if (MediaControlsInComposeFlag.isEnabled) {
            kosmos.setHasMedia(visible = false)
        } else {
            whenever(mediaHost.visible).thenReturn(false)
        }

        keyguardMediaController.refreshMediaPosition(TEST_REASON)

        assertThat(mediaContainerView.visibility).isEqualTo(GONE)
    }

    @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_IN_COMPOSE)
    @Test
    fun mediaLockedAndHidden_mediaIsHidden() =
        testScope.runTest {
            kosmos.fakeSettings.putBoolForUser(
                Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
                false,
                UserHandle.USER_CURRENT,
            )
            transitionRepository.sendTransitionSteps(
                from = KeyguardState.GONE,
                to = KeyguardState.LOCKSCREEN,
                this,
            )
            kosmos.setHasMedia(visible = true, active = true)

            assertThat(mediaContainerView.visibility).isEqualTo(GONE)
        }

    @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_IN_COMPOSE)
    @Test
    fun mediaOnLockscreen_mediaIsVisible() =
        testScope.runTest {
            transitionRepository.sendTransitionSteps(
                from = KeyguardState.GONE,
                to = KeyguardState.LOCKSCREEN,
                this,
            )
            kosmos.setHasMedia(visible = true, active = true)

            assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE)
        }

    @Test
    fun testVisibleOnKeyguardOrFullScreenUserSwitcher() {
        testStateVisibility(StatusBarState.SHADE, GONE)
@@ -156,11 +229,6 @@ class KeyguardMediaControllerTest : SysuiTestCase() {
        )
    }

    @Test
    fun testMediaHost_expandedPlayer() {
        verify(mediaHost).expansion = MediaHostState.EXPANDED
    }

    @Test
    fun dozing_inSplitShade_mediaIsHidden() {
        val splitShadeContainer = FrameLayout(context)
+27 −0
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package com.android.systemui.media.remedia.data.repository

import android.content.packageManager
import android.media.session.MediaSession
import android.os.UserHandle
import android.provider.Settings
import androidx.test.annotation.UiThreadTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -30,6 +32,7 @@ import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.remedia.data.model.MediaDataModel
import com.android.systemui.res.R
import com.android.systemui.testKosmos
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
@@ -263,6 +266,30 @@ class MediaRepositoryTest : SysuiTestCase() {
                .inOrder()
        }

    @Test
    fun toggleMediaControlsOnLockscreen() =
        testScope.runTest {
            val allowMediaOnLockscreen by collectLastValue(underTest.allowMediaPlayerOnLockscreen)

            assertThat(allowMediaOnLockscreen).isTrue()

            kosmos.fakeSettings.putBoolForUser(
                Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
                value = false,
                UserHandle.USER_CURRENT,
            )

            assertThat(allowMediaOnLockscreen).isFalse()

            kosmos.fakeSettings.putBoolForUser(
                Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
                value = true,
                UserHandle.USER_CURRENT,
            )

            assertThat(allowMediaOnLockscreen).isTrue()
        }

    private fun TestScope.addCurrentUserMediaEntry(data: MediaData) {
        underTest.addCurrentUserMediaEntry(data)
        runCurrent()
+14 −1
Original line number Diff line number Diff line
@@ -20,15 +20,19 @@ import android.content.Context
import com.android.internal.logging.InstanceId
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.media.controls.data.model.MediaSortKeyModel
import com.android.systemui.media.controls.shared.model.MediaCommonModel
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.remedia.data.model.UpdateArtInfoModel
import com.android.systemui.media.remedia.data.repository.MediaPipelineRepository
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.time.SystemClock
import java.util.TreeMap
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow

@@ -38,8 +42,17 @@ class MediaFilterRepository
@Inject
constructor(
    @Application private val applicationContext: Context,
    @Application applicationScope: CoroutineScope,
    @Background backgroundDispatcher: CoroutineDispatcher,
    private val systemClock: SystemClock,
) : MediaPipelineRepository(applicationContext) {
    secureSettings: SecureSettings,
) :
    MediaPipelineRepository(
        applicationContext,
        applicationScope,
        backgroundDispatcher,
        secureSettings,
    ) {

    private val _currentMedia: MutableStateFlow<List<MediaCommonModel>> =
        MutableStateFlow(mutableListOf())
+32 −2
Original line number Diff line number Diff line
@@ -25,6 +25,11 @@ import com.android.internal.logging.InstanceId
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.media.controls.data.repository.MediaFilterRepository
import com.android.systemui.media.controls.domain.pipeline.MediaDataCombineLatest
import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl
@@ -37,12 +42,15 @@ import com.android.systemui.media.controls.domain.resume.MediaResumeListener
import com.android.systemui.media.remedia.data.repository.MediaPipelineRepository
import com.android.systemui.media.remedia.shared.flag.MediaControlsInComposeFlag
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

@@ -60,6 +68,8 @@ constructor(
    private val mediaDataCombineLatest: MediaDataCombineLatest,
    private val mediaDataFilter: MediaDataFilterImpl,
    private val mediaPipelineRepository: MediaPipelineRepository,
    keyguardTransitionInteractor: KeyguardTransitionInteractor,
    deviceEntryInteractor: DeviceEntryInteractor,
) : MediaDataManager, CoreStartable {

    /** Are there any media notifications active? */
@@ -91,6 +101,27 @@ constructor(
            MutableStateFlow(mutableListOf())
        }

    val allowMediaOnLockscreen: StateFlow<Boolean> =
        mediaPipelineRepository.allowMediaPlayerOnLockscreen

    internal val isOnLockscreen: Flow<Boolean> =
        combine(
            @Suppress("DEPRECATION") keyguardTransitionInteractor.isFinishedIn(Scenes.Gone, GONE),
            keyguardTransitionInteractor.isInTransition(Edge.create(to = DOZING)),
            deviceEntryInteractor.isDeviceEntered,
        ) { isGone, isGoingToDozing, deviceEntered ->
            if (SceneContainerFlag.isEnabled) {
                !deviceEntered
            } else {
                !isGone || isGoingToDozing
            }
        }

    val isLockedAndHidden =
        combine(allowMediaOnLockscreen, isOnLockscreen) { allowMedia, onLockscreen ->
            !allowMedia && onLockscreen
        }

    override fun start() {
        if (!SceneContainerFlag.isEnabled && !MediaControlsInComposeFlag.isEnabled) {
            return
@@ -212,8 +243,7 @@ constructor(
        val unsupported: Nothing
            get() =
                error(
                    "Code path not supported when ${SceneContainerFlag.DESCRIPTION} or " +
                        "media_controls_in_compose is enabled"
                    "Code path not supported when ${SceneContainerFlag.DESCRIPTION} or media_controls_in_compose is enabled"
                )
    }
}
Loading