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

Commit 85265850 authored by Michael Wright's avatar Michael Wright Committed by Android (Google) Code Review
Browse files

Merge "Only reset short term model after timeout" into udc-dev

parents 95e67409 e7c9c0b0
Loading
Loading
Loading
Loading
+141 −39
Original line number Diff line number Diff line
@@ -75,10 +75,11 @@ public class AutomaticBrightnessController {

    private static final int MSG_UPDATE_AMBIENT_LUX = 1;
    private static final int MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE = 2;
    private static final int MSG_INVALIDATE_SHORT_TERM_MODEL = 3;
    private static final int MSG_INVALIDATE_CURRENT_SHORT_TERM_MODEL = 3;
    private static final int MSG_UPDATE_FOREGROUND_APP = 4;
    private static final int MSG_UPDATE_FOREGROUND_APP_SYNC = 5;
    private static final int MSG_RUN_UPDATE = 6;
    private static final int MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL = 7;

    // Callbacks for requesting updates to the display's power state
    private final Callbacks mCallbacks;
@@ -216,12 +217,11 @@ public class AutomaticBrightnessController {
    private float mBrightnessAdjustmentSampleOldLux;
    private float mBrightnessAdjustmentSampleOldBrightness;

    // When the short term model is invalidated, we don't necessarily reset it (i.e. clear the
    // user's adjustment) immediately, but wait for a drastic enough change in the ambient light.
    // The anchor determines what were the light levels when the user has set their preference, and
    // we use a relative threshold to determine when to revert to the OEM curve.
    private boolean mShortTermModelValid;
    private float mShortTermModelAnchor;
    // The short term models, current and previous. Eg, we might use the "paused" one to save out
    // the interactive short term model when switching to idle screen brightness mode, and
    // vice-versa.
    private final ShortTermModel mShortTermModel;
    private final ShortTermModel mPausedShortTermModel;

    // Controls High Brightness Mode.
    private HighBrightnessModeController mHbmController;
@@ -309,8 +309,8 @@ public class AutomaticBrightnessController {
        mAmbientBrightnessThresholdsIdle = ambientBrightnessThresholdsIdle;
        mScreenBrightnessThresholds = screenBrightnessThresholds;
        mScreenBrightnessThresholdsIdle = screenBrightnessThresholdsIdle;
        mShortTermModelValid = true;
        mShortTermModelAnchor = -1;
        mShortTermModel = new ShortTermModel();
        mPausedShortTermModel = new ShortTermModel();
        mHandler = new AutomaticBrightnessHandler(looper);
        mAmbientLightRingBuffer =
            new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizonLong, mClock);
@@ -492,10 +492,10 @@ public class AutomaticBrightnessController {
            Slog.d(TAG, "Display policy transitioning from " + oldPolicy + " to " + policy);
        }
        if (!isInteractivePolicy(policy) && isInteractivePolicy(oldPolicy) && !isInIdleMode()) {
            mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_SHORT_TERM_MODEL,
            mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_CURRENT_SHORT_TERM_MODEL,
                    mCurrentBrightnessMapper.getShortTermModelTimeout());
        } else if (isInteractivePolicy(policy) && !isInteractivePolicy(oldPolicy)) {
            mHandler.removeMessages(MSG_INVALIDATE_SHORT_TERM_MODEL);
            mHandler.removeMessages(MSG_INVALIDATE_CURRENT_SHORT_TERM_MODEL);
        }
        return true;
    }
@@ -516,25 +516,13 @@ public class AutomaticBrightnessController {

    private boolean setScreenBrightnessByUser(float lux, float brightness) {
        mCurrentBrightnessMapper.addUserDataPoint(lux, brightness);
        mShortTermModelValid = true;
        mShortTermModelAnchor = lux;
        if (mLoggingEnabled) {
            Slog.d(TAG, "ShortTermModel: anchor=" + mShortTermModelAnchor);
        }
        mShortTermModel.setUserBrightness(lux, brightness);
        return true;
    }

    public void resetShortTermModel() {
        mCurrentBrightnessMapper.clearUserDataPoints();
        mShortTermModelValid = true;
        mShortTermModelAnchor = -1;
    }

    private void invalidateShortTermModel() {
        if (mLoggingEnabled) {
            Slog.d(TAG, "ShortTermModel: invalidate user data");
        }
        mShortTermModelValid = false;
        mShortTermModel.reset();
    }

    public boolean setBrightnessConfiguration(BrightnessConfiguration configuration,
@@ -595,8 +583,12 @@ public class AutomaticBrightnessController {
            pw.println("  mShortTermModelTimeout(idle)="
                    + mIdleModeBrightnessMapper.getShortTermModelTimeout());
        }
        pw.println("  mShortTermModelAnchor=" + mShortTermModelAnchor);
        pw.println("  mShortTermModelValid=" + mShortTermModelValid);
        pw.println("  mShortTermModel=");
        mShortTermModel.dump(pw);
        pw.println("  mPausedShortTermModel=");
        mPausedShortTermModel.dump(pw);

        pw.println();
        pw.println("  mBrightnessAdjustmentSamplePending=" + mBrightnessAdjustmentSamplePending);
        pw.println("  mBrightnessAdjustmentSampleOldLux=" + mBrightnessAdjustmentSampleOldLux);
        pw.println("  mBrightnessAdjustmentSampleOldBrightness="
@@ -740,15 +732,9 @@ public class AutomaticBrightnessController {
        }
        mHbmController.onAmbientLuxChange(mAmbientLux);


        // If the short term model was invalidated and the change is drastic enough, reset it.
        if (!mShortTermModelValid && mShortTermModelAnchor != -1) {
            if (mCurrentBrightnessMapper.shouldResetShortTermModel(
                    mAmbientLux, mShortTermModelAnchor)) {
                resetShortTermModel();
            } else {
                mShortTermModelValid = true;
            }
        }
        mShortTermModel.maybeReset(mAmbientLux);
    }

    private float calculateAmbientLux(long now, long horizon) {
@@ -1118,8 +1104,29 @@ public class AutomaticBrightnessController {
            return;
        }
        Slog.i(TAG, "Switching to Idle Screen Brightness Mode");
        // Stash short term model
        ShortTermModel tempShortTermModel = new ShortTermModel();
        tempShortTermModel.set(mCurrentBrightnessMapper.getUserLux(),
                mCurrentBrightnessMapper.getUserBrightness(), /* valid= */ true);

        // Send delayed timeout
        mHandler.sendEmptyMessageAtTime(MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL,
                mClock.uptimeMillis()
                        + mCurrentBrightnessMapper.getShortTermModelTimeout());

        Slog.i(TAG, "mPreviousShortTermModel" + mPausedShortTermModel);
        // new brightness mapper
        mCurrentBrightnessMapper = mIdleModeBrightnessMapper;
        resetShortTermModel();

        // if previous stm has been invalidated, and lux has drastically changed, just use
        // the new, reset stm.
        // if previous stm is still valid then revalidate it
        if (mPausedShortTermModel != null && !mPausedShortTermModel.maybeReset(mAmbientLux)) {
            setScreenBrightnessByUser(mPausedShortTermModel.mAnchor,
                    mPausedShortTermModel.mBrightness);
        }
        mPausedShortTermModel.copyFrom(tempShortTermModel);

        update();
    }

@@ -1128,8 +1135,28 @@ public class AutomaticBrightnessController {
            return;
        }
        Slog.i(TAG, "Switching to Interactive Screen Brightness Mode");
        ShortTermModel tempShortTermModel = new ShortTermModel();
        tempShortTermModel.set(mCurrentBrightnessMapper.getUserLux(),
                mCurrentBrightnessMapper.getUserBrightness(), /* valid= */ true);
        mHandler.removeMessages(MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL);
        // Send delayed timeout
        mHandler.sendEmptyMessageAtTime(MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL,
                mClock.uptimeMillis()
                        + mCurrentBrightnessMapper.getShortTermModelTimeout());
        Slog.i(TAG, "mPreviousShortTermModel" + mPausedShortTermModel.toString());

        // restore interactive mapper.
        mCurrentBrightnessMapper = mInteractiveModeBrightnessMapper;
        resetShortTermModel();

        // if previous stm has been invalidated, and lux has drastically changed, just use
        // the new, reset stm.
        // if previous stm is still valid then revalidate it
        if (!mPausedShortTermModel.maybeReset(mAmbientLux)) {
            setScreenBrightnessByUser(mPausedShortTermModel.mAnchor,
                    mPausedShortTermModel.mBrightness);
        }
        mPausedShortTermModel.copyFrom(tempShortTermModel);

        update();
    }

@@ -1164,6 +1191,77 @@ public class AutomaticBrightnessController {
        }
    }

    private class ShortTermModel {
        // When the short term model is invalidated, we don't necessarily reset it (i.e. clear the
        // user's adjustment) immediately, but wait for a drastic enough change in the ambient
        // light.
        // The anchor determines what were the light levels when the user has set their preference,
        // and we use a relative threshold to determine when to revert to the OEM curve.
        private float mAnchor = -1f;
        private float mBrightness;
        private boolean mIsValid = true;

        private void reset() {
            mAnchor = -1f;
            mBrightness = -1f;
            mIsValid = true;
        }

        private void invalidate() {
            mIsValid = false;
            if (mLoggingEnabled) {
                Slog.d(TAG, "ShortTermModel: invalidate user data");
            }
        }

        private void setUserBrightness(float lux, float brightness) {
            mAnchor = lux;
            mBrightness = brightness;
            mIsValid = true;
            if (mLoggingEnabled) {
                Slog.d(TAG, "ShortTermModel: anchor=" + mAnchor);
            }
        }

        private boolean maybeReset(float currentLux) {
            // If the short term model was invalidated and the change is drastic enough, reset it.
            // Otherwise, we revalidate it.
            if (!mIsValid && mAnchor != -1) {
                if (mCurrentBrightnessMapper != null
                        && mCurrentBrightnessMapper.shouldResetShortTermModel(
                        currentLux, mAnchor)) {
                    resetShortTermModel();
                } else {
                    mIsValid = true;
                }
                return mIsValid;
            }
            return false;
        }

        private void set(float anchor, float brightness, boolean valid) {
            mAnchor = anchor;
            mBrightness = brightness;
            mIsValid = valid;
        }
        private void copyFrom(ShortTermModel from) {
            mAnchor = from.mAnchor;
            mBrightness = from.mBrightness;
            mIsValid = from.mIsValid;
        }

        public String toString() {
            return " mAnchor: " + mAnchor
                    + "\n mBrightness: " + mBrightness
                    + "\n mIsValid: " + mIsValid;
        }

        void dump(PrintWriter pw) {
            pw.println(this);
        }

    }

    private final class AutomaticBrightnessHandler extends Handler {
        public AutomaticBrightnessHandler(Looper looper) {
            super(looper, null, true /*async*/);
@@ -1184,8 +1282,8 @@ public class AutomaticBrightnessController {
                    collectBrightnessAdjustmentSample();
                    break;

                case MSG_INVALIDATE_SHORT_TERM_MODEL:
                    invalidateShortTermModel();
                case MSG_INVALIDATE_CURRENT_SHORT_TERM_MODEL:
                    mShortTermModel.invalidate();
                    break;

                case MSG_UPDATE_FOREGROUND_APP:
@@ -1195,6 +1293,10 @@ public class AutomaticBrightnessController {
                case MSG_UPDATE_FOREGROUND_APP_SYNC:
                    updateForegroundAppSync();
                    break;

                case MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL:
                    mPausedShortTermModel.invalidate();
                    break;
            }
        }
    }
+5 −1
Original line number Diff line number Diff line
@@ -363,13 +363,17 @@ public abstract class BrightnessMappingStrategy {
    public abstract void recalculateSplines(boolean applyAdjustment, float[] adjustment);

    /**
     * Returns the timeout for the short term model
     * Returns the timeout, in milliseconds for the short term model
     *
     * Timeout after which we remove the effects any user interactions might've had on the
     * brightness mapping. This timeout doesn't start until we transition to a non-interactive
     * display policy so that we don't reset while users are using their devices, but also so that
     * we don't erroneously keep the short-term model if the device is dozing but the
     * display is fully on.
     *
     * This timeout is also used when the device switches from interactive screen brightness mode
     * to idle screen brightness mode, to preserve the user's preference when they resume usage of
     * the device, within the specified timeframe.
     */
    public abstract long getShortTermModelTimeout();

+179 −1
Original line number Diff line number Diff line
@@ -126,7 +126,7 @@ public class AutomaticBrightnessControllerTest {
                        return mClock::now;
                    }

                }, // pass in test looper instead, pass in offsetable clock
                }, // pass in test looper instead, pass in offsettable clock
                () -> { }, mTestLooper.getLooper(), mSensorManager, lightSensor,
                mBrightnessMappingStrategy, LIGHT_SENSOR_WARMUP_TIME, BRIGHTNESS_MIN_FLOAT,
                BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE,
@@ -299,6 +299,179 @@ public class AutomaticBrightnessControllerTest {
        verifyNoMoreInteractions(mBrightnessMappingStrategy);
    }

    @Test
    public void testShortTermModelTimesOut() throws Exception {
        ArgumentCaptor<SensorEventListener> listenerCaptor =
                ArgumentCaptor.forClass(SensorEventListener.class);
        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
        SensorEventListener listener = listenerCaptor.getValue();

        // Sensor reads 123 lux,
        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
        // User sets brightness to 100
        mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
                /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
                /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT,
                /* shouldResetShortTermModel= */ true);

        when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);

        mController.switchToIdleMode();
        when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
        when(mBrightnessMappingStrategy.shouldResetShortTermModel(
                123f, 0.5f)).thenReturn(true);

        // Sensor reads 1000 lux,
        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
        mTestLooper.moveTimeForward(
                mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000);
        mTestLooper.dispatchAll();

        mController.switchToInteractiveScreenBrightnessMode();
        mTestLooper.moveTimeForward(4000);
        mTestLooper.dispatchAll();

        // Verify only happens on the first configure. (i.e. not again when switching back)
        // Intentionally using any() to ensure it's not called whatsoever.
        verify(mBrightnessMappingStrategy, times(1))
                .addUserDataPoint(123.0f, 0.5f);
        verify(mBrightnessMappingStrategy, times(1))
                .addUserDataPoint(anyFloat(), anyFloat());
    }

    @Test
    public void testShortTermModelDoesntTimeOut() throws Exception {
        ArgumentCaptor<SensorEventListener> listenerCaptor =
                ArgumentCaptor.forClass(SensorEventListener.class);
        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
        SensorEventListener listener = listenerCaptor.getValue();

        // Sensor reads 123 lux,
        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
        // User sets brightness to 100
        mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
                0.51f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
                false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
                /* shouldResetShortTermModel= */ true);

        when(mBrightnessMappingStrategy.shouldResetShortTermModel(
                anyFloat(), anyFloat())).thenReturn(true);
        when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
        when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.51f);
        when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123.0f);

        mController.switchToIdleMode();
        when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);

        // Time does not move forward, since clock is doesn't increment naturally.
        mTestLooper.dispatchAll();

        // Sensor reads 100000 lux,
        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 678910));
        mController.switchToInteractiveScreenBrightnessMode();

        // Verify short term model is not reset.
        verify(mBrightnessMappingStrategy, never()).clearUserDataPoints();

        // Verify that we add the data point once when the user sets it, and again when we return
        // interactive mode.
        verify(mBrightnessMappingStrategy, times(2))
                .addUserDataPoint(123.0f, 0.51f);
    }

    @Test
    public void testShortTermModelIsRestoredWhenSwitchingWithinTimeout() throws Exception {
        ArgumentCaptor<SensorEventListener> listenerCaptor =
                ArgumentCaptor.forClass(SensorEventListener.class);
        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
        SensorEventListener listener = listenerCaptor.getValue();

        // Sensor reads 123 lux,
        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
        // User sets brightness to 100
        mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
                /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
                /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT,
                /* shouldResetShortTermModel= */ true);

        when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
        when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.5f);
        when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123f);

        mController.switchToIdleMode();
        when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
        when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1f);
        when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(-1f);
        when(mBrightnessMappingStrategy.shouldResetShortTermModel(
                123f, 0.5f)).thenReturn(true);

        // Sensor reads 1000 lux,
        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
        mTestLooper.moveTimeForward(
                mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000);
        mTestLooper.dispatchAll();

        mController.switchToInteractiveScreenBrightnessMode();
        mTestLooper.moveTimeForward(4000);
        mTestLooper.dispatchAll();

        // Verify only happens on the first configure. (i.e. not again when switching back)
        // Intentionally using any() to ensure it's not called whatsoever.
        verify(mBrightnessMappingStrategy, times(1))
                .addUserDataPoint(123.0f, 0.5f);
        verify(mBrightnessMappingStrategy, times(1))
                .addUserDataPoint(anyFloat(), anyFloat());
    }

    @Test
    public void testShortTermModelNotRestoredAfterTimeout() throws Exception {
        ArgumentCaptor<SensorEventListener> listenerCaptor =
                ArgumentCaptor.forClass(SensorEventListener.class);
        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
        SensorEventListener listener = listenerCaptor.getValue();

        // Sensor reads 123 lux,
        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
        // User sets brightness to 100
        mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
                /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
                /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT,
                /* shouldResetShortTermModel= */ true);

        when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);

        when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.5f);
        when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123f);

        mController.switchToIdleMode();
        when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
        when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1f);
        when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(-1f);

        when(mBrightnessMappingStrategy.shouldResetShortTermModel(
                123f, 0.5f)).thenReturn(true);

        // Sensor reads 1000 lux,
        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
        // Do not fast-forward time.
        mTestLooper.dispatchAll();

        mController.switchToInteractiveScreenBrightnessMode();
        // Do not fast-forward time
        mTestLooper.dispatchAll();

        // Verify this happens on the first configure and again when switching back
        // Intentionally using any() to ensure it's not called any other times whatsoever.
        verify(mBrightnessMappingStrategy, times(2))
                .addUserDataPoint(123.0f, 0.5f);
        verify(mBrightnessMappingStrategy, times(2))
                .addUserDataPoint(anyFloat(), anyFloat());
    }

    @Test
    public void testSwitchToIdleMappingStrategy() throws Exception {
        ArgumentCaptor<SensorEventListener> listenerCaptor =
@@ -326,6 +499,11 @@ public class AutomaticBrightnessControllerTest {
        // Called once for init, and once when switching,
        // setAmbientLux() is called twice and once in updateAutoBrightness()
        verify(mBrightnessMappingStrategy, times(5)).isForIdleMode();
        // Called when switching.
        verify(mBrightnessMappingStrategy, times(1)).getShortTermModelTimeout();
        verify(mBrightnessMappingStrategy, times(1)).getUserBrightness();
        verify(mBrightnessMappingStrategy, times(1)).getUserLux();

        // Ensure, after switching, original BMS is not used anymore
        verifyNoMoreInteractions(mBrightnessMappingStrategy);