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

Commit 1bd78efd authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add media composable to keyguard" into main

parents bb71cd50 a45a9293
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