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

Commit 5a43e35b authored by Lucas Silva's avatar Lucas Silva
Browse files

Grab wakelock when we may become postured soon.

If the latest event in the sliding window has a higher confidence then
the threshold, then assuming no new events come in, we will eventually
become postured. Therefore we can use this as a signal to know that we
may be postured in the near future, and grab a wakelock to keep the
device awake.

This helps ensure that the device doesn't go to sleep before we have a
chance to start dreaming.

Fixes: 409896203
Test: atest PosturingInteractorTest
Flag: android.service.dreams.allow_dream_when_postured
Change-Id: Id0b39ceec5d756b4c9fb10e7ca1b722cb9fc45d5
parent fe76d80f
Loading
Loading
Loading
Loading
+5 −4
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import com.android.systemui.communal.posturing.data.model.PositionState
import com.android.systemui.communal.posturing.data.repository.fake
import com.android.systemui.communal.posturing.data.repository.posturingRepository
import com.android.systemui.communal.posturing.domain.interactor.advanceTimeBySlidingWindowAndRun
import com.android.systemui.communal.posturing.shared.model.ConfidenceLevel
import com.android.systemui.dock.DockManager
import com.android.systemui.dock.fakeDockManager
import com.android.systemui.kosmos.Kosmos
@@ -136,8 +137,8 @@ class CommunalAutoOpenInteractorTest : SysuiTestCase() {
            batteryRepository.fake.setDevicePluggedIn(true)
            posturingRepository.fake.emitPositionState(
                PositionState(
                    stationary = PositionState.StationaryState.Stationary(confidence = 1f),
                    orientation = PositionState.OrientationState.NotPostured(confidence = 1f),
                    stationary = ConfidenceLevel.Positive(confidence = 1f),
                    orientation = ConfidenceLevel.Negative(confidence = 1f),
                )
            )

@@ -150,8 +151,8 @@ class CommunalAutoOpenInteractorTest : SysuiTestCase() {
            advanceTimeBy(1.milliseconds)
            posturingRepository.fake.emitPositionState(
                PositionState(
                    stationary = PositionState.StationaryState.Stationary(confidence = 1f),
                    orientation = PositionState.OrientationState.Postured(confidence = 1f),
                    stationary = ConfidenceLevel.Positive(confidence = 1f),
                    orientation = ConfidenceLevel.Positive(confidence = 1f),
                )
            )
            advanceTimeBySlidingWindowAndRun()
+66 −18
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.posturing.data.model.PositionState
import com.android.systemui.communal.posturing.data.repository.fake
import com.android.systemui.communal.posturing.data.repository.posturingRepository
import com.android.systemui.communal.posturing.shared.model.ConfidenceLevel
import com.android.systemui.communal.posturing.shared.model.PosturedState
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.advanceTimeBy
@@ -63,8 +64,8 @@ class PosturingInteractorTest : SysuiTestCase() {

            posturingRepository.fake.emitPositionState(
                PositionState(
                    stationary = PositionState.StationaryState.Stationary(confidence = 1f),
                    orientation = PositionState.OrientationState.Postured(confidence = 1f),
                    stationary = ConfidenceLevel.Positive(confidence = 1f),
                    orientation = ConfidenceLevel.Positive(confidence = 1f),
                )
            )

@@ -80,8 +81,8 @@ class PosturingInteractorTest : SysuiTestCase() {

            posturingRepository.fake.emitPositionState(
                PositionState(
                    stationary = PositionState.StationaryState.Stationary(confidence = 1f),
                    orientation = PositionState.OrientationState.Postured(confidence = 0.2f),
                    stationary = ConfidenceLevel.Positive(confidence = 1f),
                    orientation = ConfidenceLevel.Positive(confidence = 0.2f),
                )
            )

@@ -97,8 +98,8 @@ class PosturingInteractorTest : SysuiTestCase() {

            posturingRepository.fake.emitPositionState(
                PositionState(
                    stationary = PositionState.StationaryState.Stationary(confidence = 1f),
                    orientation = PositionState.OrientationState.Postured(confidence = 0.2f),
                    stationary = ConfidenceLevel.Positive(confidence = 1f),
                    orientation = ConfidenceLevel.Positive(confidence = 0.2f),
                )
            )

@@ -114,8 +115,8 @@ class PosturingInteractorTest : SysuiTestCase() {

            posturingRepository.fake.emitPositionState(
                PositionState(
                    stationary = PositionState.StationaryState.Stationary(confidence = 1f),
                    orientation = PositionState.OrientationState.Postured(confidence = 0.2f),
                    stationary = ConfidenceLevel.Positive(confidence = 1f),
                    orientation = ConfidenceLevel.Positive(confidence = 0.2f),
                )
            )

@@ -125,8 +126,8 @@ class PosturingInteractorTest : SysuiTestCase() {

            posturingRepository.fake.emitPositionState(
                PositionState(
                    stationary = PositionState.StationaryState.Stationary(confidence = 1f),
                    orientation = PositionState.OrientationState.Postured(confidence = 1f),
                    stationary = ConfidenceLevel.Positive(confidence = 1f),
                    orientation = ConfidenceLevel.Positive(confidence = 1f),
                )
            )
            assertThat(postured).isFalse()
@@ -153,8 +154,8 @@ class PosturingInteractorTest : SysuiTestCase() {

            posturingRepository.fake.emitPositionState(
                PositionState(
                    stationary = PositionState.StationaryState.Stationary(confidence = 1f),
                    orientation = PositionState.OrientationState.Postured(confidence = 1f),
                    stationary = ConfidenceLevel.Positive(confidence = 1f),
                    orientation = ConfidenceLevel.Positive(confidence = 1f),
                )
            )

@@ -183,8 +184,8 @@ class PosturingInteractorTest : SysuiTestCase() {
                advanceTimeBy(1.milliseconds)
                posturingRepository.fake.emitPositionState(
                    PositionState(
                        stationary = PositionState.StationaryState.Stationary(confidence = 1f),
                        orientation = PositionState.OrientationState.Postured(confidence = 1f),
                        stationary = ConfidenceLevel.Positive(confidence = 1f),
                        orientation = ConfidenceLevel.Positive(confidence = 1f),
                    )
                )
            }
@@ -207,8 +208,8 @@ class PosturingInteractorTest : SysuiTestCase() {

            posturingRepository.fake.emitPositionState(
                PositionState(
                    stationary = PositionState.StationaryState.Stationary(confidence = 1f),
                    orientation = PositionState.OrientationState.Postured(confidence = 1f),
                    stationary = ConfidenceLevel.Positive(confidence = 1f),
                    orientation = ConfidenceLevel.Positive(confidence = 1f),
                )
            )

@@ -232,8 +233,8 @@ class PosturingInteractorTest : SysuiTestCase() {
            underTest.setValueForDebug(PosturedState.NotPostured)
            posturingRepository.fake.emitPositionState(
                PositionState(
                    stationary = PositionState.StationaryState.Stationary(confidence = 1f),
                    orientation = PositionState.OrientationState.Postured(confidence = 1f),
                    stationary = ConfidenceLevel.Positive(confidence = 1f),
                    orientation = ConfidenceLevel.Positive(confidence = 1f),
                )
            )

@@ -246,6 +247,53 @@ class PosturingInteractorTest : SysuiTestCase() {
            assertThat(postured).isTrue()
        }

    @Test
    fun testMayBePostured() =
        kosmos.runTest {
            val mayBePostured by collectLastValue(underTest.mayBePostured)
            val postured by collectLastValue(underTest.postured)
            assertThat(mayBePostured).isFalse()
            assertThat(postured).isFalse()

            posturingRepository.fake.emitPositionState(
                PositionState(
                    stationary = ConfidenceLevel.Negative(confidence = 1f),
                    orientation = ConfidenceLevel.Positive(confidence = 1f),
                )
            )

            assertThat(mayBePostured).isFalse()
            assertThat(postured).isFalse()

            // 10ms later, the device becomes stationary with 0.5 confidence.
            advanceTimeBy(10.milliseconds)
            posturingRepository.fake.emitPositionState(
                PositionState(
                    stationary = ConfidenceLevel.Positive(confidence = 0.5f),
                    orientation = ConfidenceLevel.Positive(confidence = 1f),
                )
            )

            assertThat(mayBePostured).isFalse()
            assertThat(postured).isFalse()

            // 20ms later, the device becomes fully stationary with 1.0 confidence.
            advanceTimeBy(20.milliseconds)
            posturingRepository.fake.emitPositionState(
                PositionState(
                    stationary = ConfidenceLevel.Positive(confidence = 1f),
                    orientation = ConfidenceLevel.Positive(confidence = 1f),
                )
            )

            assertThat(mayBePostured).isTrue()
            assertThat(postured).isFalse()

            advanceTimeBySlidingWindowAndRun()
            assertThat(mayBePostured).isFalse()
            assertThat(postured).isTrue()
        }

    private fun Kosmos.stubSensorManager(): (sensor: Sensor) -> Unit {
        val callbacks = mutableMapOf<Sensor, List<TriggerEventListener>>()
        val pickupSensor = mock<Sensor>()
+65 −10
Original line number Diff line number Diff line
@@ -18,11 +18,14 @@ package com.android.systemui.communal

import android.annotation.SuppressLint
import android.app.DreamManager
import android.os.PowerManager
import android.service.dreams.Flags.allowDreamWhenPostured
import com.android.app.tracing.coroutines.launchInTraced
import com.android.app.tracing.coroutines.launchTraced
import com.android.systemui.CoreStartable
import com.android.systemui.common.domain.interactor.BatteryInteractor
import com.android.systemui.communal.posturing.domain.interactor.PosturingInteractor
import com.android.systemui.communal.posturing.domain.interactor.PosturingInteractor.Companion.SLIDING_WINDOW_DURATION
import com.android.systemui.communal.posturing.shared.model.PosturedState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -31,14 +34,19 @@ import com.android.systemui.dreams.shared.model.WhenToDream
import com.android.systemui.log.dagger.CommunalTableLog
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import com.android.systemui.util.wakelock.WakeLock
import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import java.io.PrintWriter
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.dropWhile
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@@ -54,17 +62,29 @@ constructor(
    batteryInteractor: BatteryInteractor,
    @Background private val bgScope: CoroutineScope,
    @CommunalTableLog private val tableLogBuffer: TableLogBuffer,
    private val wakeLockBuilder: WakeLock.Builder,
    private val powerInteractor: PowerInteractor,
) : CoreStartable {
    private val command = DevicePosturingCommand()

    private val wakeLock by lazy {
        wakeLockBuilder
            .setMaxTimeout(SLIDING_WINDOW_DURATION.inWholeMilliseconds)
            .setTag(TAG)
            .setLevelsAndFlags(PowerManager.SCREEN_DIM_WAKE_LOCK)
            .build()
    }

    // Only subscribe to posturing if applicable to avoid running the posturing CHRE nanoapp
    // if posturing signal is not needed.
    private val postured =
    private val preconditions =
        allOf(
            batteryInteractor.isDevicePluggedIn,
            dreamSettingsInteractor.whenToDream.map { it == WhenToDream.WHILE_POSTURED },
        )
            .flatMapLatestConflated { shouldListen ->

    private val postured =
        preconditions.flatMapLatestConflated { shouldListen ->
            if (shouldListen) {
                posturingInteractor.postured
            } else {
@@ -72,6 +92,15 @@ constructor(
            }
        }

    private val mayBePosturedSoon =
        preconditions.flatMapLatestConflated { shouldListen ->
            if (shouldListen) {
                allOf(posturingInteractor.mayBePostured, powerInteractor.isAwake)
            } else {
                flowOf(false)
            }
        }

    @SuppressLint("MissingPermission")
    override fun start() {
        if (!allowDreamWhenPostured()) {
@@ -88,6 +117,32 @@ constructor(
            .onEach { postured -> dreamManager.setDevicePostured(postured) }
            .launchInTraced("$TAG#collectPostured", bgScope)

        bgScope.launchTraced("$TAG#collectMayBePosturedSoon") {
            mayBePosturedSoon
                .debounce { mayBePostured ->
                    // Wait to release the WakeLock so we have time to update the dream state.
                    if (!mayBePostured) {
                        500.milliseconds
                    } else {
                        0.milliseconds
                    }
                }
                .dropWhile { !it }
                .distinctUntilChanged()
                .logDiffsForTable(
                    tableLogBuffer = tableLogBuffer,
                    columnName = "mayBePosturedSoon",
                    initialValue = false,
                )
                .collect { mayBePosturedSoon ->
                    if (mayBePosturedSoon) {
                        wakeLock.acquire(TAG)
                    } else {
                        wakeLock.release(TAG)
                    }
                }
        }

        commandRegistry.registerCommand(COMMAND_ROOT) { command }
    }

+4 −28
Original line number Diff line number Diff line
@@ -16,33 +16,9 @@

package com.android.systemui.communal.posturing.data.model

import androidx.annotation.FloatRange
import com.android.systemui.communal.posturing.shared.model.ConfidenceLevel

data class PositionState(
    val stationary: StationaryState = StationaryState.Unknown,
    val orientation: OrientationState = OrientationState.Unknown,
) {
    sealed interface StationaryState {
        @get:FloatRange(from = 0.0, to = 1.0) val confidence: Float

        data object Unknown : StationaryState {
            override val confidence: Float = 0f
        }

        data class Stationary(override val confidence: Float) : StationaryState

        data class NotStationary(override val confidence: Float) : StationaryState
    }

    sealed interface OrientationState {
        @get:FloatRange(from = 0.0, to = 1.0) val confidence: Float

        data object Unknown : OrientationState {
            override val confidence: Float = 0f
        }

        data class Postured(override val confidence: Float) : OrientationState

        data class NotPostured(override val confidence: Float) : OrientationState
    }
}
    val stationary: ConfidenceLevel = ConfidenceLevel.Unknown,
    val orientation: ConfidenceLevel = ConfidenceLevel.Unknown,
)
+36 −49
Original line number Diff line number Diff line
@@ -16,10 +16,10 @@

package com.android.systemui.communal.posturing.domain.interactor

import android.annotation.SuppressLint
import android.hardware.Sensor
import com.android.systemui.communal.posturing.data.model.PositionState
import com.android.systemui.communal.posturing.data.repository.PosturingRepository
import com.android.systemui.communal.posturing.domain.model.AggregatedConfidenceState
import com.android.systemui.communal.posturing.shared.model.ConfidenceLevel
import com.android.systemui.communal.posturing.shared.model.PosturedState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -27,7 +27,6 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import com.android.systemui.util.kotlin.observeTriggerSensor
import com.android.systemui.util.kotlin.slidingWindow
import com.android.systemui.util.sensors.AsyncSensorManager
@@ -71,79 +70,64 @@ constructor(
     * Detects whether or not the device is stationary, applying a sliding window smoothing
     * algorithm.
     */
    private val stationarySmoothed: Flow<Boolean> =
    private val stationarySmoothed: Flow<AggregatedConfidenceState> =
        merge(
                observeTriggerSensor(Sensor.TYPE_PICK_UP_GESTURE)
                    // If pickup detected, avoid triggering posturing at all within the sliding
                    // window by emitting a negative infinity value.
                    .map { Float.NEGATIVE_INFINITY }
                    .map { ConfidenceLevel.Negative(1f) }
                    .onEach { logger.i("pickup gesture detected") },
                observeTriggerSensor(Sensor.TYPE_SIGNIFICANT_MOTION)
                    // If motion detected, avoid triggering posturing at all within the sliding
                    // window by emitting a negative infinity value.
                    .map { Float.NEGATIVE_INFINITY }
                    .map { ConfidenceLevel.Negative(1f) }
                    .onEach { logger.i("significant motion detected") },
                repository.positionState
                    .map { it.stationary }
                    .filterNot { it is PositionState.StationaryState.Unknown }
                    .map { stationaryState ->
                        if (stationaryState is PositionState.StationaryState.Stationary) {
                            stationaryState.confidence
                        } else {
                            // If not stationary, then we should effectively disable posturing by
                            // emitting the lowest possible confidence.
                            Float.NEGATIVE_INFINITY
                        }
                    },
                    .filterNot { it is ConfidenceLevel.Unknown },
            )
            .slidingWindow(SLIDING_WINDOW_DURATION, clock)
            .filterNot { it.isEmpty() }
            .map { window ->
                val avgStationaryConfidence = window.average()
                logger.i({ "stationary confidence: $double1 | window: $str1" }) {
                    str1 = window.formatWindowForDebugging()
                    double1 = avgStationaryConfidence
                }
                avgStationaryConfidence > CONFIDENCE_THRESHOLD
            }
            .map { window -> window.toConfidenceState() }

    /**
     * Detects whether or not the device is in an upright orientation, applying a sliding window
     * smoothing algorithm.
     */
    private val orientationSmoothed: Flow<Boolean> =
    private val orientationSmoothed: Flow<AggregatedConfidenceState> =
        repository.positionState
            .map { it.orientation }
            .filterNot { it is PositionState.OrientationState.Unknown }
            .map { orientationState ->
                if (orientationState is PositionState.OrientationState.Postured) {
                    orientationState.confidence
                } else {
                    // If not postured, then we should effectively disable posturing by
                    // emitting the lowest possible confidence.
                    Float.NEGATIVE_INFINITY
                }
            }
            .filterNot { it is ConfidenceLevel.Unknown }
            .slidingWindow(SLIDING_WINDOW_DURATION, clock)
            .filterNot { it.isEmpty() }
            .map { window ->
                val avgOrientationConfidence = window.average()
                logger.i({ "orientation confidence: $double1 | window: $str1" }) {
                    str1 = window.formatWindowForDebugging()
                    double1 = avgOrientationConfidence
                }
                avgOrientationConfidence > CONFIDENCE_THRESHOLD
            }
            .map { window -> window.toConfidenceState() }

    /**
     * Posturing is composed of the device being stationary and in the correct orientation. If both
     * conditions are met, then consider it postured.
     */
    private val posturedSmoothed: Flow<PosturedState> =
        allOf(stationarySmoothed, orientationSmoothed)
            .map { postured ->
                if (postured) {
        combine(stationarySmoothed, orientationSmoothed) {
                stationaryConfidence,
                orientationConfidence ->
                logger.i({ "stationary: $str1 | orientation: $str2" }) {
                    str1 = stationaryConfidence.toString()
                    str2 = orientationConfidence.toString()
                }

                val isStationary = stationaryConfidence.avgConfidence >= CONFIDENCE_THRESHOLD
                val isInOrientation = orientationConfidence.avgConfidence >= CONFIDENCE_THRESHOLD

                if (isStationary && isInOrientation) {
                    PosturedState.Postured
                } else if (
                    stationaryConfidence.latestConfidence >= CONFIDENCE_THRESHOLD &&
                        orientationConfidence.latestConfidence >= CONFIDENCE_THRESHOLD
                ) {
                    // We may be postured soon since the latest confidence is above the threshold.
                    // If  no new events come in, we will eventually transition to postured at the
                    // end of the sliding window.
                    PosturedState.MayBePostured
                } else {
                    PosturedState.NotPostured
                }
@@ -171,6 +155,9 @@ constructor(
            debugValue.asBoolean() ?: postured.asBoolean() ?: false
        }

    /** Whether the device may become postured soon. */
    val mayBePostured: Flow<Boolean> = posturedSmoothed.map { it == PosturedState.MayBePostured }

    /**
     * Helper for observing a trigger sensor, which automatically unregisters itself after it
     * executes once.
@@ -192,11 +179,11 @@ fun PosturedState.asBoolean(): Boolean? {
    return when (this) {
        is PosturedState.Postured -> true
        PosturedState.NotPostured -> false
        PosturedState.MayBePostured -> false
        PosturedState.Unknown -> null
    }
}

@SuppressLint("DefaultLocale")
fun List<Float>.formatWindowForDebugging(): String {
    return joinToString(prefix = "[", postfix = "]") { String.format("%.2f", it) }
private fun List<ConfidenceLevel>.toConfidenceState(): AggregatedConfidenceState {
    return AggregatedConfidenceState(rawWindow = this.toList())
}
Loading