Migrating WearBedtimeModeClamper to BrightnessStateModifier interface

Bug: b/367964194
Test: atest BrightnessWearBedtimeModeModifierTest
Flag: EXEMPT refactoring

Change-Id: Ic5a8d0aa6a452924bf59ac60f60a33775ddc6e3b
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
index cf44ac0..a1fd164 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
@@ -74,6 +74,5 @@
 
     protected enum Type {
         POWER,
-        WEAR_BEDTIME_MODE,
     }
 }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 9404034..a10094f 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -218,9 +218,7 @@
             return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
         } else if (mClamperType == Type.POWER) {
             return BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC;
-        } else if (mClamperType == Type.WEAR_BEDTIME_MODE) {
-            return BrightnessInfo.BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE;
-        } else {
+        }  else {
             Slog.wtf(TAG, "BrightnessMaxReason not mapped for type=" + mClamperType);
             return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
         }
@@ -350,10 +348,6 @@
                             data, currentBrightness));
                 }
             }
-            if (flags.isBrightnessWearBedtimeModeClamperEnabled()) {
-                clampers.add(new BrightnessWearBedtimeModeClamper(handler, context,
-                        clamperChangeListener, data));
-            }
             return clampers;
         }
 
@@ -362,6 +356,10 @@
                 DisplayDeviceData data) {
             List<BrightnessStateModifier> modifiers = new ArrayList<>();
             modifiers.add(new BrightnessThermalModifier(handler, listener, data));
+            if (flags.isBrightnessWearBedtimeModeClamperEnabled()) {
+                modifiers.add(new BrightnessWearBedtimeModeModifier(handler, context,
+                        listener, data));
+            }
 
             modifiers.add(new DisplayDimModifier(context));
             modifiers.add(new BrightnessLowPowerModeModifier());
@@ -395,7 +393,7 @@
      */
     public static class DisplayDeviceData implements BrightnessThermalModifier.ThermalData,
             BrightnessPowerClamper.PowerData,
-            BrightnessWearBedtimeModeClamper.WearBedtimeModeData {
+            BrightnessWearBedtimeModeModifier.WearBedtimeModeData {
         @NonNull
         private final String mUniqueDisplayId;
         @NonNull
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java
deleted file mode 100644
index 1902e35..0000000
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2023 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.server.display.brightness.clamper;
-
-import android.annotation.NonNull;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.provider.Settings;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-public class BrightnessWearBedtimeModeClamper extends
-        BrightnessClamper<BrightnessWearBedtimeModeClamper.WearBedtimeModeData> {
-
-    public static final int BEDTIME_MODE_OFF = 0;
-    public static final int BEDTIME_MODE_ON = 1;
-
-    private final Context mContext;
-
-    private final ContentObserver mSettingsObserver;
-
-    BrightnessWearBedtimeModeClamper(Handler handler, Context context,
-            BrightnessClamperController.ClamperChangeListener listener, WearBedtimeModeData data) {
-        this(new Injector(), handler, context, listener, data);
-    }
-
-    @VisibleForTesting
-    BrightnessWearBedtimeModeClamper(Injector injector, Handler handler, Context context,
-            BrightnessClamperController.ClamperChangeListener listener, WearBedtimeModeData data) {
-        super(handler, listener);
-        mContext = context;
-        mBrightnessCap = data.getBrightnessWearBedtimeModeCap();
-        mSettingsObserver = new ContentObserver(mHandler) {
-            @Override
-            public void onChange(boolean selfChange) {
-                final int bedtimeModeSetting = Settings.Global.getInt(
-                        mContext.getContentResolver(),
-                        Settings.Global.Wearable.BEDTIME_MODE,
-                        BEDTIME_MODE_OFF);
-                mIsActive = bedtimeModeSetting == BEDTIME_MODE_ON;
-                mChangeListener.onChanged();
-            }
-        };
-        injector.registerBedtimeModeObserver(context.getContentResolver(), mSettingsObserver);
-    }
-
-    @NonNull
-    @Override
-    Type getType() {
-        return Type.WEAR_BEDTIME_MODE;
-    }
-
-    @Override
-    void onDeviceConfigChanged() {}
-
-    @Override
-    void onDisplayChanged(WearBedtimeModeData displayData) {
-        mHandler.post(() -> {
-            mBrightnessCap = displayData.getBrightnessWearBedtimeModeCap();
-            mChangeListener.onChanged();
-        });
-    }
-
-    @Override
-    void stop() {
-        mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
-    }
-
-    interface WearBedtimeModeData {
-        float getBrightnessWearBedtimeModeCap();
-    }
-
-    @VisibleForTesting
-    static class Injector {
-        void registerBedtimeModeObserver(@NonNull ContentResolver cr,
-                @NonNull ContentObserver observer) {
-            cr.registerContentObserver(
-                    Settings.Global.getUriFor(Settings.Global.Wearable.BEDTIME_MODE),
-                    /* notifyForDescendants= */ false, observer, UserHandle.USER_ALL);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeModifier.java
new file mode 100644
index 0000000..c9c8c33
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeModifier.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2023 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.server.display.brightness.clamper;
+
+import android.annotation.NonNull;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.hardware.display.BrightnessInfo;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import java.io.PrintWriter;
+
+public class BrightnessWearBedtimeModeModifier implements BrightnessStateModifier,
+        BrightnessClamperController.DisplayDeviceDataListener,
+        BrightnessClamperController.StatefulModifier {
+
+    public static final int BEDTIME_MODE_OFF = 0;
+    public static final int BEDTIME_MODE_ON = 1;
+
+    private final Context mContext;
+
+    private final ContentObserver mSettingsObserver;
+    protected final Handler mHandler;
+    protected final BrightnessClamperController.ClamperChangeListener mChangeListener;
+
+    private float mBrightnessCap;
+    private boolean mIsActive = false;
+    private boolean mApplied = false;
+
+    BrightnessWearBedtimeModeModifier(Handler handler, Context context,
+            BrightnessClamperController.ClamperChangeListener listener, WearBedtimeModeData data) {
+        this(new Injector(), handler, context, listener, data);
+    }
+
+    @VisibleForTesting
+    BrightnessWearBedtimeModeModifier(Injector injector, Handler handler, Context context,
+            BrightnessClamperController.ClamperChangeListener listener, WearBedtimeModeData data) {
+        mHandler = handler;
+        mChangeListener = listener;
+        mContext = context;
+        mBrightnessCap = data.getBrightnessWearBedtimeModeCap();
+        mSettingsObserver = new ContentObserver(mHandler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                final int bedtimeModeSetting = Settings.Global.getInt(
+                        mContext.getContentResolver(),
+                        Settings.Global.Wearable.BEDTIME_MODE,
+                        BEDTIME_MODE_OFF);
+                mIsActive = bedtimeModeSetting == BEDTIME_MODE_ON;
+                mChangeListener.onChanged();
+            }
+        };
+        injector.registerBedtimeModeObserver(context.getContentResolver(), mSettingsObserver);
+    }
+
+    //region BrightnessStateModifier
+    @Override
+    public void apply(DisplayManagerInternal.DisplayPowerRequest request,
+            DisplayBrightnessState.Builder stateBuilder) {
+        if (mIsActive && stateBuilder.getMaxBrightness() > mBrightnessCap) {
+            stateBuilder.setMaxBrightness(mBrightnessCap);
+            stateBuilder.setBrightness(Math.min(stateBuilder.getBrightness(), mBrightnessCap));
+            stateBuilder.setBrightnessMaxReason(
+                    BrightnessInfo.BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE);
+            stateBuilder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED);
+            // set fast change only when modifier is activated.
+            // this will allow auto brightness to apply slow change even when modifier is active
+            if (!mApplied) {
+                stateBuilder.setIsSlowChange(false);
+            }
+            mApplied = true;
+        } else {
+            mApplied = false;
+        }
+    }
+
+    @Override
+    public void stop() {
+        mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+        writer.println("BrightnessWearBedtimeModeModifier:");
+        writer.println("  mBrightnessCap: " + mBrightnessCap);
+        writer.println("  mIsActive: " + mIsActive);
+        writer.println("  mApplied: " + mApplied);
+    }
+
+    @Override
+    public boolean shouldListenToLightSensor() {
+        return false;
+    }
+
+    @Override
+    public void setAmbientLux(float lux) {
+        // noop
+    }
+    //endregion
+
+    //region DisplayDeviceDataListener
+    @Override
+    public void onDisplayChanged(BrightnessClamperController.DisplayDeviceData data) {
+        mHandler.post(() -> {
+            mBrightnessCap = data.getBrightnessWearBedtimeModeCap();
+            mChangeListener.onChanged();
+        });
+    }
+    //endregion
+
+    //region StatefulModifier
+    @Override
+    public void applyStateChange(
+            BrightnessClamperController.ModifiersAggregatedState aggregatedState) {
+        if (mIsActive && aggregatedState.mMaxBrightness > mBrightnessCap) {
+            aggregatedState.mMaxBrightness = mBrightnessCap;
+            aggregatedState.mMaxBrightnessReason =
+                    BrightnessInfo.BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE;
+        }
+    }
+    //endregion
+
+    interface WearBedtimeModeData {
+        float getBrightnessWearBedtimeModeCap();
+    }
+
+    @VisibleForTesting
+    static class Injector {
+        void registerBedtimeModeObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer) {
+            cr.registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.Wearable.BEDTIME_MODE),
+                    /* notifyForDescendants= */ false, observer, UserHandle.USER_ALL);
+        }
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java
deleted file mode 100644
index 306b4f8..0000000
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2023 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.server.display.brightness.clamper;
-
-import static com.android.server.display.brightness.clamper.BrightnessWearBedtimeModeClamper.BEDTIME_MODE_OFF;
-import static com.android.server.display.brightness.clamper.BrightnessWearBedtimeModeClamper.BEDTIME_MODE_ON;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.verify;
-
-import android.content.ContentResolver;
-import android.database.ContentObserver;
-import android.provider.Settings;
-import android.testing.TestableContext;
-
-import androidx.annotation.NonNull;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.internal.display.BrightnessSynchronizer;
-import com.android.server.testutils.TestHandler;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-public class BrightnessWearBedtimeModeClamperTest {
-
-    private static final float BRIGHTNESS_CAP = 0.3f;
-
-    @Mock
-    private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener;
-
-    @Rule
-    public final TestableContext mContext = new TestableContext(
-            InstrumentationRegistry.getInstrumentation().getContext());
-
-    private final TestHandler mTestHandler = new TestHandler(null);
-    private final TestInjector mInjector = new TestInjector();
-    private BrightnessWearBedtimeModeClamper mClamper;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mClamper = new BrightnessWearBedtimeModeClamper(mInjector, mTestHandler, mContext,
-                mMockClamperChangeListener, () -> BRIGHTNESS_CAP);
-        mTestHandler.flush();
-    }
-
-    @Test
-    public void testBrightnessCap() {
-        assertEquals(BRIGHTNESS_CAP, mClamper.getBrightnessCap(), BrightnessSynchronizer.EPSILON);
-    }
-
-    @Test
-    public void testBedtimeModeOn() {
-        setBedtimeModeEnabled(true);
-        assertTrue(mClamper.isActive());
-        verify(mMockClamperChangeListener).onChanged();
-    }
-
-    @Test
-    public void testBedtimeModeOff() {
-        setBedtimeModeEnabled(false);
-        assertFalse(mClamper.isActive());
-        verify(mMockClamperChangeListener).onChanged();
-    }
-
-    @Test
-    public void testType() {
-        assertEquals(BrightnessClamper.Type.WEAR_BEDTIME_MODE, mClamper.getType());
-    }
-
-    @Test
-    public void testOnDisplayChanged() {
-        float newBrightnessCap = 0.61f;
-
-        mClamper.onDisplayChanged(() -> newBrightnessCap);
-        mTestHandler.flush();
-
-        assertEquals(newBrightnessCap, mClamper.getBrightnessCap(), BrightnessSynchronizer.EPSILON);
-        verify(mMockClamperChangeListener).onChanged();
-    }
-
-    private void setBedtimeModeEnabled(boolean enabled) {
-        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.Wearable.BEDTIME_MODE,
-                enabled ? BEDTIME_MODE_ON : BEDTIME_MODE_OFF);
-        mInjector.notifyBedtimeModeChanged();
-        mTestHandler.flush();
-    }
-
-    private static class TestInjector extends BrightnessWearBedtimeModeClamper.Injector {
-
-        private ContentObserver mObserver;
-
-        @Override
-        void registerBedtimeModeObserver(@NonNull ContentResolver cr,
-                @NonNull ContentObserver observer) {
-            mObserver = observer;
-        }
-
-        private void notifyBedtimeModeChanged() {
-            if (mObserver != null) {
-                mObserver.dispatchChange(/* selfChange= */ false,
-                        Settings.Global.getUriFor(Settings.Global.Wearable.BEDTIME_MODE));
-            }
-        }
-    }
-}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeModifierTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeModifierTest.java
new file mode 100644
index 0000000..8271a0f
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeModifierTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2023 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.server.display.brightness.clamper;
+
+import static com.android.server.display.brightness.clamper.BrightnessWearBedtimeModeModifier.BEDTIME_MODE_OFF;
+import static com.android.server.display.brightness.clamper.BrightnessWearBedtimeModeModifier.BEDTIME_MODE_ON;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.hardware.display.BrightnessInfo;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.provider.Settings;
+import android.testing.TestableContext;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.clamper.BrightnessClamperController.ModifiersAggregatedState;
+import com.android.server.testutils.TestHandler;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class BrightnessWearBedtimeModeModifierTest {
+    private static final int NO_MODIFIER = 0;
+    private static final float BRIGHTNESS_CAP = 0.3f;
+    private static final String DISPLAY_ID = "displayId";
+
+    @Mock
+    private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener;
+    @Mock
+    private DisplayManagerInternal.DisplayPowerRequest mMockRequest;
+    @Mock
+    private DisplayDeviceConfig mMockDisplayDeviceConfig;
+    @Mock
+    private IBinder mMockBinder;
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getContext());
+
+    private final TestHandler mTestHandler = new TestHandler(null);
+    private final TestInjector mInjector = new TestInjector();
+    private BrightnessWearBedtimeModeModifier mModifier;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mModifier = new BrightnessWearBedtimeModeModifier(mInjector, mTestHandler, mContext,
+                mMockClamperChangeListener, () -> BRIGHTNESS_CAP);
+        mTestHandler.flush();
+    }
+
+    @Test
+    public void testBedtimeModeOff() {
+        setBedtimeModeEnabled(false);
+        assertModifierState(
+                0.5f, true,
+                PowerManager.BRIGHTNESS_MAX, 0.5f,
+                false, true);
+        verify(mMockClamperChangeListener).onChanged();
+    }
+
+    @Test
+    public void testBedtimeModeOn() {
+        setBedtimeModeEnabled(true);
+        assertModifierState(
+                0.5f, true,
+                BRIGHTNESS_CAP, BRIGHTNESS_CAP,
+                true, false);
+        verify(mMockClamperChangeListener).onChanged();
+    }
+
+    @Test
+    public void testOnDisplayChanged() {
+        setBedtimeModeEnabled(true);
+        clearInvocations(mMockClamperChangeListener);
+        float newBrightnessCap = 0.61f;
+        onDisplayChange(newBrightnessCap);
+        mTestHandler.flush();
+
+        assertModifierState(
+                0.5f, true,
+                newBrightnessCap, 0.5f,
+                true, false);
+        verify(mMockClamperChangeListener).onChanged();
+    }
+
+    private void setBedtimeModeEnabled(boolean enabled) {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.Wearable.BEDTIME_MODE,
+                enabled ? BEDTIME_MODE_ON : BEDTIME_MODE_OFF);
+        mInjector.notifyBedtimeModeChanged();
+        mTestHandler.flush();
+    }
+
+    private void onDisplayChange(float brightnessCap) {
+        when(mMockDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode())
+                .thenReturn(brightnessCap);
+        mModifier.onDisplayChanged(ClamperTestUtilsKt.createDisplayDeviceData(
+                mMockDisplayDeviceConfig, mMockBinder, DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID));
+    }
+
+    private void assertModifierState(
+            float currentBrightness,
+            boolean currentSlowChange,
+            float maxBrightness, float brightness,
+            boolean isActive,
+            boolean isSlowChange) {
+        ModifiersAggregatedState modifierState = new ModifiersAggregatedState();
+        DisplayBrightnessState.Builder stateBuilder = DisplayBrightnessState.builder();
+        stateBuilder.setBrightness(currentBrightness);
+        stateBuilder.setIsSlowChange(currentSlowChange);
+
+        int maxBrightnessReason = isActive ? BrightnessInfo.BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE
+                : BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
+        int modifier = isActive ? BrightnessReason.MODIFIER_THROTTLED : NO_MODIFIER;
+
+        mModifier.applyStateChange(modifierState);
+        assertThat(modifierState.mMaxBrightness).isEqualTo(maxBrightness);
+        assertThat(modifierState.mMaxBrightnessReason).isEqualTo(maxBrightnessReason);
+
+        mModifier.apply(mMockRequest, stateBuilder);
+
+        assertThat(stateBuilder.getMaxBrightness())
+                .isWithin(BrightnessSynchronizer.EPSILON).of(maxBrightness);
+        assertThat(stateBuilder.getBrightness())
+                .isWithin(BrightnessSynchronizer.EPSILON).of(brightness);
+        assertThat(stateBuilder.getBrightnessMaxReason()).isEqualTo(maxBrightnessReason);
+        assertThat(stateBuilder.getBrightnessReason().getModifier()).isEqualTo(modifier);
+        assertThat(stateBuilder.isSlowChange()).isEqualTo(isSlowChange);
+    }
+
+
+    private static class TestInjector extends BrightnessWearBedtimeModeModifier.Injector {
+
+        private ContentObserver mObserver;
+
+        @Override
+        void registerBedtimeModeObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer) {
+            mObserver = observer;
+        }
+
+        private void notifyBedtimeModeChanged() {
+            if (mObserver != null) {
+                mObserver.dispatchChange(/* selfChange= */ false,
+                        Settings.Global.getUriFor(Settings.Global.Wearable.BEDTIME_MODE));
+            }
+        }
+    }
+}