1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.display;
18 
19 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
20 
21 import static org.junit.Assert.assertArrayEquals;
22 import static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.assertTrue;
24 import static org.mockito.ArgumentMatchers.eq;
25 import static org.mockito.Mockito.any;
26 import static org.mockito.Mockito.anyFloat;
27 import static org.mockito.Mockito.anyInt;
28 import static org.mockito.Mockito.clearInvocations;
29 import static org.mockito.Mockito.never;
30 import static org.mockito.Mockito.times;
31 import static org.mockito.Mockito.verify;
32 import static org.mockito.Mockito.verifyNoMoreInteractions;
33 import static org.mockito.Mockito.when;
34 
35 import android.content.Context;
36 import android.content.pm.ApplicationInfo;
37 import android.hardware.Sensor;
38 import android.hardware.SensorEventListener;
39 import android.hardware.SensorManager;
40 import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
41 import android.os.Handler;
42 import android.os.test.TestLooper;
43 
44 import androidx.test.InstrumentationRegistry;
45 import androidx.test.filters.SmallTest;
46 import androidx.test.runner.AndroidJUnit4;
47 
48 import com.android.server.testutils.OffsettableClock;
49 
50 import org.junit.After;
51 import org.junit.Before;
52 import org.junit.Test;
53 import org.junit.runner.RunWith;
54 import org.mockito.ArgumentCaptor;
55 import org.mockito.Mock;
56 import org.mockito.Mockito;
57 import org.mockito.MockitoAnnotations;
58 
59 @SmallTest
60 @RunWith(AndroidJUnit4.class)
61 public class AutomaticBrightnessControllerTest {
62     private static final float BRIGHTNESS_MIN_FLOAT = 0.0f;
63     private static final float BRIGHTNESS_MAX_FLOAT = 1.0f;
64     private static final int LIGHT_SENSOR_RATE = 20;
65     private static final int INITIAL_LIGHT_SENSOR_RATE = 20;
66     private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG = 0;
67     private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG = 0;
68     private static final float DOZE_SCALE_FACTOR = 0.0f;
69     private static final boolean RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG = false;
70     private static final int LIGHT_SENSOR_WARMUP_TIME = 0;
71     private static final int AMBIENT_LIGHT_HORIZON_SHORT = 1000;
72     private static final int AMBIENT_LIGHT_HORIZON_LONG = 2000;
73     private static final float EPSILON = 0.001f;
74     private OffsettableClock mClock = new OffsettableClock();
75     private TestLooper mTestLooper;
76     private Context mContext;
77     private AutomaticBrightnessController mController;
78     private Sensor mLightSensor;
79 
80     @Mock SensorManager mSensorManager;
81     @Mock BrightnessMappingStrategy mBrightnessMappingStrategy;
82     @Mock BrightnessMappingStrategy mIdleBrightnessMappingStrategy;
83     @Mock HysteresisLevels mAmbientBrightnessThresholds;
84     @Mock HysteresisLevels mScreenBrightnessThresholds;
85     @Mock HysteresisLevels mAmbientBrightnessThresholdsIdle;
86     @Mock HysteresisLevels mScreenBrightnessThresholdsIdle;
87     @Mock Handler mNoOpHandler;
88     @Mock BrightnessRangeController mBrightnessRangeController;
89     @Mock BrightnessThrottler mBrightnessThrottler;
90 
91     @Before
setUp()92     public void setUp() throws Exception {
93         // Share classloader to allow package private access.
94         System.setProperty("dexmaker.share_classloader", "true");
95         MockitoAnnotations.initMocks(this);
96 
97         mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
98         mContext = InstrumentationRegistry.getContext();
99         mController = setupController(mLightSensor, BrightnessMappingStrategy.NO_USER_LUX,
100                 BrightnessMappingStrategy.NO_USER_BRIGHTNESS);
101     }
102 
103     @After
tearDown()104     public void tearDown() {
105         if (mController != null) {
106             // Stop the update Brightness loop.
107             mController.stop();
108             mController = null;
109         }
110     }
111 
setupController(Sensor lightSensor, float userLux, float userBrightness)112     private AutomaticBrightnessController setupController(Sensor lightSensor, float userLux,
113             float userBrightness) {
114         mClock = new OffsettableClock.Stopped();
115         mTestLooper = new TestLooper(mClock::now);
116 
117         AutomaticBrightnessController controller = new AutomaticBrightnessController(
118                 new AutomaticBrightnessController.Injector() {
119                     @Override
120                     public Handler getBackgroundThreadHandler() {
121                         return mNoOpHandler;
122                     }
123 
124                     @Override
125                     AutomaticBrightnessController.Clock createClock() {
126                         return mClock::now;
127                     }
128 
129                 }, // pass in test looper instead, pass in offsettable clock
130                 () -> { }, mTestLooper.getLooper(), mSensorManager, lightSensor,
131                 mBrightnessMappingStrategy, LIGHT_SENSOR_WARMUP_TIME, BRIGHTNESS_MIN_FLOAT,
132                 BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE,
133                 INITIAL_LIGHT_SENSOR_RATE, BRIGHTENING_LIGHT_DEBOUNCE_CONFIG,
134                 DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG,
135                 mAmbientBrightnessThresholds, mScreenBrightnessThresholds,
136                 mAmbientBrightnessThresholdsIdle, mScreenBrightnessThresholdsIdle,
137                 mContext, mBrightnessRangeController, mBrightnessThrottler,
138                 mIdleBrightnessMappingStrategy, AMBIENT_LIGHT_HORIZON_SHORT,
139                 AMBIENT_LIGHT_HORIZON_LONG, userLux, userBrightness
140         );
141 
142         when(mBrightnessRangeController.getCurrentBrightnessMax()).thenReturn(
143                 BRIGHTNESS_MAX_FLOAT);
144         when(mBrightnessRangeController.getCurrentBrightnessMin()).thenReturn(
145                 BRIGHTNESS_MIN_FLOAT);
146         // Disable brightness throttling by default. Individual tests can enable it as needed.
147         when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
148         when(mBrightnessThrottler.isThrottled()).thenReturn(false);
149 
150         // Configure the brightness controller and grab an instance of the sensor listener,
151         // through which we can deliver fake (for test) sensor values.
152         controller.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
153                 0 /* brightness= */, false /* userChangedBrightness= */, 0 /* adjustment= */,
154                 false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
155                 /* shouldResetShortTermModel= */ true);
156 
157         return controller;
158     }
159 
160     @Test
testNoHysteresisAtMinBrightness()161     public void testNoHysteresisAtMinBrightness() throws Exception {
162         ArgumentCaptor<SensorEventListener> listenerCaptor =
163                 ArgumentCaptor.forClass(SensorEventListener.class);
164         verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
165                 eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
166         SensorEventListener listener = listenerCaptor.getValue();
167 
168         // Set up system to return 0.02f as a brightness value
169         float lux1 = 100.0f;
170         // Brightness as float (from 0.0f to 1.0f)
171         float normalizedBrightness1 = 0.02f;
172         when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux1))
173                 .thenReturn(lux1);
174         when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux1))
175                 .thenReturn(lux1);
176         when(mBrightnessMappingStrategy.getBrightness(eq(lux1), eq(null), anyInt()))
177                 .thenReturn(normalizedBrightness1);
178 
179         // This is the important bit: When the new brightness is set, make sure the new
180         // brightening threshold is beyond the maximum brightness value...so that we can test that
181         // our threshold clamping works.
182         when(mScreenBrightnessThresholds.getBrighteningThreshold(normalizedBrightness1))
183                 .thenReturn(1.0f);
184 
185         // Send new sensor value and verify
186         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux1));
187         assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), EPSILON);
188 
189         // Set up system to return 0.0f (minimum possible brightness) as a brightness value
190         float lux2 = 10.0f;
191         float normalizedBrightness2 = 0.0f;
192         when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux2))
193                 .thenReturn(lux2);
194         when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux2))
195                 .thenReturn(lux2);
196         when(mBrightnessMappingStrategy.getBrightness(anyFloat(), eq(null), anyInt()))
197                 .thenReturn(normalizedBrightness2);
198 
199         // Send new sensor value and verify
200         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux2));
201         assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), EPSILON);
202     }
203 
204     @Test
testNoHysteresisAtMaxBrightness()205     public void testNoHysteresisAtMaxBrightness() throws Exception {
206         ArgumentCaptor<SensorEventListener> listenerCaptor =
207                 ArgumentCaptor.forClass(SensorEventListener.class);
208         verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
209                 eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
210         SensorEventListener listener = listenerCaptor.getValue();
211 
212         // Set up system to return 0.98f as a brightness value
213         float lux1 = 100.0f;
214         float normalizedBrightness1 = 0.98f;
215         when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux1))
216                 .thenReturn(lux1);
217         when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux1))
218                 .thenReturn(lux1);
219         when(mBrightnessMappingStrategy.getBrightness(eq(lux1), eq(null), anyInt()))
220                 .thenReturn(normalizedBrightness1);
221 
222         // This is the important bit: When the new brightness is set, make sure the new
223         // brightening threshold is beyond the maximum brightness value...so that we can test that
224         // our threshold clamping works.
225         when(mScreenBrightnessThresholds.getBrighteningThreshold(normalizedBrightness1))
226                 .thenReturn(1.1f);
227 
228         // Send new sensor value and verify
229         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux1));
230         assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), EPSILON);
231 
232 
233         // Set up system to return 1.0f as a brightness value (brightness_max)
234         float lux2 = 110.0f;
235         float normalizedBrightness2 = 1.0f;
236         when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux2))
237                 .thenReturn(lux2);
238         when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux2))
239                 .thenReturn(lux2);
240         when(mBrightnessMappingStrategy.getBrightness(anyFloat(), eq(null), anyInt()))
241                 .thenReturn(normalizedBrightness2);
242 
243         // Send new sensor value and verify
244         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux2));
245         assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), EPSILON);
246     }
247 
248     @Test
testUserAddUserDataPoint()249     public void testUserAddUserDataPoint() throws Exception {
250         ArgumentCaptor<SensorEventListener> listenerCaptor =
251                 ArgumentCaptor.forClass(SensorEventListener.class);
252         verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
253                 eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
254         SensorEventListener listener = listenerCaptor.getValue();
255 
256         // Sensor reads 1000 lux,
257         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
258 
259         // User sets brightness to 100
260         mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
261                 0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
262                 false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
263                 /* shouldResetShortTermModel= */ true);
264 
265         // There should be a user data point added to the mapper.
266         verify(mBrightnessMappingStrategy).addUserDataPoint(1000f, 0.5f);
267     }
268 
269     @Test
testRecalculateSplines()270     public void testRecalculateSplines() throws Exception {
271         // Enabling the light sensor, and setting the ambient lux to 1000
272         int currentLux = 1000;
273         ArgumentCaptor<SensorEventListener> listenerCaptor =
274                 ArgumentCaptor.forClass(SensorEventListener.class);
275         verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
276                 eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
277         SensorEventListener listener = listenerCaptor.getValue();
278         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, currentLux));
279 
280         // User sets brightness to 0.5f
281         when(mBrightnessMappingStrategy.getBrightness(currentLux,
282                 null, ApplicationInfo.CATEGORY_UNDEFINED)).thenReturn(0.5f);
283         mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
284                 0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
285                 false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
286                 /* shouldResetShortTermModel= */ true);
287 
288         //Recalculating the spline with RBC enabled, verifying that the short term model is reset,
289         //and the interaction is learnt in short term model
290         float[] adjustments = new float[]{0.2f, 0.6f};
291         mController.recalculateSplines(true, adjustments);
292         verify(mBrightnessMappingStrategy).clearUserDataPoints();
293         verify(mBrightnessMappingStrategy).recalculateSplines(true, adjustments);
294         verify(mBrightnessMappingStrategy, times(2)).addUserDataPoint(currentLux, 0.5f);
295 
296         clearInvocations(mBrightnessMappingStrategy);
297 
298         // Verify short term model is not learnt when RBC is disabled
299         mController.recalculateSplines(false, adjustments);
300         verify(mBrightnessMappingStrategy).clearUserDataPoints();
301         verify(mBrightnessMappingStrategy).recalculateSplines(false, adjustments);
302         verifyNoMoreInteractions(mBrightnessMappingStrategy);
303     }
304 
305     @Test
testShortTermModelTimesOut()306     public void testShortTermModelTimesOut() throws Exception {
307         ArgumentCaptor<SensorEventListener> listenerCaptor =
308                 ArgumentCaptor.forClass(SensorEventListener.class);
309         verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
310                 eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
311         SensorEventListener listener = listenerCaptor.getValue();
312 
313         // Sensor reads 123 lux,
314         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
315         // User sets brightness to 100
316         mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
317                 /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
318                 /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT,
319                 /* shouldResetShortTermModel= */ true);
320 
321         when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
322 
323         mController.switchToIdleMode();
324         when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
325         when(mBrightnessMappingStrategy.shouldResetShortTermModel(
326                 123f, 0.5f)).thenReturn(true);
327 
328         // Sensor reads 1000 lux,
329         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
330         mTestLooper.moveTimeForward(
331                 mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000);
332         mTestLooper.dispatchAll();
333 
334         mController.switchToInteractiveScreenBrightnessMode();
335         mTestLooper.moveTimeForward(4000);
336         mTestLooper.dispatchAll();
337 
338         // Verify only happens on the first configure. (i.e. not again when switching back)
339         // Intentionally using any() to ensure it's not called whatsoever.
340         verify(mBrightnessMappingStrategy, times(1))
341                 .addUserDataPoint(123.0f, 0.5f);
342         verify(mBrightnessMappingStrategy, times(1))
343                 .addUserDataPoint(anyFloat(), anyFloat());
344     }
345 
346     @Test
testShortTermModelDoesntTimeOut()347     public void testShortTermModelDoesntTimeOut() throws Exception {
348         ArgumentCaptor<SensorEventListener> listenerCaptor =
349                 ArgumentCaptor.forClass(SensorEventListener.class);
350         verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
351                 eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
352         SensorEventListener listener = listenerCaptor.getValue();
353 
354         // Sensor reads 123 lux,
355         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
356         // User sets brightness to 100
357         mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
358                 0.51f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
359                 false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
360                 /* shouldResetShortTermModel= */ true);
361 
362         when(mBrightnessMappingStrategy.shouldResetShortTermModel(
363                 anyFloat(), anyFloat())).thenReturn(true);
364         when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
365         when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.51f);
366         when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123.0f);
367 
368         mController.switchToIdleMode();
369         when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
370 
371         // Time does not move forward, since clock is doesn't increment naturally.
372         mTestLooper.dispatchAll();
373 
374         // Sensor reads 100000 lux,
375         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 678910));
376         mController.switchToInteractiveScreenBrightnessMode();
377 
378         // Verify short term model is not reset.
379         verify(mBrightnessMappingStrategy, never()).clearUserDataPoints();
380 
381         // Verify that we add the data point once when the user sets it, and again when we return
382         // interactive mode.
383         verify(mBrightnessMappingStrategy, times(2))
384                 .addUserDataPoint(123.0f, 0.51f);
385     }
386 
387     @Test
testShortTermModelIsRestoredWhenSwitchingWithinTimeout()388     public void testShortTermModelIsRestoredWhenSwitchingWithinTimeout() throws Exception {
389         ArgumentCaptor<SensorEventListener> listenerCaptor =
390                 ArgumentCaptor.forClass(SensorEventListener.class);
391         verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
392                 eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
393         SensorEventListener listener = listenerCaptor.getValue();
394 
395         // Sensor reads 123 lux,
396         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
397         // User sets brightness to 100
398         mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
399                 /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
400                 /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT,
401                 /* shouldResetShortTermModel= */ true);
402 
403         when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
404         when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.5f);
405         when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123f);
406 
407         mController.switchToIdleMode();
408         when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
409         when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1f);
410         when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(-1f);
411         when(mBrightnessMappingStrategy.shouldResetShortTermModel(
412                 123f, 0.5f)).thenReturn(true);
413 
414         // Sensor reads 1000 lux,
415         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
416         mTestLooper.moveTimeForward(
417                 mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000);
418         mTestLooper.dispatchAll();
419 
420         mController.switchToInteractiveScreenBrightnessMode();
421         mTestLooper.moveTimeForward(4000);
422         mTestLooper.dispatchAll();
423 
424         // Verify only happens on the first configure. (i.e. not again when switching back)
425         // Intentionally using any() to ensure it's not called whatsoever.
426         verify(mBrightnessMappingStrategy, times(1))
427                 .addUserDataPoint(123.0f, 0.5f);
428         verify(mBrightnessMappingStrategy, times(1))
429                 .addUserDataPoint(anyFloat(), anyFloat());
430     }
431 
432     @Test
testShortTermModelNotRestoredAfterTimeout()433     public void testShortTermModelNotRestoredAfterTimeout() throws Exception {
434         ArgumentCaptor<SensorEventListener> listenerCaptor =
435                 ArgumentCaptor.forClass(SensorEventListener.class);
436         verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
437                 eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
438         SensorEventListener listener = listenerCaptor.getValue();
439 
440         // Sensor reads 123 lux,
441         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
442         // User sets brightness to 100
443         mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
444                 /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
445                 /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT,
446                 /* shouldResetShortTermModel= */ true);
447 
448         when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
449 
450         when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.5f);
451         when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123f);
452 
453         mController.switchToIdleMode();
454         when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
455         when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1f);
456         when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(-1f);
457 
458         when(mBrightnessMappingStrategy.shouldResetShortTermModel(
459                 123f, 0.5f)).thenReturn(true);
460 
461         // Sensor reads 1000 lux,
462         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
463         // Do not fast-forward time.
464         mTestLooper.dispatchAll();
465 
466         mController.switchToInteractiveScreenBrightnessMode();
467         // Do not fast-forward time
468         mTestLooper.dispatchAll();
469 
470         // Verify this happens on the first configure and again when switching back
471         // Intentionally using any() to ensure it's not called any other times whatsoever.
472         verify(mBrightnessMappingStrategy, times(2))
473                 .addUserDataPoint(123.0f, 0.5f);
474         verify(mBrightnessMappingStrategy, times(2))
475                 .addUserDataPoint(anyFloat(), anyFloat());
476     }
477 
478     @Test
testSwitchBetweenModesNoUserInteractions()479     public void testSwitchBetweenModesNoUserInteractions() throws Exception {
480         ArgumentCaptor<SensorEventListener> listenerCaptor =
481                 ArgumentCaptor.forClass(SensorEventListener.class);
482         verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
483                 eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
484         SensorEventListener listener = listenerCaptor.getValue();
485 
486         // Sensor reads 123 lux,
487         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
488         when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
489         when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1.0f);
490         when(mBrightnessMappingStrategy.getUserLux()).thenReturn(-1.0f);
491 
492         // No user brightness interaction.
493 
494         mController.switchToIdleMode();
495         when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
496         when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1.0f);
497         when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(-1.0f);
498 
499         // Sensor reads 1000 lux,
500         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
501         // Do not fast-forward time.
502         mTestLooper.dispatchAll();
503 
504         mController.switchToInteractiveScreenBrightnessMode();
505         // Do not fast-forward time
506         mTestLooper.dispatchAll();
507 
508         // Ensure that there are no data points added, since the user has never adjusted the
509         // brightness
510         verify(mBrightnessMappingStrategy, times(0))
511                 .addUserDataPoint(anyFloat(), anyFloat());
512     }
513 
514     @Test
testSwitchToIdleMappingStrategy()515     public void testSwitchToIdleMappingStrategy() throws Exception {
516         ArgumentCaptor<SensorEventListener> listenerCaptor =
517                 ArgumentCaptor.forClass(SensorEventListener.class);
518         verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
519                 eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
520         SensorEventListener listener = listenerCaptor.getValue();
521 
522         // Sensor reads 1000 lux,
523         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
524 
525         // User sets brightness to 100
526         mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
527                 0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
528                 false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
529                 /* shouldResetShortTermModel= */ true);
530 
531         // There should be a user data point added to the mapper.
532         verify(mBrightnessMappingStrategy, times(1)).addUserDataPoint(1000f, 0.5f);
533         verify(mBrightnessMappingStrategy, times(2)).setBrightnessConfiguration(any());
534         verify(mBrightnessMappingStrategy, times(3)).getBrightness(anyFloat(), any(), anyInt());
535 
536         // Now let's do the same for idle mode
537         mController.switchToIdleMode();
538         // Called once for init, and once when switching,
539         // setAmbientLux() is called twice and once in updateAutoBrightness()
540         verify(mBrightnessMappingStrategy, times(5)).isForIdleMode();
541         // Called when switching.
542         verify(mBrightnessMappingStrategy, times(1)).getShortTermModelTimeout();
543         verify(mBrightnessMappingStrategy, times(1)).getUserBrightness();
544         verify(mBrightnessMappingStrategy, times(1)).getUserLux();
545 
546         // Ensure, after switching, original BMS is not used anymore
547         verifyNoMoreInteractions(mBrightnessMappingStrategy);
548 
549         // User sets idle brightness to 0.5
550         mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
551                 0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
552                 false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
553                 /* shouldResetShortTermModel= */ true);
554 
555         // Ensure we use the correct mapping strategy
556         verify(mIdleBrightnessMappingStrategy, times(1)).addUserDataPoint(1000f, 0.5f);
557     }
558 
559     @Test
testAmbientLightHorizon()560     public void testAmbientLightHorizon() throws Exception {
561         ArgumentCaptor<SensorEventListener> listenerCaptor =
562                 ArgumentCaptor.forClass(SensorEventListener.class);
563         verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
564                 eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
565         SensorEventListener listener = listenerCaptor.getValue();
566 
567         long increment = 500;
568         // set autobrightness to low
569         // t = 0
570         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
571 
572         // t = 500
573         mClock.fastForward(increment);
574         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
575 
576         // t = 1000
577         mClock.fastForward(increment);
578         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
579         assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
580 
581         // t = 1500
582         mClock.fastForward(increment);
583         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
584         assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
585 
586         // t = 2000
587         // ensure that our reading is at 0.
588         mClock.fastForward(increment);
589         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
590         assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
591 
592         // t = 2500
593         // first 10000 lux sensor event reading
594         mClock.fastForward(increment);
595         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
596         assertTrue(mController.getAmbientLux() > 0.0f);
597         assertTrue(mController.getAmbientLux() < 10000.0f);
598 
599         // t = 3000
600         // lux reading should still not yet be 10000.
601         mClock.fastForward(increment);
602         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
603         assertTrue(mController.getAmbientLux() > 0.0f);
604         assertTrue(mController.getAmbientLux() < 10000.0f);
605 
606         // t = 3500
607         mClock.fastForward(increment);
608         // lux has been high (10000) for 1000ms.
609         // lux reading should be 10000
610         // short horizon (ambient lux) is high, long horizon is still not high
611         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
612         assertEquals(10000.0f, mController.getAmbientLux(), EPSILON);
613 
614         // t = 4000
615         // stay high
616         mClock.fastForward(increment);
617         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
618         assertEquals(10000.0f, mController.getAmbientLux(), EPSILON);
619 
620         // t = 4500
621         Mockito.clearInvocations(mBrightnessMappingStrategy);
622         mClock.fastForward(increment);
623         // short horizon is high, long horizon is high too
624         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
625         verify(mBrightnessMappingStrategy, times(1)).getBrightness(10000, null, -1);
626         assertEquals(10000.0f, mController.getAmbientLux(), EPSILON);
627 
628         // t = 5000
629         mClock.fastForward(increment);
630         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
631         assertTrue(mController.getAmbientLux() > 0.0f);
632         assertTrue(mController.getAmbientLux() < 10000.0f);
633 
634         // t = 5500
635         mClock.fastForward(increment);
636         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
637         assertTrue(mController.getAmbientLux() > 0.0f);
638         assertTrue(mController.getAmbientLux() < 10000.0f);
639 
640         // t = 6000
641         mClock.fastForward(increment);
642         // ambient lux goes to 0
643         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
644         assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
645 
646         // only the values within the horizon should be kept
647         assertArrayEquals(new float[] {10000, 10000, 0, 0, 0}, mController.getLastSensorValues(),
648                 EPSILON);
649         assertArrayEquals(new long[] {4000, 4500, 5000, 5500, 6000},
650                 mController.getLastSensorTimestamps());
651     }
652 
653     @Test
654     public void testHysteresisLevels() {
655         float[] ambientBrighteningThresholds = {50, 100};
656         float[] ambientDarkeningThresholds = {10, 20};
657         float[] ambientThresholdLevels = {0, 500};
658         float ambientDarkeningMinChangeThreshold = 3.0f;
659         float ambientBrighteningMinChangeThreshold = 1.5f;
660         HysteresisLevels hysteresisLevels = new HysteresisLevels(ambientBrighteningThresholds,
661                 ambientDarkeningThresholds, ambientThresholdLevels, ambientThresholdLevels,
662                 ambientDarkeningMinChangeThreshold, ambientBrighteningMinChangeThreshold);
663 
664         // test low, activate minimum change thresholds.
665         assertEquals(1.5f, hysteresisLevels.getBrighteningThreshold(0.0f), EPSILON);
666         assertEquals(0f, hysteresisLevels.getDarkeningThreshold(0.0f), EPSILON);
667         assertEquals(1f, hysteresisLevels.getDarkeningThreshold(4.0f), EPSILON);
668 
669         // test max
670         // epsilon is x2 here, since the next floating point value about 20,000 is 0.0019531 greater
671         assertEquals(20000f, hysteresisLevels.getBrighteningThreshold(10000.0f), EPSILON * 2);
672         assertEquals(8000f, hysteresisLevels.getDarkeningThreshold(10000.0f), EPSILON);
673 
674         // test just below threshold
675         assertEquals(748.5f, hysteresisLevels.getBrighteningThreshold(499f), EPSILON);
676         assertEquals(449.1f, hysteresisLevels.getDarkeningThreshold(499f), EPSILON);
677 
678         // test at (considered above) threshold
679         assertEquals(1000f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON);
680         assertEquals(400f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON);
681     }
682 
683     @Test
684     public void testBrightnessGetsThrottled() throws Exception {
685         ArgumentCaptor<SensorEventListener> listenerCaptor =
686                 ArgumentCaptor.forClass(SensorEventListener.class);
687         verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
688                 eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
689         SensorEventListener listener = listenerCaptor.getValue();
690 
691         // Set up system to return max brightness at 100 lux
692         final float normalizedBrightness = BRIGHTNESS_MAX_FLOAT;
693         final float lux = 100.0f;
694         when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux))
695                 .thenReturn(lux);
696         when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux))
697                 .thenReturn(lux);
698         when(mBrightnessMappingStrategy.getBrightness(eq(lux), eq(null), anyInt()))
699                 .thenReturn(normalizedBrightness);
700 
701         // Sensor reads 100 lux. We should get max brightness.
702         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
703         assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f);
704         assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getRawAutomaticScreenBrightness(), 0.0f);
705 
706         // Apply throttling and notify ABC (simulates DisplayPowerController#updatePowerState())
707         final float throttledBrightness = 0.123f;
708         when(mBrightnessThrottler.getBrightnessCap()).thenReturn(throttledBrightness);
709         when(mBrightnessThrottler.isThrottled()).thenReturn(true);
710         mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
711                 BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
712                 0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
713                 /* shouldResetShortTermModel= */ true);
714         assertEquals(throttledBrightness, mController.getAutomaticScreenBrightness(), 0.0f);
715         // The raw brightness value should not have throttling applied
716         assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getRawAutomaticScreenBrightness(), 0.0f);
717 
718         // Remove throttling and notify ABC again
719         when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
720         when(mBrightnessThrottler.isThrottled()).thenReturn(false);
721         mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
722                 BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
723                 0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
724                 /* shouldResetShortTermModel= */ true);
725         assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f);
726         assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getRawAutomaticScreenBrightness(), 0.0f);
727     }
728 
729     @Test
730     public void testGetSensorReadings() throws Exception {
731         ArgumentCaptor<SensorEventListener> listenerCaptor =
732                 ArgumentCaptor.forClass(SensorEventListener.class);
733         verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
734                 eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
735         SensorEventListener listener = listenerCaptor.getValue();
736 
737         // Choose values such that the ring buffer's capacity is extended and the buffer is pruned
738         int increment = 11;
739         int lux = 5000;
740         for (int i = 0; i < 1000; i++) {
741             lux += increment;
742             mClock.fastForward(increment);
743             listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
744         }
745 
746         int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment + 1);
747         float[] sensorValues = mController.getLastSensorValues();
748         long[] sensorTimestamps = mController.getLastSensorTimestamps();
749 
750         // Only the values within the horizon should be kept
751         assertEquals(valuesCount, sensorValues.length);
752         assertEquals(valuesCount, sensorTimestamps.length);
753 
754         long sensorTimestamp = mClock.now();
755         for (int i = valuesCount - 1; i >= 1; i--) {
756             assertEquals(lux, sensorValues[i], EPSILON);
757             assertEquals(sensorTimestamp, sensorTimestamps[i]);
758             lux -= increment;
759             sensorTimestamp -= increment;
760         }
761         assertEquals(lux, sensorValues[0], EPSILON);
762         assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]);
763     }
764 
765     @Test
766     public void testGetSensorReadingsFullBuffer() throws Exception {
767         ArgumentCaptor<SensorEventListener> listenerCaptor =
768                 ArgumentCaptor.forClass(SensorEventListener.class);
769         verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
770                 eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
771         SensorEventListener listener = listenerCaptor.getValue();
772         int initialCapacity = 150;
773 
774         // Choose values such that the ring buffer is pruned
775         int increment1 = 200;
776         int lux = 5000;
777         for (int i = 0; i < 20; i++) {
778             lux += increment1;
779             mClock.fastForward(increment1);
780             listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
781         }
782 
783         int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment1 + 1);
784 
785         // Choose values such that the buffer becomes full
786         int increment2 = 1;
787         for (int i = 0; i < initialCapacity - valuesCount; i++) {
788             lux += increment2;
789             mClock.fastForward(increment2);
790             listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
791         }
792 
793         float[] sensorValues = mController.getLastSensorValues();
794         long[] sensorTimestamps = mController.getLastSensorTimestamps();
795 
796         // The buffer should be full
797         assertEquals(initialCapacity, sensorValues.length);
798         assertEquals(initialCapacity, sensorTimestamps.length);
799 
800         long sensorTimestamp = mClock.now();
801         for (int i = initialCapacity - 1; i >= 1; i--) {
802             assertEquals(lux, sensorValues[i], EPSILON);
803             assertEquals(sensorTimestamp, sensorTimestamps[i]);
804 
805             if (i >= valuesCount) {
806                 lux -= increment2;
807                 sensorTimestamp -= increment2;
808             } else {
809                 lux -= increment1;
810                 sensorTimestamp -= increment1;
811             }
812         }
813         assertEquals(lux, sensorValues[0], EPSILON);
814         assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]);
815     }
816 
817     @Test
818     public void testResetShortTermModelWhenConfigChanges() {
819         when(mBrightnessMappingStrategy.isForIdleMode()).thenReturn(false);
820         when(mBrightnessMappingStrategy.setBrightnessConfiguration(any())).thenReturn(true);
821 
822         mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
823                 BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
824                 0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
825                 /* shouldResetShortTermModel= */ false);
826         verify(mBrightnessMappingStrategy, never()).clearUserDataPoints();
827 
828         mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
829                 BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
830                 0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
831                 /* shouldResetShortTermModel= */ true);
832         verify(mBrightnessMappingStrategy).clearUserDataPoints();
833     }
834 
835     @Test
836     public void testUseProvidedShortTermModel() {
837         verify(mBrightnessMappingStrategy, never()).addUserDataPoint(anyFloat(), anyFloat());
838 
839         float userLux = 1000;
840         float userBrightness = 0.3f;
841         setupController(mLightSensor, userLux, userBrightness);
842         verify(mBrightnessMappingStrategy).addUserDataPoint(userLux, userBrightness);
843     }
844 }
845