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

Commit ef1bfc74 authored by Lucas Silva's avatar Lucas Silva Committed by Android (Google) Code Review
Browse files

Merge "Remove condition monitor from lowlight monitor" into main

parents ad69db70 2c99c773
Loading
Loading
Loading
Loading
+27 −50
Original line number Diff line number Diff line
@@ -32,17 +32,15 @@ import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.backgroundScope
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.lowlight.ambientLightModeMonitor
import com.android.systemui.lowlight.fake
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.shared.condition.Condition
import com.android.systemui.shared.condition.Monitor
import com.android.systemui.statusbar.commandline.commandRegistry
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
@@ -52,8 +50,6 @@ import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
import org.junit.Test
@@ -85,20 +81,13 @@ class LowLightMonitorTest : SysuiTestCase() {
            }
        }

    private val Kosmos.monitor: Monitor by Kosmos.Fixture { Monitor(testDispatcher.asExecutor()) }

    private val Kosmos.logger: LowLightLogger by
        Kosmos.Fixture { LowLightLogger(logcatLogBuffer()) }

    private val Kosmos.condition: FakeCondition by
        Kosmos.Fixture { FakeCondition(scope = applicationCoroutineScope, initialValue = false) }

    private val Kosmos.underTest: LowLightMonitor by
        Kosmos.Fixture {
            LowLightMonitor(
                lowLightDreamManager = { lowLightDreamManager },
                conditionsMonitor = monitor,
                lowLightConditions = { setOf(condition) },
                dreamSettingsInteractor = dreamSettingsInteractorKosmos,
                displayStateInteractor = displayStateInteractor,
                logger = logger,
@@ -109,6 +98,8 @@ class LowLightMonitorTest : SysuiTestCase() {
                userLockedInteractor = userLockedInteractor,
                keyguardInteractor = keyguardInteractor,
                powerInteractor = powerInteractor,
                ambientLightModeMonitor = ambientLightModeMonitor,
                uiEventLogger = mock(),
            )
        }

@@ -180,8 +171,7 @@ class LowLightMonitorTest : SysuiTestCase() {

            assertThat(mode).isEqualTo(LowLightDreamManager.AMBIENT_LIGHT_MODE_REGULAR)

            // Set conditions to true
            condition.setValue(true)
            setLowLightFromSensor(true)

            // Verify setting low light when condition is true
            assertThat(mode).isEqualTo(LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT)
@@ -197,9 +187,9 @@ class LowLightMonitorTest : SysuiTestCase() {
            setDisplayOn(true)

            // Set conditions to true then false
            condition.setValue(true)
            setLowLightFromSensor(true)
            assertThat(mode).isEqualTo(LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT)
            condition.setValue(false)
            setLowLightFromSensor(false)

            // Verify ambient light toggles back to light mode regular
            assertThat(mode).isEqualTo(LowLightDreamManager.AMBIENT_LIGHT_MODE_REGULAR)
@@ -211,11 +201,11 @@ class LowLightMonitorTest : SysuiTestCase() {
            underTest.start()

            setDisplayOn(true)
            assertThat(condition.started).isTrue()
            assertThat(ambientLightModeMonitor.fake.started).isTrue()

            // Verify removing subscription when screen turns off.
            setDisplayOn(false)
            assertThat(condition.started).isFalse()
            assertThat(ambientLightModeMonitor.fake.started).isFalse()
        }

    @Test
@@ -226,11 +216,11 @@ class LowLightMonitorTest : SysuiTestCase() {
            setDisplayOn(true)
            setDreamEnabled(true)

            assertThat(condition.started).isTrue()
            assertThat(ambientLightModeMonitor.fake.started).isTrue()

            setDreamEnabled(false)
            // Verify removing subscription when dream disabled.
            assertThat(condition.started).isFalse()
            assertThat(ambientLightModeMonitor.fake.started).isFalse()
        }

    @Test
@@ -239,7 +229,7 @@ class LowLightMonitorTest : SysuiTestCase() {
            setDisplayOn(true)

            underTest.start()
            assertThat(condition.started).isTrue()
            assertThat(ambientLightModeMonitor.fake.started).isTrue()
        }

    @Test
@@ -248,7 +238,7 @@ class LowLightMonitorTest : SysuiTestCase() {
            val mode by collectLastValue(ambientLightMode)
            setDisplayOn(true)
            setUserUnlocked(true)
            condition.setValue(true)
            setLowLightFromSensor(true)

            fakeKeyguardRepository.setKeyguardShowing(true)
            fakeKeyguardRepository.setDreaming(false)
@@ -268,7 +258,7 @@ class LowLightMonitorTest : SysuiTestCase() {
            setUserUnlocked(true)
            fakeKeyguardRepository.setKeyguardShowing(false)
            fakeKeyguardRepository.setDreaming(true)
            condition.setValue(true)
            setLowLightFromSensor(true)

            underTest.start()
            assertThat(mode).isEqualTo(LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT)
@@ -287,7 +277,7 @@ class LowLightMonitorTest : SysuiTestCase() {
            setUserUnlocked(true)
            fakeKeyguardRepository.setKeyguardShowing(false)
            fakeKeyguardRepository.setDreaming(true)
            condition.setValue(true)
            setLowLightFromSensor(true)

            assertThat(mode).isEqualTo(LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT)

@@ -306,7 +296,7 @@ class LowLightMonitorTest : SysuiTestCase() {
            dreamComponent = null

            underTest.start()
            assertThat(condition.started).isFalse()
            assertThat(ambientLightModeMonitor.fake.started).isFalse()
        }

    @Test
@@ -315,7 +305,7 @@ class LowLightMonitorTest : SysuiTestCase() {
            val mode by collectLastValue(ambientLightMode)
            setDisplayOn(true)
            // low-light condition not met
            condition.setValue(false)
            setLowLightFromSensor(false)

            underTest.start()
            assertThat(mode).isEqualTo(LowLightDreamManager.AMBIENT_LIGHT_MODE_REGULAR)
@@ -335,7 +325,7 @@ class LowLightMonitorTest : SysuiTestCase() {
            val mode by collectLastValue(ambientLightMode)
            setDisplayOn(true)
            // low-light condition is met
            condition.setValue(true)
            setLowLightFromSensor(true)

            underTest.start()
            assertThat(mode).isEqualTo(LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT)
@@ -355,7 +345,7 @@ class LowLightMonitorTest : SysuiTestCase() {
            val mode by collectLastValue(ambientLightMode)
            setDisplayOn(true)
            // low-light condition is false
            condition.setValue(false)
            setLowLightFromSensor(false)

            underTest.start()
            assertThat(mode).isEqualTo(LowLightDreamManager.AMBIENT_LIGHT_MODE_REGULAR)
@@ -369,26 +359,13 @@ class LowLightMonitorTest : SysuiTestCase() {
            assertThat(mode).isEqualTo(LowLightDreamManager.AMBIENT_LIGHT_MODE_REGULAR)
        }

    private class FakeCondition(
        scope: CoroutineScope,
        initialValue: Boolean?,
        overriding: Boolean = false,
        @StartStrategy override val startStrategy: Int = START_EAGERLY,
    ) : Condition(scope, initialValue, overriding) {
        private var _started = false
        val started: Boolean
            get() = _started

        override suspend fun start() {
            _started = true
        }

        override fun stop() {
            _started = false
        }

        fun setValue(value: Boolean?) {
            value?.also { updateCondition(value) } ?: clearCondition()
    private fun Kosmos.setLowLightFromSensor(lowlight: Boolean) {
        val lightMode =
            if (lowlight) {
                AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK
            } else {
                AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT
            }
        ambientLightModeMonitor.fake.setAmbientLightMode(lightMode)
    }
}
+45 −43
Original line number Diff line number Diff line
@@ -22,15 +22,52 @@ import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.util.Log
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lowlightclock.dagger.LowLightModule.Companion.LIGHT_SENSOR
import com.android.systemui.util.sensors.AsyncSensorManager
import java.io.PrintWriter
import java.util.Optional
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Provider

interface AmbientLightModeMonitor {
    companion object {
        const val AMBIENT_LIGHT_MODE_LIGHT = 0
        const val AMBIENT_LIGHT_MODE_DARK = 1
        const val AMBIENT_LIGHT_MODE_UNDECIDED = 2
    }

    // Represents all ambient light modes.
    @Retention(AnnotationRetention.SOURCE)
    @IntDef(AMBIENT_LIGHT_MODE_LIGHT, AMBIENT_LIGHT_MODE_DARK, AMBIENT_LIGHT_MODE_UNDECIDED)
    annotation class AmbientLightMode

    /** Interface of the ambient light mode callback, which gets triggered when the mode changes. */
    fun interface Callback {
        fun onChange(@AmbientLightMode mode: Int)
    }

    /** Interface of the algorithm that transforms light sensor events to an ambient light mode. */
    interface DebounceAlgorithm {
        // Setting Callback to nullable so mockito can verify without throwing NullPointerException.
        fun start(callback: Callback?)

        fun stop()

        fun onUpdateLightSensorEvent(value: Float)
    }

    /**
     * Start monitoring the current ambient light mode.
     *
     * @param callback callback that gets triggered when the ambient light mode changes.
     */
    fun start(callback: Callback)

    /** Stop monitoring the current ambient light mode. */
    fun stop()
}

/**
 * Monitors ambient light signals, applies a debouncing algorithm, and produces the current ambient
 * light mode.
@@ -39,33 +76,20 @@ import javax.inject.Provider
 *   light mode.
 * @property sensorManager the sensor manager used to register sensor event updates.
 */
class AmbientLightModeMonitor
@SysUISingleton
class AmbientLightModeMonitorImpl
@Inject
constructor(
    private val algorithm: Optional<DebounceAlgorithm>,
    private val algorithm: Optional<AmbientLightModeMonitor.DebounceAlgorithm>,
    private val sensorManager: AsyncSensorManager,
    @Named(LIGHT_SENSOR) private val lightSensor: Optional<Provider<Sensor>>,
) : Dumpable {
) : AmbientLightModeMonitor {
    companion object {
        private const val TAG = "AmbientLightModeMonitor"
        private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)

        const val AMBIENT_LIGHT_MODE_LIGHT = 0
        const val AMBIENT_LIGHT_MODE_DARK = 1
        const val AMBIENT_LIGHT_MODE_UNDECIDED = 2
    }

    // Represents all ambient light modes.
    @Retention(AnnotationRetention.SOURCE)
    @IntDef(AMBIENT_LIGHT_MODE_LIGHT, AMBIENT_LIGHT_MODE_DARK, AMBIENT_LIGHT_MODE_UNDECIDED)
    annotation class AmbientLightMode

    /**
     * Start monitoring the current ambient light mode.
     *
     * @param callback callback that gets triggered when the ambient light mode changes.
     */
    fun start(callback: Callback) {
    override fun start(callback: AmbientLightModeMonitor.Callback) {
        if (DEBUG) Log.d(TAG, "start monitoring ambient light mode")

        if (lightSensor.isEmpty || lightSensor.get().get() == null) {
@@ -87,7 +111,7 @@ constructor(
    }

    /** Stop monitoring the current ambient light mode. */
    fun stop() {
    override fun stop() {
        if (DEBUG) Log.d(TAG, "stop monitoring ambient light mode")

        if (algorithm.isPresent) {
@@ -96,13 +120,6 @@ constructor(
        sensorManager.unregisterListener(mSensorEventListener)
    }

    override fun dump(pw: PrintWriter, args: Array<out String>) {
        pw.println()
        pw.println("Ambient light mode monitor:")
        pw.println("  lightSensor=$lightSensor")
        pw.println()
    }

    private val mSensorEventListener: SensorEventListener =
        object : SensorEventListener {
            override fun onSensorChanged(event: SensorEvent) {
@@ -120,19 +137,4 @@ constructor(
                // Do nothing.
            }
        }

    /** Interface of the ambient light mode callback, which gets triggered when the mode changes. */
    fun interface Callback {
        fun onChange(@AmbientLightMode mode: Int)
    }

    /** Interface of the algorithm that transforms light sensor events to an ambient light mode. */
    interface DebounceAlgorithm {
        // Setting Callback to nullable so mockito can verify without throwing NullPointerException.
        fun start(callback: Callback?)

        fun stop()

        fun onUpdateLightSensorEvent(value: Float)
    }
}
+0 −65
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.lowlightclock

import com.android.internal.logging.UiEventLogger
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.shared.condition.Condition
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope

/** Condition for monitoring when the device enters and exits lowlight mode. */
class LowLightCondition
@Inject
constructor(
    @Background scope: CoroutineScope,
    private val ambientLightModeMonitor: AmbientLightModeMonitor,
    private val uiEventLogger: UiEventLogger,
) : Condition(scope) {
    override suspend fun start() {
        ambientLightModeMonitor.start { lowLightMode: Int -> onLowLightChanged(lowLightMode) }
    }

    override fun stop() {
        ambientLightModeMonitor.stop()

        // Reset condition met to false.
        updateCondition(false)
    }

    override val startStrategy: Int
        get() = // As this condition keeps the lowlight sensor active, it should only run when
            // needed.
            START_WHEN_NEEDED

    private fun onLowLightChanged(lowLightMode: Int) {
        if (lowLightMode == AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED) {
            // Ignore undecided mode changes.
            return
        }

        val isLowLight = lowLightMode == AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK
        if (isLowLight == isConditionMet) {
            // No change in condition, don't do anything.
            return
        }
        uiEventLogger.log(
            if (isLowLight) LowLightDockEvent.AMBIENT_LIGHT_TO_DARK
            else LowLightDockEvent.AMBIENT_LIGHT_TO_LIGHT
        )
        updateCondition(isLowLight)
    }
}
+28 −19
Original line number Diff line number Diff line
@@ -19,22 +19,19 @@ import android.content.ComponentName
import android.content.pm.PackageManager
import android.os.UserHandle
import com.android.dream.lowlight.LowLightDreamManager
import com.android.internal.logging.UiEventLogger
import com.android.systemui.CoreStartable
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.SystemUser
import com.android.systemui.dreams.dagger.DreamModule
import com.android.systemui.dreams.domain.interactor.DreamSettingsInteractor
import com.android.systemui.dreams.shared.model.WhenToDream
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.lowlightclock.dagger.LowLightModule
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.shared.condition.Condition
import com.android.systemui.shared.condition.Monitor
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.user.domain.interactor.UserLockedInteractor
import com.android.systemui.util.condition.ConditionalCoreStartable
import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
@@ -51,8 +48,10 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

@@ -64,9 +63,6 @@ class LowLightMonitor
@Inject
constructor(
    private val lowLightDreamManager: Lazy<LowLightDreamManager>,
    @param:SystemUser private val conditionsMonitor: Monitor,
    @param:Named(LowLightModule.LOW_LIGHT_PRECONDITIONS)
    private val lowLightConditions: Lazy<Set<Condition>>,
    dreamSettingsInteractor: DreamSettingsInteractor,
    displayStateInteractor: DisplayStateInteractor,
    private val logger: LowLightLogger,
@@ -78,7 +74,9 @@ constructor(
    private val userLockedInteractor: UserLockedInteractor,
    keyguardInteractor: KeyguardInteractor,
    powerInteractor: PowerInteractor,
) : ConditionalCoreStartable(conditionsMonitor) {
    private val ambientLightModeMonitor: AmbientLightModeMonitor,
    private val uiEventLogger: UiEventLogger,
) : CoreStartable {

    /** Whether the screen is currently on. */
    private val isScreenOn = not(displayStateInteractor.isDefaultDisplayOff).distinctUntilChanged()
@@ -96,16 +94,27 @@ constructor(
            .stateIn(scope = scope, started = SharingStarted.Eagerly, initialValue = null)

    /** Whether the device is currently in a low-light environment. */
    private val isLowLightFromSensor = conflatedCallbackFlow {
        val token =
            conditionsMonitor.addSubscription(
                Monitor.Subscription.Builder { trySend(it) }
                    .addConditions(lowLightConditions.get())
                    .build()
    private val isLowLightFromSensor =
        conflatedCallbackFlow {
                ambientLightModeMonitor.start { lowLightMode: Int -> trySend(lowLightMode) }
                awaitClose { ambientLightModeMonitor.stop() }
            }
            .filterNot { it == AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED }
            .map { it == AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK }
            .distinctUntilChanged()
            .onEach { isLowLight ->
                uiEventLogger.log(
                    if (isLowLight) LowLightDockEvent.AMBIENT_LIGHT_TO_DARK
                    else LowLightDockEvent.AMBIENT_LIGHT_TO_LIGHT
                )

        awaitClose { conditionsMonitor.removeSubscription(token) }
            }
            // AmbientLightModeMonitor only supports a single callback, so ensure this is re-used
            // if there are multiple subscribers.
            .stateIn(
                scope,
                started = SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
                initialValue = false,
            )

    private val isLowLight: Flow<Boolean> =
        combine(isLowLightForced, isLowLightFromSensor) { forcedValue, sensorValue ->
@@ -129,7 +138,7 @@ constructor(
            ),
        )

    override fun onStart() {
    override fun start() {
        scope.launch {
            if (lowLightDreamService != null) {
                // Note that the dream service is disabled by default. This prevents the dream from
+7 −9
Original line number Diff line number Diff line
@@ -23,34 +23,33 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.lowlightclock.AmbientLightModeMonitor
import com.android.systemui.lowlightclock.AmbientLightModeMonitor.DebounceAlgorithm
import com.android.systemui.lowlightclock.LowLightCondition
import com.android.systemui.lowlightclock.AmbientLightModeMonitorImpl
import com.android.systemui.lowlightclock.LowLightDisplayController
import com.android.systemui.lowlightclock.LowLightMonitor
import com.android.systemui.res.R
import com.android.systemui.shared.condition.Condition
import dagger.Binds
import dagger.BindsOptionalOf
import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import dagger.multibindings.IntoSet
import javax.inject.Named

@Module(includes = [LowLightDreamModule::class])
abstract class LowLightModule {
    @Binds
    @IntoSet
    @Named(LOW_LIGHT_PRECONDITIONS)
    abstract fun bindLowLightCondition(condition: LowLightCondition): Condition

    @BindsOptionalOf abstract fun bindsLowLightDisplayController(): LowLightDisplayController

    @BindsOptionalOf @Named(LIGHT_SENSOR) abstract fun bindsLightSensor(): Sensor

    @BindsOptionalOf abstract fun bindsDebounceAlgorithm(): DebounceAlgorithm

    @Binds
    abstract fun bindAmbientLightModeMonitor(
        impl: AmbientLightModeMonitorImpl
    ): AmbientLightModeMonitor

    /** Inject into LowLightMonitor. */
    @Binds
    @IntoMap
@@ -64,7 +63,6 @@ abstract class LowLightModule {
        const val ALPHA_ANIMATION_IN_START_DELAY_MILLIS: String =
            "alpha_animation_in_start_delay_millis"
        const val ALPHA_ANIMATION_DURATION_MILLIS: String = "alpha_animation_duration_millis"
        const val LOW_LIGHT_PRECONDITIONS: String = "low_light_preconditions"
        const val LIGHT_SENSOR: String = "low_light_monitor_light_sensor"

        /** Provides a [LogBuffer] for logs related to low-light features. */
Loading