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

Commit 00b3b61f authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Introduce AmbientLightModeMonitor."

parents 63517353 2d31be6b
Loading
Loading
Loading
Loading
+127 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.idle

import android.annotation.IntDef
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.util.Log
import com.android.systemui.util.sensors.AsyncSensorManager
import javax.inject.Inject
import kotlin.properties.Delegates

/**
 * Monitors ambient light signals, applies a debouncing algorithm, and produces the current
 * [AmbientLightMode].
 *
 * For debouncer behavior, refer to go/titan-light-sensor-debouncer.
 *
 * @property sensorManager the sensor manager used to register sensor event updates.
 */
class AmbientLightModeMonitor @Inject constructor(
    private val sensorManager: AsyncSensorManager
) {
    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
    }

    // Light sensor used to detect ambient lighting conditions.
    private val lightSensor: Sensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)

    // Registered callback, which gets triggered when the ambient light mode changes.
    private var callback: Callback? = null

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

    // The current ambient light mode.
    @AmbientLightMode private var mode: Int by Delegates.observable(AMBIENT_LIGHT_MODE_UNDECIDED
    ) { _, old, new ->
        if (old != new) {
            callback?.onChange(new)
        }
    }

    /**
     * Start monitoring the current ambient light mode.
     *
     * @param callback callback that gets triggered when the ambient light mode changes. It also
     * gets triggered immediately to update the current value when this function is called.
     */
    fun start(callback: Callback) {
        if (DEBUG) Log.d(TAG, "start monitoring ambient light mode")

        if (this.callback != null) {
            if (DEBUG) Log.w(TAG, "already started")
            return
        }

        this.callback = callback
        callback.onChange(mode)

        sensorManager.registerListener(mSensorEventListener, lightSensor,
                SensorManager.SENSOR_DELAY_NORMAL)
    }

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

        if (callback == null) {
            if (DEBUG) Log.w(TAG, "haven't started")
            return
        }

        callback = null
        sensorManager.unregisterListener(mSensorEventListener)
    }

    private val mSensorEventListener: SensorEventListener = object : SensorEventListener {
        override fun onSensorChanged(event: SensorEvent) {
            if (event.values.isEmpty()) {
                if (DEBUG) Log.w(TAG, "SensorEvent doesn't have any value")
                return
            }

            // TODO(b/201657509): add debouncing logic.
            val shouldBeLowLight = event.values[0] < 10
            mode = if (shouldBeLowLight) AMBIENT_LIGHT_MODE_DARK else AMBIENT_LIGHT_MODE_LIGHT
        }

        override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
            // Do nothing.
        }
    }

    /**
     * Interface of the ambient light mode callback, which gets triggered when the mode changes.
     */
    interface Callback {
        fun onChange(@AmbientLightMode mode: Int)
    }
}
 No newline at end of file
+34 −41
Original line number Diff line number Diff line
@@ -24,10 +24,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Looper;
import android.os.PowerManager;
import android.os.SystemClock;
@@ -44,7 +40,6 @@ import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.sensors.AsyncSensorManager;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -56,8 +51,7 @@ import javax.inject.Provider;
/**
 * {@link IdleHostViewController} processes signals to control the lifecycle of the idle screen.
 */
public class IdleHostViewController extends ViewController<IdleHostView> implements
        SensorEventListener {
public class IdleHostViewController extends ViewController<IdleHostView> {
    private static final String INPUT_MONITOR_IDENTIFIER = "IdleHostViewController";
    private static final String TAG = "IdleHostViewController";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -114,11 +108,6 @@ public class IdleHostViewController extends ViewController<IdleHostView> impleme

    private final PowerManager mPowerManager;

    private final AsyncSensorManager mSensorManager;

    // Light sensor used to detect low light condition.
    private final Sensor mSensor;

    // Runnable for canceling enabling idle.
    private Runnable mCancelEnableIdling;

@@ -146,6 +135,9 @@ public class IdleHostViewController extends ViewController<IdleHostView> impleme
    // Intent filter for receiving dream broadcasts.
    private IntentFilter mDreamIntentFilter;

    // Monitor for the current ambient light mode. Used to trigger / exit low-light mode.
    private final AmbientLightModeMonitor mAmbientLightModeMonitor;

    // Delayed callback for starting idling.
    private final Runnable mEnableIdlingCallback = () -> {
        if (DEBUG) {
@@ -181,6 +173,31 @@ public class IdleHostViewController extends ViewController<IdleHostView> impleme
        }
    };

    private final AmbientLightModeMonitor.Callback mAmbientLightModeCallback =
            mode -> {
                boolean shouldBeLowLight;
                switch (mode) {
                    case AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED:
                        return;
                    case AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT:
                        shouldBeLowLight = false;
                        break;
                    case AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK:
                        shouldBeLowLight = true;
                        break;
                    default:
                        Log.w(TAG, "invalid ambient light mode");
                        return;
                }

                if (DEBUG) Log.d(TAG, "ambient light mode changed to " + mode);

                final boolean isLowLight = getState(STATE_LOW_LIGHT);
                if (shouldBeLowLight != isLowLight) {
                    setState(STATE_LOW_LIGHT, shouldBeLowLight);
                }
            };

    final Provider<View> mIdleViewProvider;

    @Inject
@@ -188,7 +205,6 @@ public class IdleHostViewController extends ViewController<IdleHostView> impleme
            Context context,
            BroadcastDispatcher broadcastDispatcher,
            PowerManager powerManager,
            AsyncSensorManager sensorManager,
            IdleHostView view, InputMonitorFactory factory,
            @Main DelayableExecutor delayableExecutor,
            @Main Resources resources,
@@ -197,18 +213,19 @@ public class IdleHostViewController extends ViewController<IdleHostView> impleme
            Choreographer choreographer,
            KeyguardStateController keyguardStateController,
            StatusBarStateController statusBarStateController,
            DreamHelper dreamHelper) {
            DreamHelper dreamHelper,
            AmbientLightModeMonitor ambientLightModeMonitor) {
        super(view);
        mContext = context;
        mBroadcastDispatcher = broadcastDispatcher;
        mPowerManager = powerManager;
        mSensorManager = sensorManager;
        mIdleViewProvider = idleViewProvider;
        mKeyguardStateController = keyguardStateController;
        mStatusBarStateController = statusBarStateController;
        mLooper = looper;
        mChoreographer = choreographer;
        mDreamHelper = dreamHelper;
        mAmbientLightModeMonitor = ambientLightModeMonitor;

        mState = STATE_KEYGUARD_SHOWING;

@@ -222,7 +239,6 @@ public class IdleHostViewController extends ViewController<IdleHostView> impleme
        mIdleTimeout = resources.getInteger(R.integer.config_idleModeTimeout);
        mInputMonitorFactory = factory;
        mDelayableExecutor = delayableExecutor;
        mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);

        if (DEBUG) {
            Log.d(TAG, "initial state:" + mState + " enabled:" + enabled
@@ -405,11 +421,10 @@ public class IdleHostViewController extends ViewController<IdleHostView> impleme

        if (mIsMonitoringLowLight) {
            if (DEBUG) Log.d(TAG, "enable low light monitoring");
            mSensorManager.registerListener(this /*listener*/, mSensor,
                    SensorManager.SENSOR_DELAY_NORMAL);
            mAmbientLightModeMonitor.start(mAmbientLightModeCallback);
        } else {
            if (DEBUG) Log.d(TAG, "disable low light monitoring");
            mSensorManager.unregisterListener(this);
            mAmbientLightModeMonitor.stop();
        }
    }

@@ -449,28 +464,6 @@ public class IdleHostViewController extends ViewController<IdleHostView> impleme
        mStatusBarStateController.removeCallback(mStatusBarCallback);
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        if (event.values.length == 0) {
            if (DEBUG) Log.w(TAG, "SensorEvent doesn't have value");
            return;
        }

        final boolean shouldBeLowLight = event.values[0] < 10;
        final boolean isLowLight = getState(STATE_LOW_LIGHT);

        if (shouldBeLowLight != isLowLight) {
            setState(STATE_LOW_LIGHT, shouldBeLowLight);
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        if (DEBUG) {
            Log.d(TAG, "onAccuracyChanged accuracy=" + accuracy);
        }
    }

    // Returns whether the device just stopped idling by comparing the previous state with the
    // current one.
    private boolean stoppedIdling(int oldState) {
+160 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.idle

import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.sensors.AsyncSensorManager
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.reset
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.any
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations

@SmallTest
@RunWith(AndroidTestingRunner::class)
class AmbientLightModeMonitorTest : SysuiTestCase() {
    @Mock private lateinit var sensorManager: AsyncSensorManager
    @Mock private lateinit var sensor: Sensor

    private lateinit var ambientLightModeMonitor: AmbientLightModeMonitor

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)

        `when`(sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(sensor)
        ambientLightModeMonitor = AmbientLightModeMonitor(sensorManager)
    }

    @Test
    fun shouldTriggerCallbackImmediatelyOnStart() {
        val callback = mock(AmbientLightModeMonitor.Callback::class.java)
        ambientLightModeMonitor.start(callback)

        // Monitor just started, should receive UNDECIDED.
        verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED)

        // Receives SensorEvent, and now mode is LIGHT.
        val sensorEventListener = captureSensorEventListener()
        sensorEventListener.onSensorChanged(sensorEventWithSingleValue(15f))

        // Stop monitor.
        ambientLightModeMonitor.stop()

        // Restart monitor.
        reset(callback)
        ambientLightModeMonitor.start(callback)

        // Verify receiving current mode (LIGHT) immediately.
        verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT)
    }

    @Test
    fun shouldReportDarkModeWhenSensorValueIsLessThanTen() {
        val callback = mock(AmbientLightModeMonitor.Callback::class.java)
        ambientLightModeMonitor.start(callback)

        val sensorEventListener = captureSensorEventListener()
        reset(callback)

        sensorEventListener.onSensorChanged(sensorEventWithSingleValue(0f))
        verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)

        sensorEventListener.onSensorChanged(sensorEventWithSingleValue(1f))
        verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)

        sensorEventListener.onSensorChanged(sensorEventWithSingleValue(5f))
        verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)

        sensorEventListener.onSensorChanged(sensorEventWithSingleValue(9.9f))
        verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)
    }

    @Test
    fun shouldReportLightModeWhenSensorValueIsGreaterThanOrEqualToTen() {
        val callback = mock(AmbientLightModeMonitor.Callback::class.java)
        ambientLightModeMonitor.start(callback)

        val sensorEventListener = captureSensorEventListener()
        reset(callback)

        sensorEventListener.onSensorChanged(sensorEventWithSingleValue(10f))
        verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT)

        sensorEventListener.onSensorChanged(sensorEventWithSingleValue(10.1f))
        verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT)

        sensorEventListener.onSensorChanged(sensorEventWithSingleValue(15f))
        verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT)

        sensorEventListener.onSensorChanged(sensorEventWithSingleValue(100f))
        verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT)
    }

    @Test
    fun shouldOnlyTriggerCallbackWhenValueChanges() {
        val callback = mock(AmbientLightModeMonitor.Callback::class.java)
        ambientLightModeMonitor.start(callback)

        val sensorEventListener = captureSensorEventListener()

        // Light mode, should trigger callback.
        reset(callback)
        sensorEventListener.onSensorChanged(sensorEventWithSingleValue(20f))
        verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT)

        // Light mode again, should NOT trigger callback.
        reset(callback)
        sensorEventListener.onSensorChanged(sensorEventWithSingleValue(25f))
        verify(callback, never()).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT)

        // Dark mode, should trigger callback.
        reset(callback)
        sensorEventListener.onSensorChanged(sensorEventWithSingleValue(2f))
        verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)

        // Dark mode again, should not trigger callback.
        reset(callback)
        sensorEventListener.onSensorChanged(sensorEventWithSingleValue(3f))
        verify(callback, never()).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)
    }

    // Captures [SensorEventListener], assuming it has been registered with [sensorManager].
    private fun captureSensorEventListener(): SensorEventListener {
        val captor = ArgumentCaptor.forClass(SensorEventListener::class.java)
        verify(sensorManager).registerListener(captor.capture(), any(), anyInt())
        return captor.value
    }

    // Returns a [SensorEvent] with a single [value].
    private fun sensorEventWithSingleValue(value: Float): SensorEvent {
        return SensorEvent(sensor, 1, 1, FloatArray(1) { value })
    }
}
+32 −34
Original line number Diff line number Diff line
@@ -28,8 +28,6 @@ import static org.mockito.Mockito.when;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.res.Resources;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.os.Looper;
import android.os.PowerManager;
import android.testing.AndroidTestingRunner;
@@ -46,7 +44,6 @@ import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.sensors.AsyncSensorManager;

import org.junit.Before;
import org.junit.Test;
@@ -55,8 +52,6 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.time.Instant;

import javax.inject.Provider;

@SmallTest
@@ -64,7 +59,6 @@ import javax.inject.Provider;
public class IdleHostViewControllerTest extends SysuiTestCase {
    @Mock private BroadcastDispatcher mBroadcastDispatcher;
    @Mock private PowerManager mPowerManager;
    @Mock private AsyncSensorManager mSensorManager;
    @Mock private IdleHostView mIdleHostView;
    @Mock private InputMonitorFactory mInputMonitorFactory;
    @Mock private DelayableExecutor mDelayableExecutor;
@@ -74,12 +68,11 @@ public class IdleHostViewControllerTest extends SysuiTestCase {
    @Mock private Choreographer mChoreographer;
    @Mock private KeyguardStateController mKeyguardStateController;
    @Mock private StatusBarStateController mStatusBarStateController;
    @Mock private Sensor mSensor;
    @Mock private DreamHelper mDreamHelper;
    @Mock private InputMonitorCompat mInputMonitor;
    @Mock private InputChannelCompat.InputEventReceiver mInputEventReceiver;
    @Mock private AmbientLightModeMonitor mAmbientLightModeMonitor;

    private final long mTimestamp = Instant.now().toEpochMilli();
    private KeyguardStateController.Callback mKeyguardStateCallback;
    private StatusBarStateController.StateListener mStatusBarStateListener;
    private IdleHostViewController mController;
@@ -90,15 +83,15 @@ public class IdleHostViewControllerTest extends SysuiTestCase {

        when(mResources.getBoolean(R.bool.config_enableIdleMode)).thenReturn(true);
        when(mStatusBarStateController.isDozing()).thenReturn(false);
        when(mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(mSensor);
        when(mInputMonitorFactory.getInputMonitor("IdleHostViewController"))
                .thenReturn(mInputMonitor);
        when(mInputMonitor.getInputReceiver(any(), any(), any())).thenReturn(mInputEventReceiver);

        mController = new IdleHostViewController(mContext,
                mBroadcastDispatcher, mPowerManager, mSensorManager, mIdleHostView,
                mBroadcastDispatcher, mPowerManager, mIdleHostView,
                mInputMonitorFactory, mDelayableExecutor, mResources, mLooper, mViewProvider,
                mChoreographer, mKeyguardStateController, mStatusBarStateController, mDreamHelper);
                mChoreographer, mKeyguardStateController, mStatusBarStateController, mDreamHelper,
                mAmbientLightModeMonitor);
        mController.init();
        mController.onViewAttached();

@@ -122,9 +115,9 @@ public class IdleHostViewControllerTest extends SysuiTestCase {
        mKeyguardStateCallback.onKeyguardShowingChanged();

        // Regular ambient lighting.
        final SensorEvent sensorEvent = new SensorEvent(mSensor, 3, mTimestamp,
                new float[]{90});
        mController.onSensorChanged(sensorEvent);
        final AmbientLightModeMonitor.Callback lightMonitorCallback =
                captureAmbientLightModeMonitorCallback();
        lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT);

        // Times out.
        ArgumentCaptor<Runnable> callbackCapture = ArgumentCaptor.forClass(Runnable.class);
@@ -149,9 +142,9 @@ public class IdleHostViewControllerTest extends SysuiTestCase {
        final BroadcastReceiver dreamBroadcastReceiver = dreamBroadcastReceiverCaptor.getValue();

        // Low ambient lighting.
        final SensorEvent sensorEvent = new SensorEvent(mSensor, 3, mTimestamp,
                new float[]{5});
        mController.onSensorChanged(sensorEvent);
        final AmbientLightModeMonitor.Callback lightMonitorCallback =
                captureAmbientLightModeMonitorCallback();
        lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);

        // Verifies it goes to sleep because of low light.
        verify(mPowerManager).goToSleep(anyLong(), anyInt(), anyInt());
@@ -179,15 +172,15 @@ public class IdleHostViewControllerTest extends SysuiTestCase {

    @Test
    public void testTransitionBetweenIdleAndLowLightMode() {
        // Regular ambient lighting.
        final SensorEvent sensorEventRegularLight = new SensorEvent(mSensor, 3, mTimestamp,
                new float[]{90});
        mController.onSensorChanged(sensorEventRegularLight);

        // Keyguard showing.
        when(mKeyguardStateController.isShowing()).thenReturn(true);
        mKeyguardStateCallback.onKeyguardShowingChanged();

        // Regular ambient lighting.
        final AmbientLightModeMonitor.Callback lightMonitorCallback =
                captureAmbientLightModeMonitorCallback();
        lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT);

        // Times out.
        ArgumentCaptor<Runnable> callbackCapture = ArgumentCaptor.forClass(Runnable.class);
        verify(mDelayableExecutor).executeDelayed(callbackCapture.capture(), anyLong());
@@ -198,15 +191,13 @@ public class IdleHostViewControllerTest extends SysuiTestCase {
        clearInvocations(mDreamHelper);

        // Ambient lighting becomes dim.
        final SensorEvent sensorEventLowLight = new SensorEvent(mSensor, 3, mTimestamp,
                new float[]{5});
        mController.onSensorChanged(sensorEventLowLight);
        lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);

        // Verifies in low light mode (dozing).
        verify(mPowerManager).goToSleep(anyLong(), anyInt(), anyInt());

        // Ambient lighting becomes bright again.
        mController.onSensorChanged(sensorEventRegularLight);
        lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT);

        // Verifies in idle mode (dreaming).
        verify(mDreamHelper).startDreaming(any());
@@ -214,22 +205,20 @@ public class IdleHostViewControllerTest extends SysuiTestCase {

    @Test
    public void testStartDozingWhenLowLight() {
        // Regular ambient lighting.
        final SensorEvent sensorEventRegularLight = new SensorEvent(mSensor, 3, mTimestamp,
                new float[]{90});
        mController.onSensorChanged(sensorEventRegularLight);

        // Keyguard showing.
        when(mKeyguardStateController.isShowing()).thenReturn(true);
        mKeyguardStateCallback.onKeyguardShowingChanged();

        // Regular ambient lighting.
        final AmbientLightModeMonitor.Callback lightMonitorCallback =
                captureAmbientLightModeMonitorCallback();
        lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT);

        // Verifies it doesn't go to sleep yet.
        verify(mPowerManager, never()).goToSleep(anyLong(), anyInt(), anyInt());

        // Ambient lighting becomes dim.
        final SensorEvent sensorEventLowLight = new SensorEvent(mSensor, 3, mTimestamp,
                new float[]{5});
        mController.onSensorChanged(sensorEventLowLight);
        lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);

        // Verifies it goes to sleep.
        verify(mPowerManager).goToSleep(anyLong(), anyInt(), anyInt());
@@ -251,4 +240,13 @@ public class IdleHostViewControllerTest extends SysuiTestCase {
        // Should dispose input event receiver.
        verify(mInputEventReceiver).dispose();
    }

    // Captures [AmbientLightModeMonitor.Callback] assuming that the ambient light mode monitor
    // has been started.
    private AmbientLightModeMonitor.Callback captureAmbientLightModeMonitorCallback() {
        ArgumentCaptor<AmbientLightModeMonitor.Callback> captor =
                ArgumentCaptor.forClass(AmbientLightModeMonitor.Callback.class);
        verify(mAmbientLightModeMonitor).start(captor.capture());
        return captor.getValue();
    }
}