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

Commit de5f1bae authored by Aaron Liu's avatar Aaron Liu Committed by Android (Google) Code Review
Browse files

Merge "Move clock centering and size logic from npvc." into main

parents 7cd10390 654d023f
Loading
Loading
Loading
Loading
+233 −0
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.keyguard.domain.interactor

import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardClockSwitch.LARGE
import com.android.keyguard.KeyguardClockSwitch.SMALL
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.keyguardClockRepository
import com.android.systemui.keyguard.data.repository.keyguardRepository
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.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardClockInteractorTest : SysuiTestCase() {
    private lateinit var kosmos: Kosmos
    private lateinit var underTest: KeyguardClockInteractor
    private lateinit var testScope: TestScope

    @Before
    fun setup() {
        kosmos = testKosmos()
        testScope = kosmos.testScope
        underTest = kosmos.keyguardClockInteractor
    }

    @Test
    @DisableFlags(AConfigFlags.FLAG_SCENE_CONTAINER)
    fun clockSize_sceneContainerFlagOff_basedOnRepository() =
        testScope.runTest {
            val value by collectLastValue(underTest.clockSize)
            kosmos.keyguardClockRepository.setClockSize(LARGE)
            assertThat(value).isEqualTo(LARGE)

            kosmos.keyguardClockRepository.setClockSize(SMALL)
            assertThat(value).isEqualTo(SMALL)
        }

    @Test
    @DisableFlags(AConfigFlags.FLAG_SCENE_CONTAINER)
    fun clockShouldBeCentered_sceneContainerFlagOff_basedOnRepository() =
        testScope.runTest {
            val value by collectLastValue(underTest.clockShouldBeCentered)
            kosmos.keyguardInteractor.setClockShouldBeCentered(true)
            assertThat(value).isEqualTo(true)

            kosmos.keyguardInteractor.setClockShouldBeCentered(false)
            assertThat(value).isEqualTo(false)
        }

    @Test
    @EnableSceneContainer
    fun clockSize_forceSmallClock_SMALL() =
        testScope.runTest {
            val value by collectLastValue(underTest.clockSize)
            kosmos.fakeKeyguardClockRepository.setShouldForceSmallClock(true)
            kosmos.fakeFeatureFlagsClassic.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, true)
            transitionTo(KeyguardState.AOD, KeyguardState.LOCKSCREEN)
            assertThat(value).isEqualTo(SMALL)
        }

    @Test
    @EnableSceneContainer
    fun clockSize_SceneContainerFlagOn_shadeModeSingle_hasNotifs_SMALL() =
        testScope.runTest {
            val value by collectLastValue(underTest.clockSize)
            kosmos.shadeRepository.setShadeMode(ShadeMode.Single)
            kosmos.activeNotificationListRepository.setActiveNotifs(1)
            assertThat(value).isEqualTo(SMALL)
        }

    @Test
    @EnableSceneContainer
    fun clockSize_SceneContainerFlagOn_shadeModeSingle_hasMedia_SMALL() =
        testScope.runTest {
            val value by collectLastValue(underTest.clockSize)
            kosmos.shadeRepository.setShadeMode(ShadeMode.Single)
            val userMedia = MediaData().copy(active = true)
            kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
            assertThat(value).isEqualTo(SMALL)
        }

    @Test
    @EnableSceneContainer
    fun clockSize_SceneContainerFlagOn_shadeModeSplit_isMediaVisible_SMALL() =
        testScope.runTest {
            val value by collectLastValue(underTest.clockSize)
            val userMedia = MediaData().copy(active = true)
            kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
            kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
            kosmos.keyguardRepository.setIsDozing(false)
            assertThat(value).isEqualTo(SMALL)
        }

    @Test
    @EnableSceneContainer
    fun clockSize_SceneContainerFlagOn_shadeModeSplit_noMedia_LARGE() =
        testScope.runTest {
            val value by collectLastValue(underTest.clockSize)
            kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
            kosmos.keyguardRepository.setIsDozing(false)
            assertThat(value).isEqualTo(LARGE)
        }

    @Test
    @EnableSceneContainer
    fun clockSize_SceneContainerFlagOn_shadeModeSplit_isDozing_LARGE() =
        testScope.runTest {
            val value by collectLastValue(underTest.clockSize)
            val userMedia = MediaData().copy(active = true)
            kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
            kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
            kosmos.keyguardRepository.setIsDozing(true)
            assertThat(value).isEqualTo(LARGE)
        }

    @Test
    @EnableSceneContainer
    fun clockShouldBeCentered_sceneContainerFlagOn_notSplitMode_true() =
        testScope.runTest {
            val value by collectLastValue(underTest.clockShouldBeCentered)
            kosmos.shadeRepository.setShadeMode(ShadeMode.Single)
            assertThat(value).isEqualTo(true)
        }

    @Test
    @EnableSceneContainer
    fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_noActiveNotifications_true() =
        testScope.runTest {
            val value by collectLastValue(underTest.clockShouldBeCentered)
            kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
            kosmos.activeNotificationListRepository.setActiveNotifs(0)
            assertThat(value).isEqualTo(true)
        }

    @Test
    @EnableSceneContainer
    fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_isActiveDreamLockscreenHosted_true() =
        testScope.runTest {
            val value by collectLastValue(underTest.clockShouldBeCentered)
            kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
            kosmos.activeNotificationListRepository.setActiveNotifs(1)
            kosmos.keyguardRepository.setIsActiveDreamLockscreenHosted(true)
            assertThat(value).isEqualTo(true)
        }

    @Test
    @EnableSceneContainer
    fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_hasPulsingNotifications_false() =
        testScope.runTest {
            val value by collectLastValue(underTest.clockShouldBeCentered)
            kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
            kosmos.activeNotificationListRepository.setActiveNotifs(1)
            kosmos.headsUpNotificationRepository.headsUpAnimatingAway.value = true
            kosmos.keyguardRepository.setIsDozing(true)
            assertThat(value).isEqualTo(false)
        }

    @Test
    @EnableSceneContainer
    fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_onAod_true() =
        testScope.runTest {
            val value by collectLastValue(underTest.clockShouldBeCentered)
            kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
            kosmos.activeNotificationListRepository.setActiveNotifs(1)
            transitionTo(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
            assertThat(value).isEqualTo(true)
        }

    @Test
    @EnableSceneContainer
    fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_offAod_false() =
        testScope.runTest {
            val value by collectLastValue(underTest.clockShouldBeCentered)
            kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
            kosmos.activeNotificationListRepository.setActiveNotifs(1)
            transitionTo(KeyguardState.AOD, KeyguardState.LOCKSCREEN)
            assertThat(value).isEqualTo(false)
        }

    private suspend fun transitionTo(from: KeyguardState, to: KeyguardState) {
        kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
            TransitionStep(from, to, 0f, TransitionState.STARTED)
        )
        kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
            TransitionStep(from, to, 0.5f, TransitionState.RUNNING)
        )
        kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
            TransitionStep(from, to, 1f, TransitionState.FINISHED)
        )
    }
}
+20 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.keyguard.data.repository

import android.content.Context
import android.os.UserHandle
import android.provider.Settings
import com.android.keyguard.ClockEventController
@@ -24,9 +25,12 @@ import com.android.keyguard.KeyguardClockSwitch.LARGE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.shared.model.SettingsClockSize
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.res.R
import com.android.systemui.shared.clocks.ClockRegistry
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
@@ -47,7 +51,11 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext

interface KeyguardClockRepository {
    /** clock size determined by notificationPanelViewController, LARGE or SMALL */
    /**
     * clock size determined by notificationPanelViewController, LARGE or SMALL
     *
     * @deprecated When scene container flag is on use clockSize from domain level.
     */
    val clockSize: StateFlow<Int>

    /** clock size selected in picker, DYNAMIC or SMALL */
@@ -61,6 +69,9 @@ interface KeyguardClockRepository {
    val previewClock: Flow<ClockController>

    val clockEventController: ClockEventController

    val shouldForceSmallClock: Boolean

    fun setClockSize(@ClockSize size: Int)
}

@@ -73,6 +84,8 @@ constructor(
    override val clockEventController: ClockEventController,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
    @Application private val applicationScope: CoroutineScope,
    @Application private val applicationContext: Context,
    private val featureFlags: FeatureFlagsClassic,
) : KeyguardClockRepository {

    /** Receive SMALL or LARGE clock should be displayed on keyguard. */
@@ -135,6 +148,12 @@ constructor(
            clockRegistry.createCurrentClock()
        }

    override val shouldForceSmallClock: Boolean
        get() =
            featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE) &&
                // True on small landscape screens
                applicationContext.resources.getBoolean(R.bool.force_small_clock_on_lockscreen)

    private fun getClockSize(): SettingsClockSize {
        return if (
            secureSettings.getIntForUser(
+5 −1
Original line number Diff line number Diff line
@@ -206,7 +206,11 @@ interface KeyguardRepository {
    )
    val keyguardDoneAnimationsFinished: Flow<Unit>

    /** Receive whether clock should be centered on lockscreen. */
    /**
     * Receive whether clock should be centered on lockscreen.
     *
     * @deprecated When scene container flag is on use clockShouldBeCentered from domain level.
     */
    val clockShouldBeCentered: Flow<Boolean>

    /**
+84 −2
Original line number Diff line number Diff line
@@ -21,23 +21,48 @@ import android.util.Log
import com.android.keyguard.ClockEventController
import com.android.keyguard.KeyguardClockSwitch
import com.android.keyguard.KeyguardClockSwitch.ClockSize
import com.android.keyguard.KeyguardClockSwitch.LARGE
import com.android.keyguard.KeyguardClockSwitch.SMALL
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardClockRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SettingsClockSize
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.util.kotlin.combine
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
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

private val TAG = KeyguardClockInteractor::class.simpleName
/** Manages and ecapsulates the clock components of the lockscreen root view. */
/** Manages and encapsulates the clock components of the lockscreen root view. */
@SysUISingleton
class KeyguardClockInteractor
@Inject
constructor(
    mediaCarouselInteractor: MediaCarouselInteractor,
    activeNotificationsInteractor: ActiveNotificationsInteractor,
    shadeInteractor: ShadeInteractor,
    keyguardInteractor: KeyguardInteractor,
    keyguardTransitionInteractor: KeyguardTransitionInteractor,
    headsUpNotificationInteractor: HeadsUpNotificationInteractor,
    @Application private val applicationScope: CoroutineScope,
    private val keyguardClockRepository: KeyguardClockRepository,
) {
    private val isOnAod: Flow<Boolean> =
        keyguardTransitionInteractor.currentKeyguardState.map { it == KeyguardState.AOD }

    val selectedClockSize: StateFlow<SettingsClockSize> = keyguardClockRepository.selectedClockSize

@@ -51,7 +76,64 @@ constructor(

    var clock: ClockController? by keyguardClockRepository.clockEventController::clock

    val clockSize: StateFlow<Int> = keyguardClockRepository.clockSize
    // TODO (b/333389512): Convert this into a more readable enum.
    val clockSize: StateFlow<Int> =
        if (SceneContainerFlag.isEnabled) {
            combine(
                    shadeInteractor.shadeMode,
                    activeNotificationsInteractor.areAnyNotificationsPresent,
                    mediaCarouselInteractor.hasActiveMediaOrRecommendation,
                    keyguardInteractor.isDozing,
                    isOnAod,
                ) { shadeMode, hasNotifs, hasMedia, isDozing, isOnAod ->
                    return@combine when {
                        keyguardClockRepository.shouldForceSmallClock && !isOnAod -> SMALL
                        shadeMode == ShadeMode.Single && (hasNotifs || hasMedia) -> SMALL
                        shadeMode == ShadeMode.Single -> LARGE
                        hasMedia && !isDozing -> SMALL
                        else -> LARGE
                    }
                }
                .stateIn(
                    scope = applicationScope,
                    started = SharingStarted.WhileSubscribed(),
                    initialValue = LARGE
                )
        } else {
            SceneContainerFlag.assertInLegacyMode()
            keyguardClockRepository.clockSize
        }

    val clockShouldBeCentered: Flow<Boolean> =
        if (SceneContainerFlag.isEnabled) {
            combine(
                shadeInteractor.shadeMode,
                activeNotificationsInteractor.areAnyNotificationsPresent,
                keyguardInteractor.isActiveDreamLockscreenHosted,
                isOnAod,
                headsUpNotificationInteractor.isHeadsUpOrAnimatingAway,
                keyguardInteractor.isDozing,
            ) {
                shadeMode,
                areAnyNotificationsPresent,
                isActiveDreamLockscreenHosted,
                isOnAod,
                isHeadsUp,
                isDozing ->
                when {
                    shadeMode != ShadeMode.Split -> true
                    !areAnyNotificationsPresent -> true
                    isActiveDreamLockscreenHosted -> true
                    // Pulsing notification appears on the right. Move clock left to avoid overlap.
                    isHeadsUp && isDozing -> false
                    else -> isOnAod
                }
            }
        } else {
            SceneContainerFlag.assertInLegacyMode()
            keyguardInteractor.clockShouldBeCentered
        }

    fun setClockSize(@ClockSize size: Int) {
        keyguardClockRepository.setClockSize(size)
    }
+3 −5
Original line number Diff line number Diff line
@@ -26,7 +26,6 @@ import com.android.systemui.customization.R as customizationR
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.keyguard.shared.model.SettingsClockSize
import com.android.systemui.res.R
@@ -46,11 +45,10 @@ import kotlinx.coroutines.flow.stateIn
class KeyguardClockViewModel
@Inject
constructor(
    keyguardInteractor: KeyguardInteractor,
    private val keyguardClockInteractor: KeyguardClockInteractor,
    keyguardClockInteractor: KeyguardClockInteractor,
    @Application private val applicationScope: CoroutineScope,
    notifsKeyguardInteractor: NotificationsKeyguardInteractor,
    @VisibleForTesting val shadeInteractor: ShadeInteractor,
    @get:VisibleForTesting val shadeInteractor: ShadeInteractor,
) {
    var burnInLayer: Layer? = null
    val useLargeClock: Boolean
@@ -99,7 +97,7 @@ constructor(
            )

    val clockShouldBeCentered: StateFlow<Boolean> =
        keyguardInteractor.clockShouldBeCentered.stateIn(
        keyguardClockInteractor.clockShouldBeCentered.stateIn(
            scope = applicationScope,
            started = SharingStarted.WhileSubscribed(),
            initialValue = false
Loading