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

Commit 4f9ab76a authored by William Xiao's avatar William Xiao
Browse files

Fix low light triggering on phones when entering doze

Tangor did not have doze/aod so it was not accounted for in the
conditions. While DreamManager does explicitly not override doze with
the system dream, the doze dream does not start dozing fast enough to
prevent the low light code from overriding it with the system dream.

This change adjusts the DeviceInactiveCondition to only be true if the
device is not dozing or entering dozing to prevent the possibility of
entering low light dream when unplugged and entering aod.

Bug: 394707567
Fixes: 394707567
Flag: com.android.systemui.glanceable_hub_v2
Test: atest DeviceInactiveConditionTest
      manually verified on device in dark environment
Change-Id: I83f9584940ab3e8a70125ad9dd050e77810465f4
parent f13b68f1
Loading
Loading
Loading
Loading
+100 −0
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.communal

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.keyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
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.keyguard.wakefulnessLifecycle
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.policy.keyguardStateController
import com.android.systemui.testKosmos
import com.android.systemui.util.kotlin.JavaAdapter
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

@SmallTest
@RunWith(AndroidJUnit4::class)
class DeviceInactiveConditionTest : SysuiTestCase() {
    private val kosmos =
        testKosmos().useUnconfinedTestDispatcher().also {
            whenever(it.wakefulnessLifecycle.wakefulness) doReturn WAKEFULNESS_AWAKE
        }

    private val Kosmos.underTest by
        Kosmos.Fixture {
            DeviceInactiveCondition(
                applicationCoroutineScope,
                keyguardStateController,
                wakefulnessLifecycle,
                keyguardUpdateMonitor,
                keyguardInteractor,
                JavaAdapter(applicationCoroutineScope),
            )
        }

    @Test
    fun asleep_conditionTrue() =
        kosmos.runTest {
            // Condition is false to start.
            underTest.start()
            assertThat(underTest.isConditionMet).isFalse()

            // Condition is true when device goes to sleep.
            sleep()
            assertThat(underTest.isConditionMet).isTrue()
        }

    @Test
    fun dozingAndAsleep_conditionFalse() =
        kosmos.runTest {
            // Condition is true when device is asleep.
            underTest.start()
            sleep()
            assertThat(underTest.isConditionMet).isTrue()

            // Condition turns false after doze starts.
            fakeKeyguardRepository.setDozeTransitionModel(
                DozeTransitionModel(from = DozeStateModel.UNINITIALIZED, to = DozeStateModel.DOZE)
            )
            assertThat(underTest.isConditionMet).isFalse()
        }

    fun Kosmos.sleep() {
        whenever(wakefulnessLifecycle.wakefulness) doReturn WAKEFULNESS_ASLEEP
        argumentCaptor<WakefulnessLifecycle.Observer>().apply {
            verify(wakefulnessLifecycle).addObserver(capture())
            firstValue.onStartedGoingToSleep()
        }
    }
}
+23 −8
Original line number Diff line number Diff line
@@ -17,16 +17,19 @@
package com.android.systemui.communal;

import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP;

import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.shared.model.DozeStateModel;
import com.android.systemui.shared.condition.Condition;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.kotlin.JavaAdapter;

import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.Job;

import javax.inject.Inject;

@@ -38,6 +41,10 @@ public class DeviceInactiveCondition extends Condition {
    private final KeyguardStateController mKeyguardStateController;
    private final WakefulnessLifecycle mWakefulnessLifecycle;
    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
    private final KeyguardInteractor mKeyguardInteractor;
    private final JavaAdapter mJavaAdapter;
    private Job mAnyDozeListenerJob;
    private boolean mAnyDoze;
    private final KeyguardStateController.Callback mKeyguardStateCallback =
            new KeyguardStateController.Callback() {
                @Override
@@ -63,12 +70,14 @@ public class DeviceInactiveCondition extends Condition {
    @Inject
    public DeviceInactiveCondition(@Application CoroutineScope scope,
            KeyguardStateController keyguardStateController,
            WakefulnessLifecycle wakefulnessLifecycle,
            KeyguardUpdateMonitor keyguardUpdateMonitor) {
            WakefulnessLifecycle wakefulnessLifecycle, KeyguardUpdateMonitor keyguardUpdateMonitor,
            KeyguardInteractor keyguardInteractor, JavaAdapter javaAdapter) {
        super(scope);
        mKeyguardStateController = keyguardStateController;
        mWakefulnessLifecycle = wakefulnessLifecycle;
        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
        mKeyguardInteractor = keyguardInteractor;
        mJavaAdapter = javaAdapter;
    }

    @Override
@@ -77,6 +86,11 @@ public class DeviceInactiveCondition extends Condition {
        mKeyguardStateController.addCallback(mKeyguardStateCallback);
        mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
        mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
        mAnyDozeListenerJob = mJavaAdapter.alwaysCollectFlow(
                mKeyguardInteractor.getDozeTransitionModel(), dozeModel -> {
                    mAnyDoze = !DozeStateModel.Companion.isDozeOff(dozeModel.getTo());
                    updateState();
                });
    }

    @Override
@@ -84,6 +98,7 @@ public class DeviceInactiveCondition extends Condition {
        mKeyguardStateController.removeCallback(mKeyguardStateCallback);
        mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
        mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
        mAnyDozeListenerJob.cancel(null);
    }

    @Override
@@ -92,10 +107,10 @@ public class DeviceInactiveCondition extends Condition {
    }

    private void updateState() {
        final boolean asleep =
                mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_ASLEEP
                        || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_GOING_TO_SLEEP;
        updateCondition(asleep || mKeyguardStateController.isShowing()
                || mKeyguardUpdateMonitor.isDreaming());
        final boolean asleep = mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_ASLEEP;
        // Doze/AoD is also a dream, but we should never override it with low light as to the user
        // it's totally unrelated.
        updateCondition(!mAnyDoze && (asleep || mKeyguardStateController.isShowing()
                || mKeyguardUpdateMonitor.isDreaming()));
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -78,7 +78,7 @@ public abstract class LowLightModule {

    @Provides
    @IntoSet
    @Named(com.android.systemui.lowlightclock.dagger.LowLightModule.LOW_LIGHT_PRECONDITIONS)
    @Named(LOW_LIGHT_PRECONDITIONS)
    static Condition provideLowLightCondition(LowLightCondition lowLightCondition,
            DirectBootCondition directBootCondition) {
        // Start lowlight if we are either in lowlight or in direct boot. The ordering of the