xref: /aosp_15_r20/cts/tests/tests/display/src/android/display/cts/BrightnessTest.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1 /*
2  * Copyright (C) 2018 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 android.display.cts;
18 
19 import static android.hardware.display.BrightnessCorrection.createScaleAndTranslateLog;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.junit.Assert.assertEquals;
24 import static org.junit.Assert.assertFalse;
25 import static org.junit.Assert.assertNotNull;
26 import static org.junit.Assert.assertNull;
27 import static org.junit.Assert.assertThrows;
28 import static org.junit.Assert.assertTrue;
29 import static org.junit.Assert.fail;
30 import static org.junit.Assume.assumeNotNull;
31 import static org.junit.Assume.assumeTrue;
32 
33 import android.Manifest;
34 import android.content.Context;
35 import android.content.pm.ApplicationInfo;
36 import android.content.pm.PackageInfo;
37 import android.content.pm.PackageManager;
38 import android.hardware.display.BrightnessChangeEvent;
39 import android.hardware.display.BrightnessConfiguration;
40 import android.hardware.display.DisplayManager;
41 import android.platform.test.annotations.AppModeFull;
42 import android.provider.Settings;
43 import android.util.Pair;
44 
45 import androidx.test.filters.MediumTest;
46 import androidx.test.platform.app.InstrumentationRegistry;
47 import androidx.test.runner.AndroidJUnit4;
48 
49 import com.google.common.collect.Range;
50 
51 import org.junit.Before;
52 import org.junit.Ignore;
53 import org.junit.Test;
54 import org.junit.runner.RunWith;
55 
56 import java.util.ArrayList;
57 import java.util.HashMap;
58 import java.util.List;
59 import java.util.Map;
60 import java.util.function.Predicate;
61 
62 @AppModeFull
63 @MediumTest
64 @RunWith(AndroidJUnit4.class)
65 public class BrightnessTest extends TestBase {
66 
67     private Map<Long, BrightnessChangeEvent> mLastReadEvents = new HashMap<>();
68     private DisplayManager mDisplayManager;
69     private Context mContext;
70     private PackageManager mPackageManager;
71 
72     @Before
setUp()73     public void setUp() {
74         mContext = InstrumentationRegistry.getInstrumentation().getContext();
75         mDisplayManager = mContext.getSystemService(DisplayManager.class);
76         mPackageManager = mContext.getPackageManager();
77         launchScreenOnActivity();
78         revokePermission(Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS);
79         try (var usage = new PermissionClosable(Manifest.permission.BRIGHTNESS_SLIDER_USAGE)) {
80             recordSliderEvents();
81         }
82     }
83 
84     @Ignore("b/359582534")
85     @Test
testBrightnessSliderTracking()86     public void testBrightnessSliderTracking() throws InterruptedException {
87         // Only run if we have a valid ambient light sensor.
88         assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_LIGHT));
89 
90         // Don't run as there is no app that has permission to access slider usage.
91         assumeTrue(
92                 numberOfSystemAppsWithPermission(Manifest.permission.BRIGHTNESS_SLIDER_USAGE) > 0);
93 
94         assumeTrue(
95                 numberOfSystemAppsWithPermission(Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS)
96                     > 0);
97 
98         try (var brtClosable = new BrightnessClosable()) {
99             var defaultConfig = mDisplayManager.getDefaultBrightnessConfiguration();
100             // This might be null, meaning that the device doesn't support autobrightness
101             assumeNotNull(defaultConfig);
102             setSystemSetting(Settings.System.SCREEN_BRIGHTNESS_MODE,
103                     Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
104             int mode = getSystemSetting(Settings.System.SCREEN_BRIGHTNESS_MODE);
105             assertEquals(Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC, mode);
106             waitForFirstSliderEvent();
107             setDisplayBrightness(brtClosable.getMinimumBrightness());
108 
109             // Update brightness
110             var newEvents = setDisplayBrightness(brtClosable.getMiddleBrightness());
111             assertEquals(1, newEvents.size());
112             BrightnessChangeEvent firstEvent = newEvents.get(0);
113             assertValidLuxData(firstEvent);
114 
115             // Update brightness again
116             newEvents = setDisplayBrightness(brtClosable.getMaximumBrightness());
117             assertEquals(1, newEvents.size());
118             BrightnessChangeEvent secondEvent = newEvents.get(0);
119             assertValidLuxData(secondEvent);
120             assertEquals(secondEvent.lastBrightness, firstEvent.brightness, 1.0f);
121             assertTrue(secondEvent.isUserSetBrightness);
122             assertTrue("failed " + secondEvent.brightness + " not greater than " +
123                     firstEvent.brightness, secondEvent.brightness > firstEvent.brightness);
124         }
125     }
126 
127     @Test
testBrightnesSliderTrackingDecrease()128     public void testBrightnesSliderTrackingDecrease() throws InterruptedException {
129         // Only run if we have a valid ambient light sensor.
130         assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_LIGHT));
131 
132         // Don't run as there is no app that has permission to access slider usage.
133         assumeTrue(
134                 numberOfSystemAppsWithPermission(Manifest.permission.BRIGHTNESS_SLIDER_USAGE) > 0);
135 
136         assumeTrue(
137                 numberOfSystemAppsWithPermission(Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS)
138                     > 0);
139 
140         try (var brtClosable = new BrightnessClosable()) {
141             var defaultConfig = mDisplayManager.getDefaultBrightnessConfiguration();
142             // This might be null, meaning that the device doesn't support autobrightness
143             assumeNotNull(defaultConfig);
144             setSystemSetting(Settings.System.SCREEN_BRIGHTNESS_MODE,
145                     Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
146             int mode = getSystemSetting(Settings.System.SCREEN_BRIGHTNESS_MODE);
147             assertEquals(Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC, mode);
148             waitForFirstSliderEvent();
149             setDisplayBrightness(brtClosable.getMaximumBrightness());
150             var newEvents = setDisplayBrightness(brtClosable.getMiddleBrightness());
151             assertEquals(1, newEvents.size());
152             BrightnessChangeEvent firstEvent = newEvents.get(0);
153             assertValidLuxData(firstEvent);
154             // Update brightness again
155             newEvents = setDisplayBrightness(brtClosable.getMinimumBrightness());
156             assertEquals(1, newEvents.size());
157             BrightnessChangeEvent secondEvent = newEvents.get(0);
158             assertValidLuxData(secondEvent);
159             assertEquals(secondEvent.lastBrightness, firstEvent.brightness, 1.0f);
160             assertTrue(secondEvent.isUserSetBrightness);
161             assertTrue("failed " + secondEvent.brightness + " not less than "
162                     + firstEvent.brightness, secondEvent.brightness < firstEvent.brightness);
163         }
164     }
165 
166     @Test
testNoTrackingForManualBrightness()167     public void testNoTrackingForManualBrightness() throws InterruptedException {
168         // Don't run as there is no app that has permission to access slider usage.
169         assumeTrue(
170                 numberOfSystemAppsWithPermission(Manifest.permission.BRIGHTNESS_SLIDER_USAGE) > 0);
171 
172         try (var brtClosable = new BrightnessClosable()) {
173             setSystemSetting(Settings.System.SCREEN_BRIGHTNESS_MODE,
174                     Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
175             int mode = getSystemSetting(Settings.System.SCREEN_BRIGHTNESS_MODE);
176             assertEquals(Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, mode);
177             recordSliderEvents();
178             var newEvents = setDisplayBrightness(brtClosable.getMinimumBrightness());
179             assertTrue(newEvents.isEmpty());
180             // Then change the brightness
181             newEvents = setDisplayBrightness(brtClosable.getMaximumBrightness());
182             // There shouldn't be any events.
183             assertTrue(newEvents.isEmpty());
184         }
185     }
186 
187     @Test
testNoColorSampleData()188     public void testNoColorSampleData() throws InterruptedException {
189         // Only run if we have a valid ambient light sensor.
190         assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_LIGHT));
191 
192           // Don't run as there is no app that has permission to access slider usage.
193         assumeTrue(
194                 numberOfSystemAppsWithPermission(Manifest.permission.BRIGHTNESS_SLIDER_USAGE) > 0);
195 
196         // Don't run as there is no app that has permission to push curves.
197         assumeTrue(numberOfSystemAppsWithPermission(
198                 Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) > 0);
199 
200         try (var brtClosable = new BrightnessClosable()) {
201             var defaultConfig = mDisplayManager.getDefaultBrightnessConfiguration();
202             // This might be null, meaning that the device doesn't support autobrightness
203             assumeNotNull(defaultConfig);
204             setSystemSetting(Settings.System.SCREEN_BRIGHTNESS_MODE,
205                     Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
206             int mode = getSystemSetting(Settings.System.SCREEN_BRIGHTNESS_MODE);
207             assertEquals(Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC, mode);
208 
209             // Set brightness config to not sample color.
210             BrightnessConfiguration config =
211                     new BrightnessConfiguration.Builder(
212                             new float[]{0.0f, 1000.0f}, new float[]{20.0f, 500.0f})
213                             .setShouldCollectColorSamples(false).build();
214             mDisplayManager.setBrightnessConfiguration(config);
215             waitForFirstSliderEvent();
216             var newEvents = setDisplayBrightness(brtClosable.getMinimumBrightness());
217             // No color samples.
218             assertEquals(0, newEvents.get(0).colorSampleDuration);
219             assertNull(newEvents.get(0).colorValueBuckets);
220             // No test for sampling color as support is optional.
221         }
222     }
223 
224     @Test
testSliderUsagePermission()225     public void testSliderUsagePermission() {
226         assertThrows(SecurityException.class, mDisplayManager::getBrightnessEvents);
227     }
228 
229     @Test
testConfigureBrightnessPermission()230     public void testConfigureBrightnessPermission() {
231         BrightnessConfiguration config =
232             new BrightnessConfiguration.Builder(
233                     new float[]{0.0f, 1000.0f},new float[]{20.0f, 500.0f})
234                 .setDescription("some test").build();
235 
236         assertThrows(SecurityException.class,
237                 () -> mDisplayManager.setBrightnessConfiguration(config));
238     }
239 
240     @Test
testSetGetSimpleCurve()241     public void testSetGetSimpleCurve() {
242         // Only run if we have a valid ambient light sensor.
243         assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_LIGHT));
244 
245         // Don't run as there is no app that has permission to push curves.
246         assumeTrue(numberOfSystemAppsWithPermission(
247                 Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) > 0);
248 
249         try (var brt = new PermissionClosable(Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS)) {
250             var defaultConfig = mDisplayManager.getDefaultBrightnessConfiguration();
251             // This might be null, meaning that the device doesn't support brightness configuration
252             assumeNotNull(defaultConfig);
253 
254             BrightnessConfiguration config =
255                     new BrightnessConfiguration.Builder(
256                             new float[]{0.0f, 1000.0f}, new float[]{20.0f, 500.0f})
257                             .addCorrectionByCategory(ApplicationInfo.CATEGORY_IMAGE,
258                                     createScaleAndTranslateLog(0.80f, 0.2f))
259                             .addCorrectionByPackageName("some.package.name",
260                                     createScaleAndTranslateLog(0.70f, 0.1f))
261                             .setShortTermModelTimeoutMillis(
262                                     defaultConfig.getShortTermModelTimeoutMillis() + 1000L)
263                             .setShortTermModelLowerLuxMultiplier(
264                                     defaultConfig.getShortTermModelLowerLuxMultiplier() + 0.2f)
265                             .setShortTermModelUpperLuxMultiplier(
266                                     defaultConfig.getShortTermModelUpperLuxMultiplier() + 0.3f)
267                             .setDescription("some test").build();
268             mDisplayManager.setBrightnessConfiguration(config);
269             BrightnessConfiguration returnedConfig = mDisplayManager.getBrightnessConfiguration();
270             assertEquals(config, returnedConfig);
271             assertEquals(returnedConfig.getCorrectionByCategory(ApplicationInfo.CATEGORY_IMAGE),
272                     createScaleAndTranslateLog(0.80f, 0.2f));
273             assertEquals(returnedConfig.getCorrectionByPackageName("some.package.name"),
274                     createScaleAndTranslateLog(0.70f, 0.1f));
275             assertNull(returnedConfig.getCorrectionByCategory(ApplicationInfo.CATEGORY_GAME));
276             assertNull(returnedConfig.getCorrectionByPackageName("someother.package.name"));
277             assertEquals(defaultConfig.getShortTermModelTimeoutMillis() + 1000L,
278                     returnedConfig.getShortTermModelTimeoutMillis());
279             assertEquals(defaultConfig.getShortTermModelLowerLuxMultiplier() + 0.2f,
280                     returnedConfig.getShortTermModelLowerLuxMultiplier(), 0.001f);
281             assertEquals(defaultConfig.getShortTermModelUpperLuxMultiplier() + 0.3f,
282                     returnedConfig.getShortTermModelUpperLuxMultiplier(), 0.001f);
283 
284             // After clearing the curve we should get back the default curve.
285             mDisplayManager.setBrightnessConfiguration(null);
286             returnedConfig = mDisplayManager.getBrightnessConfiguration();
287             assertEquals(mDisplayManager.getDefaultBrightnessConfiguration(), returnedConfig);
288         }
289     }
290 
291     @Test
testGetDefaultCurve()292     public void testGetDefaultCurve()  {
293         // Don't run as there is no app that has permission to push curves.
294         assumeTrue(numberOfSystemAppsWithPermission(
295                 Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) > 0);
296 
297         try (var brt = new PermissionClosable(Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS)) {
298             var defaultConfig = mDisplayManager.getDefaultBrightnessConfiguration();
299             assumeNotNull(defaultConfig);
300 
301             Pair<float[], float[]> curve = defaultConfig.getCurve();
302             assertTrue(curve.first.length > 0);
303             assertEquals(curve.first.length, curve.second.length);
304             assertInRange(curve.first, 0, Float.MAX_VALUE);
305             assertInRange(curve.second, 0, Float.MAX_VALUE);
306             assertEquals(0.0, curve.first[0], 0.1);
307             assertMonotonic(curve.first, true /*strictly increasing*/, "lux");
308             assertMonotonic(curve.second, false /*strictly increasing*/, "nits");
309             assertTrue(defaultConfig.getShortTermModelLowerLuxMultiplier() > 0.0f);
310             assertTrue(defaultConfig.getShortTermModelLowerLuxMultiplier() < 10.0f);
311             assertTrue(defaultConfig.getShortTermModelUpperLuxMultiplier() > 0.0f);
312             assertTrue(defaultConfig.getShortTermModelUpperLuxMultiplier() < 10.0f);
313             assertTrue(defaultConfig.getShortTermModelTimeoutMillis() > 0L);
314             assertTrue(defaultConfig.getShortTermModelTimeoutMillis() < 24 * 60 * 60 * 1000L);
315             assertFalse(defaultConfig.shouldCollectColorSamples());
316         }
317     }
318 
319 
320     @Test
321     public void testSliderEventsReflectCurves() throws InterruptedException {
322         // Only run if we have a valid ambient light sensor.
323         assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_LIGHT));
324 
325         // Don't run as there is no app that has permission to access slider usage.
326         assumeTrue(
327                 numberOfSystemAppsWithPermission(Manifest.permission.BRIGHTNESS_SLIDER_USAGE) > 0);
328         // Don't run as there is no app that has permission to push curves.
329         assumeTrue(numberOfSystemAppsWithPermission(
330                 Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) > 0);
331 
332         BrightnessConfiguration config =
333                 new BrightnessConfiguration.Builder(
334                         new float[]{0.0f, 10000.0f},new float[]{15.0f, 400.0f})
335                         .setDescription("model:8").build();
336 
try(var brtClosable = new BrightnessClosable())337         try (var brtClosable = new BrightnessClosable()) {
338             var defaultConfig = mDisplayManager.getDefaultBrightnessConfiguration();
339             // This might be null, meaning that the device doesn't support autobrightness
340             assumeNotNull(defaultConfig);
341             setSystemSetting(Settings.System.SCREEN_BRIGHTNESS_MODE,
342                     Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
343             int mode = getSystemSetting(Settings.System.SCREEN_BRIGHTNESS_MODE);
344             assertEquals(Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC, mode);
345             waitForFirstSliderEvent();
346             setDisplayBrightness(brtClosable.getMinimumBrightness());
347 
348             // Update brightness while we have a custom curve.
349             mDisplayManager.setBrightnessConfiguration(config);
350             var newEvents = setDisplayBrightness(brtClosable.getMiddleBrightness(),
351                     (e) -> !e.isDefaultBrightnessConfig);
352             assertFalse(newEvents.isEmpty());
353             BrightnessChangeEvent firstEvent = newEvents.get(newEvents.size() - 1);
354             assertValidLuxData(firstEvent);
355 
356             // Update brightness again now with default curve.
357             mDisplayManager.setBrightnessConfiguration(null);
358             newEvents = setDisplayBrightness(brtClosable.getMaximumBrightness(),
359                     (e) -> e.isDefaultBrightnessConfig);
360             assertFalse(newEvents.isEmpty());
361             BrightnessChangeEvent secondEvent = newEvents.get(newEvents.size() - 1);
362             assertValidLuxData(secondEvent);
363         }
364     }
365 
366     @Test
testAtMostOneAppHoldsBrightnessConfigurationPermission()367     public void testAtMostOneAppHoldsBrightnessConfigurationPermission() {
368         assertTrue(numberOfSystemAppsWithPermission(
369                     Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) < 2);
370     }
371 
372     @Test
373     public void testSetAndGetBrightnessConfiguration() {
374         assumeTrue(numberOfSystemAppsWithPermission(
375                 Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) > 0);
376 
try(var brightnessAutoClosable = new BrightnessClosable())377         try (var brightnessAutoClosable = new BrightnessClosable()) {
378             BrightnessConfiguration configSet =
379                     new BrightnessConfiguration.Builder(
380                             new float[]{0.0f, 1345.0f}, new float[]{15.0f, 250.0f})
381                             .setDescription("model:8").build();
382             BrightnessConfiguration configGet;
383 
384             mDisplayManager.setBrightnessConfiguration(configSet);
385             configGet = mDisplayManager.getBrightnessConfiguration();
386 
387             assertNotNull(configGet);
388             assertEquals(configSet, configGet);
389         }
390     }
391 
392     @Test
testSetAndGetPerDisplay()393     public void testSetAndGetPerDisplay() throws InterruptedException{
394         // Only run if we have a valid ambient light sensor.
395         assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_LIGHT));
396 
397         assumeTrue(numberOfSystemAppsWithPermission(
398                 Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) > 0);
399 
400         try (var brtClosable = new BrightnessClosable()) {
401             var defaultConfig = mDisplayManager.getDefaultBrightnessConfiguration();
402             // This might be null, meaning that the device doesn't support autobrightness
403             assumeNotNull(defaultConfig);
404             // Setup slider events.
405             setSystemSetting(Settings.System.SCREEN_BRIGHTNESS_MODE,
406                     Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
407             int mode = getSystemSetting(Settings.System.SCREEN_BRIGHTNESS_MODE);
408             assertEquals(Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC, mode);
409             waitForFirstSliderEvent();
410             setDisplayBrightness(brtClosable.getMinimumBrightness());
411 
412             // Get a unique display id via brightness change event
413             var newEvents = setDisplayBrightness(brtClosable.getMiddleBrightness());
414             BrightnessChangeEvent firstEvent = newEvents.get(0);
415             String uniqueDisplayId = firstEvent.uniqueDisplayId;
416             assertNotNull(uniqueDisplayId);
417 
418             // Set & get a configuration for that specific display
419             BrightnessConfiguration configSet =
420                     new BrightnessConfiguration.Builder(
421                             new float[]{0.0f, 12345.0f}, new float[]{15.0f, 200.0f})
422                             .setDescription("test:0").build();
423             mDisplayManager.setBrightnessConfigurationForDisplay(configSet, uniqueDisplayId);
424             BrightnessConfiguration returnedConfig =
425                     mDisplayManager.getBrightnessConfigurationForDisplay(uniqueDisplayId);
426 
427             assertEquals(configSet, returnedConfig);
428 
429             // Set & get a different configuration for that specific display
430             BrightnessConfiguration configSetTwo =
431                     new BrightnessConfiguration.Builder(
432                             new float[]{0.0f, 678.0f}, new float[]{15.0f, 500.0f})
433                             .setDescription("test:1").build();
434             mDisplayManager.setBrightnessConfigurationForDisplay(configSetTwo, uniqueDisplayId);
435             BrightnessConfiguration returnedConfigTwo =
436                     mDisplayManager.getBrightnessConfigurationForDisplay(uniqueDisplayId);
437 
438             assertEquals(configSetTwo, returnedConfigTwo);
439 
440             // Since brightness change event will happen on the default display, this should also
441             // return the same value.
442             BrightnessConfiguration unspecifiedDisplayConfig =
443                     mDisplayManager.getBrightnessConfiguration();
444             assertEquals(configSetTwo, unspecifiedDisplayConfig);
445         }
446     }
447 
assertValidLuxData(BrightnessChangeEvent event)448     private void assertValidLuxData(BrightnessChangeEvent event) {
449         assertNotNull(event.luxTimestamps);
450         assertNotNull(event.luxValues);
451         assertTrue(event.luxTimestamps.length > 0);
452         assertEquals(event.luxValues.length, event.luxTimestamps.length);
453         for (int i = 1; i < event.luxTimestamps.length; ++i) {
454             assertTrue(event.luxTimestamps[i - 1] <= event.luxTimestamps[i]);
455         }
456         for (int i = 0; i < event.luxValues.length; ++i) {
457             assertTrue(event.luxValues[i] >= 0.0f);
458             assertTrue(event.luxValues[i] <= Float.MAX_VALUE);
459             assertFalse(Float.isNaN(event.luxValues[i]));
460         }
461     }
462 
463     /**
464      * Returns the number of system apps with the given permission.
465      */
numberOfSystemAppsWithPermission(String permission)466     private int numberOfSystemAppsWithPermission(String permission) {
467         List<PackageInfo> packages = mContext.getPackageManager().getPackagesHoldingPermissions(
468                 new String[]{permission}, PackageManager.MATCH_SYSTEM_ONLY);
469         packages.removeIf(packageInfo -> packageInfo.packageName.equals("com.android.shell"));
470         return packages.size();
471     }
472 
getNewEvents(int expected)473     private List<BrightnessChangeEvent> getNewEvents(int expected) throws InterruptedException {
474         return getNewEvents(expected, (e) -> true);
475     }
476 
getNewEvents(int expected, Predicate<BrightnessChangeEvent> pred)477     private List<BrightnessChangeEvent> getNewEvents(int expected,
478             Predicate<BrightnessChangeEvent> pred) throws InterruptedException {
479         List<BrightnessChangeEvent> newEvents = new ArrayList<>();
480         for (int i = 0; newEvents.size() < expected && i < 20; ++i) {
481             if (i != 0) {
482                 Thread.sleep(100);
483             }
484             for (BrightnessChangeEvent e : getNewEvents()) {
485                 if (pred.test(e)) {
486                     newEvents.add(e);
487                 }
488             }
489         }
490         return newEvents;
491     }
492 
getNewEvents()493     private List<BrightnessChangeEvent> getNewEvents() {
494         List<BrightnessChangeEvent> newEvents = new ArrayList<>();
495         List<BrightnessChangeEvent> events = mDisplayManager.getBrightnessEvents();
496         for (BrightnessChangeEvent event : events) {
497             if (!mLastReadEvents.containsKey(event.timeStamp)) {
498                 newEvents.add(event);
499             }
500         }
501         mLastReadEvents = new HashMap<>();
502         for (BrightnessChangeEvent event : events) {
503             mLastReadEvents.put(event.timeStamp, event);
504         }
505         return newEvents;
506     }
507 
recordSliderEvents()508     private void recordSliderEvents() {
509         mLastReadEvents = new HashMap<>();
510         List<BrightnessChangeEvent> eventsBefore = mDisplayManager.getBrightnessEvents();
511         for (BrightnessChangeEvent event : eventsBefore) {
512             mLastReadEvents.put(event.timeStamp, event);
513         }
514     }
515 
waitForFirstSliderEvent()516     private void waitForFirstSliderEvent() throws  InterruptedException {
517         recordSliderEvents();
518         // Keep changing brightness until we get an event to handle devices with sensors
519         // that take a while to warm up.
520         int brightness = 25;
521         for (int i = 0; i < 20; ++i) {
522             setSystemSetting(Settings.System.SCREEN_BRIGHTNESS, brightness);
523             brightness = brightness == 25 ? 80 : 25;
524             Thread.sleep(100);
525             if (!getNewEvents().isEmpty()) {
526                 return;
527             }
528         }
529         fail("Failed to fetch first slider event. Is the ambient brightness sensor working?");
530     }
531 
getSystemSetting(String setting)532     private int getSystemSetting(String setting) {
533         return Integer.parseInt(runShellCommand("settings get system " + setting));
534     }
535 
setSystemSetting(String setting, int value)536     private void setSystemSetting(String setting, int value) {
537         runShellCommand("settings put system " + setting + " " + value);
538     }
539 
setDisplayBrightness(float value)540     private List<BrightnessChangeEvent> setDisplayBrightness(float value) {
541         return setDisplayBrightness(value, (e) -> true);
542     }
543 
setDisplayBrightness(float value, Predicate<BrightnessChangeEvent> pred)544     private List<BrightnessChangeEvent> setDisplayBrightness(float value,
545             Predicate<BrightnessChangeEvent> pred) {
546         runShellCommand("cmd display set-brightness " + value);
547         try {
548             return getNewEvents(1, pred);
549         } catch (InterruptedException e) {
550             // If Thread.sleep gets interrupted rethrow as runtime exception to avoid annotation.
551             throw new RuntimeException(e);
552         }
553     }
554 
grantPermission(String permission)555     private void grantPermission(String permission) {
556         InstrumentationRegistry.getInstrumentation().getUiAutomation()
557                 .grantRuntimePermission(mContext.getPackageName(), permission);
558     }
559 
revokePermission(String permission)560     private void revokePermission(String permission) {
561         InstrumentationRegistry.getInstrumentation().getUiAutomation()
562                 .revokeRuntimePermission(mContext.getPackageName(), permission);
563     }
564 
assertInRange(float[] values, float min, float max)565     private static void assertInRange(float[] values, float min, float max) {
566         for (int i = 0; i < values.length; i++) {
567             assertFalse(Float.isNaN(values[i]));
568             assertTrue(values[i] >= min);
569             assertTrue(values[i] <= max);
570         }
571     }
572 
assertMonotonic(float[] values, boolean strictlyIncreasing, String name)573     private static void assertMonotonic(float[] values, boolean strictlyIncreasing, String name) {
574         if (values.length <= 1) {
575             return;
576         }
577         float prev = values[0];
578         for (int i = 1; i < values.length; i++) {
579             if (prev > values[i] || (prev == values[i] && strictlyIncreasing)) {
580                 String condition = strictlyIncreasing ? "strictly increasing" : "monotonic";
581                 fail(name + " values must be " + condition);
582             }
583             prev = values[i];
584         }
585     }
586 
587     private class BrightnessClosable implements AutoCloseable {
588         private final float mPrevBrightness;
589         private final int mPrevBrightnessMode;
590         private final BrightnessConfiguration mPrevBrightnessConfig;
591         private final float mMaxBrightness;
592         private final float mMinBrightness;
593         private final PermissionClosable mBrightnessPermission;
594         private final PermissionClosable mSliderPermission;
595 
BrightnessClosable()596         BrightnessClosable() {
597             mBrightnessPermission = new PermissionClosable(
598                     Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS);
599             mSliderPermission = new PermissionClosable(Manifest.permission.BRIGHTNESS_SLIDER_USAGE);
600             mPrevBrightness = brightnessIntToFloat(getSystemSetting(
601                     Settings.System.SCREEN_BRIGHTNESS));
602             mPrevBrightnessMode = getSystemSetting(Settings.System.SCREEN_BRIGHTNESS_MODE);
603             mPrevBrightnessConfig = mDisplayManager.getBrightnessConfiguration();
604             // Enforce min brightness to get the system absolute min brightness
605             setDisplayBrightness(0f);
606             mMinBrightness = brightnessIntToFloat(getSystemSetting(
607                     Settings.System.SCREEN_BRIGHTNESS));
608             // Enforce max brightness to get the system absolute max brightness
609             setDisplayBrightness(1.0f);
610             mMaxBrightness = brightnessIntToFloat(getSystemSetting(
611                     Settings.System.SCREEN_BRIGHTNESS));
612         }
613 
614         @Override
close()615         public void close() {
616             setDisplayBrightness(mPrevBrightness);
617             setSystemSetting(Settings.System.SCREEN_BRIGHTNESS_MODE, mPrevBrightnessMode);
618             mDisplayManager.setBrightnessConfiguration(mPrevBrightnessConfig);
619             mSliderPermission.close();
620             mBrightnessPermission.close();
621         }
622 
getMinimumBrightness()623         float getMinimumBrightness() {
624             return mMinBrightness;
625         }
626 
getMaximumBrightness()627         float getMaximumBrightness() {
628             return mMaxBrightness;
629         }
630 
getMiddleBrightness()631         float getMiddleBrightness() {
632             return (getMinimumBrightness() + getMaximumBrightness()) / 2f;
633         }
634 
635         /**
636          * Converts between the int brightness system and the float brightness system.
637          */
brightnessIntToFloat(int brightnessInt)638         private static float brightnessIntToFloat(int brightnessInt) {
639             assertThat(brightnessInt).isIn(Range.closed(1, 255));
640             return (float) (brightnessInt - 1) / 254f;
641         }
642     }
643 
644     private class PermissionClosable implements AutoCloseable {
645         private final String mPermission;
646 
PermissionClosable(String permission)647         PermissionClosable(String permission) {
648             mPermission = permission;
649             grantPermission(mPermission);
650         }
651 
652         @Override
close()653         public void close() {
654             revokePermission(mPermission);
655         }
656     }
657 }
658