blob: c6f6fa81db754cdd5b25f5f0a4a4f0222cf4737b [file] [log] [blame]
/*
* Copyright (C) 2019 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;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE;
import static com.android.server.display.DisplayModeDirector.Vote.PRIORITY_FLICKER;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.internal.verification.VerificationModeFactory.times;
import android.annotation.NonNull;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Handler;
import android.os.Looper;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.Preconditions;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.display.DisplayModeDirector.BrightnessObserver;
import com.android.server.display.DisplayModeDirector.DesiredDisplayModeSpecs;
import com.android.server.display.DisplayModeDirector.Vote;
import com.android.server.testutils.FakeDeviceConfigInterface;
import com.google.common.truth.Truth;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DisplayModeDirectorTest {
// The tolerance within which we consider something approximately equals.
private static final String TAG = "DisplayModeDirectorTest";
private static final boolean DEBUG = false;
private static final float FLOAT_TOLERANCE = 0.01f;
private Context mContext;
private FakesInjector mInjector;
private Handler mHandler;
@Rule
public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
when(mContext.getContentResolver()).thenReturn(resolver);
mInjector = new FakesInjector();
mHandler = new Handler(Looper.getMainLooper());
}
private DisplayModeDirector createDirectorFromRefreshRateArray(
float[] refreshRates, int baseModeId) {
DisplayModeDirector director =
new DisplayModeDirector(mContext, mHandler, mInjector);
int displayId = 0;
Display.Mode[] modes = new Display.Mode[refreshRates.length];
for (int i = 0; i < refreshRates.length; i++) {
modes[i] = new Display.Mode(
/*modeId=*/baseModeId + i, /*width=*/1000, /*height=*/1000, refreshRates[i]);
}
SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
supportedModesByDisplay.put(displayId, modes);
director.injectSupportedModesByDisplay(supportedModesByDisplay);
SparseArray<Display.Mode> defaultModesByDisplay = new SparseArray<>();
defaultModesByDisplay.put(displayId, modes[0]);
director.injectDefaultModeByDisplay(defaultModesByDisplay);
return director;
}
private DisplayModeDirector createDirectorFromFpsRange(int minFps, int maxFps) {
int numRefreshRates = maxFps - minFps + 1;
float[] refreshRates = new float[numRefreshRates];
for (int i = 0; i < numRefreshRates; i++) {
refreshRates[i] = minFps + i;
}
return createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/minFps);
}
@Test
public void testDisplayModeVoting() {
int displayId = 0;
// With no votes present, DisplayModeDirector should allow any refresh rate.
DesiredDisplayModeSpecs modeSpecs =
createDirectorFromFpsRange(60, 90).getDesiredDisplayModeSpecs(displayId);
Truth.assertThat(modeSpecs.baseModeId).isEqualTo(60);
Truth.assertThat(modeSpecs.primaryRefreshRateRange.min).isEqualTo(0f);
Truth.assertThat(modeSpecs.primaryRefreshRateRange.max).isEqualTo(Float.POSITIVE_INFINITY);
int numPriorities =
DisplayModeDirector.Vote.MAX_PRIORITY - DisplayModeDirector.Vote.MIN_PRIORITY + 1;
// Ensure vote priority works as expected. As we add new votes with higher priority, they
// should take precedence over lower priority votes.
{
int minFps = 60;
int maxFps = 90;
DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
assertTrue(2 * numPriorities < maxFps - minFps + 1);
SparseArray<Vote> votes = new SparseArray<>();
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
votesByDisplay.put(displayId, votes);
for (int i = 0; i < numPriorities; i++) {
int priority = Vote.MIN_PRIORITY + i;
votes.put(priority, Vote.forRefreshRates(minFps + i, maxFps - i));
director.injectVotesByDisplay(votesByDisplay);
modeSpecs = director.getDesiredDisplayModeSpecs(displayId);
Truth.assertThat(modeSpecs.baseModeId).isEqualTo(minFps + i);
Truth.assertThat(modeSpecs.primaryRefreshRateRange.min)
.isEqualTo((float) (minFps + i));
Truth.assertThat(modeSpecs.primaryRefreshRateRange.max)
.isEqualTo((float) (maxFps - i));
}
}
// Ensure lower priority votes are able to influence the final decision, even in the
// presence of higher priority votes.
{
assertTrue(numPriorities >= 2);
DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
SparseArray<Vote> votes = new SparseArray<>();
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
votesByDisplay.put(displayId, votes);
votes.put(Vote.MAX_PRIORITY, Vote.forRefreshRates(65, 85));
votes.put(Vote.MIN_PRIORITY, Vote.forRefreshRates(70, 80));
director.injectVotesByDisplay(votesByDisplay);
modeSpecs = director.getDesiredDisplayModeSpecs(displayId);
Truth.assertThat(modeSpecs.baseModeId).isEqualTo(70);
Truth.assertThat(modeSpecs.primaryRefreshRateRange.min).isEqualTo(70f);
Truth.assertThat(modeSpecs.primaryRefreshRateRange.max).isEqualTo(80f);
}
}
@Test
public void testVotingWithFloatingPointErrors() {
int displayId = 0;
DisplayModeDirector director = createDirectorFromFpsRange(50, 90);
SparseArray<Vote> votes = new SparseArray<>();
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
votesByDisplay.put(displayId, votes);
float error = FLOAT_TOLERANCE / 4;
votes.put(Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE, Vote.forRefreshRates(0, 60));
votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forRefreshRates(60 + error, 60 + error));
votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE,
Vote.forRefreshRates(60 - error, 60 - error));
director.injectVotesByDisplay(votesByDisplay);
DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
Truth.assertThat(desiredSpecs.baseModeId).isEqualTo(60);
}
@Test
public void testFlickerHasLowerPriorityThanUser() {
assertTrue(PRIORITY_FLICKER < Vote.PRIORITY_APP_REQUEST_REFRESH_RATE);
assertTrue(PRIORITY_FLICKER < Vote.PRIORITY_APP_REQUEST_SIZE);
int displayId = 0;
DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
SparseArray<Vote> votes = new SparseArray<>();
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
votesByDisplay.put(displayId, votes);
votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60, 90));
votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(60, 60));
director.injectVotesByDisplay(votesByDisplay);
DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
votes.clear();
votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60, 90));
votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(90, 90));
director.injectVotesByDisplay(votesByDisplay);
desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90);
Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90);
votes.clear();
votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(90, 90));
votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(60, 60));
director.injectVotesByDisplay(votesByDisplay);
desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90);
Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90);
votes.clear();
votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60, 60));
votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(90, 90));
director.injectVotesByDisplay(votesByDisplay);
desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
}
@Test
public void testAppRequestRefreshRateRange() {
// Confirm that the app request range doesn't include flicker or min refresh rate settings,
// but does include everything else.
assertTrue(
PRIORITY_FLICKER < Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
assertTrue(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE
< Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
assertTrue(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE
>= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
int displayId = 0;
DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
SparseArray<Vote> votes = new SparseArray<>();
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
votesByDisplay.put(displayId, votes);
votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(60, 60));
director.injectVotesByDisplay(votesByDisplay);
DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
Truth.assertThat(desiredSpecs.appRequestRefreshRateRange.min).isAtMost(60f);
Truth.assertThat(desiredSpecs.appRequestRefreshRateRange.max).isAtLeast(90f);
votes.put(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE,
Vote.forRefreshRates(90, Float.POSITIVE_INFINITY));
director.injectVotesByDisplay(votesByDisplay);
desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90);
Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isAtLeast(90f);
Truth.assertThat(desiredSpecs.appRequestRefreshRateRange.min).isAtMost(60f);
Truth.assertThat(desiredSpecs.appRequestRefreshRateRange.max).isAtLeast(90f);
votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(75, 75));
director.injectVotesByDisplay(votesByDisplay);
desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(75);
Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(75);
Truth.assertThat(desiredSpecs.appRequestRefreshRateRange.min)
.isWithin(FLOAT_TOLERANCE)
.of(75);
Truth.assertThat(desiredSpecs.appRequestRefreshRateRange.max)
.isWithin(FLOAT_TOLERANCE)
.of(75);
}
void verifySpecsWithRefreshRateSettings(DisplayModeDirector director, float minFps,
float peakFps, float defaultFps, float primaryMin, float primaryMax,
float appRequestMin, float appRequestMax) {
DesiredDisplayModeSpecs specs = director.getDesiredDisplayModeSpecsWithInjectedFpsSettings(
minFps, peakFps, defaultFps);
Truth.assertThat(specs.primaryRefreshRateRange.min).isEqualTo(primaryMin);
Truth.assertThat(specs.primaryRefreshRateRange.max).isEqualTo(primaryMax);
Truth.assertThat(specs.appRequestRefreshRateRange.min).isEqualTo(appRequestMin);
Truth.assertThat(specs.appRequestRefreshRateRange.max).isEqualTo(appRequestMax);
}
@Test
public void testSpecsFromRefreshRateSettings() {
// Confirm that, with varying settings for min, peak, and default refresh rate,
// DesiredDisplayModeSpecs is calculated correctly.
float[] refreshRates = {30.f, 60.f, 90.f, 120.f, 150.f};
DisplayModeDirector director =
createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/0);
float inf = Float.POSITIVE_INFINITY;
verifySpecsWithRefreshRateSettings(director, 0, 0, 0, 0, inf, 0, inf);
verifySpecsWithRefreshRateSettings(director, 0, 0, 90, 0, 90, 0, inf);
verifySpecsWithRefreshRateSettings(director, 0, 90, 0, 0, 90, 0, 90);
verifySpecsWithRefreshRateSettings(director, 0, 90, 60, 0, 60, 0, 90);
verifySpecsWithRefreshRateSettings(director, 0, 90, 120, 0, 90, 0, 90);
verifySpecsWithRefreshRateSettings(director, 90, 0, 0, 90, inf, 0, inf);
verifySpecsWithRefreshRateSettings(director, 90, 0, 120, 90, 120, 0, inf);
verifySpecsWithRefreshRateSettings(director, 90, 0, 60, 90, inf, 0, inf);
verifySpecsWithRefreshRateSettings(director, 90, 120, 0, 90, 120, 0, 120);
verifySpecsWithRefreshRateSettings(director, 90, 60, 0, 90, 90, 0, 90);
verifySpecsWithRefreshRateSettings(director, 60, 120, 90, 60, 90, 0, 120);
}
void verifyBrightnessObserverCall(DisplayModeDirector director, float minFps, float peakFps,
float defaultFps, float brightnessObserverMin, float brightnessObserverMax) {
BrightnessObserver brightnessObserver = Mockito.mock(BrightnessObserver.class);
director.injectBrightnessObserver(brightnessObserver);
director.getDesiredDisplayModeSpecsWithInjectedFpsSettings(minFps, peakFps, defaultFps);
verify(brightnessObserver)
.onRefreshRateSettingChangedLocked(brightnessObserverMin, brightnessObserverMax);
}
@Test
public void testBrightnessObserverCallWithRefreshRateSettings() {
// Confirm that, with varying settings for min, peak, and default refresh rate, we make the
// correct call to the brightness observer.
float[] refreshRates = {60.f, 90.f, 120.f};
DisplayModeDirector director =
createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/0);
verifyBrightnessObserverCall(director, 0, 0, 0, 0, 0);
verifyBrightnessObserverCall(director, 0, 0, 90, 0, 90);
verifyBrightnessObserverCall(director, 0, 90, 0, 0, 90);
verifyBrightnessObserverCall(director, 0, 90, 60, 0, 60);
verifyBrightnessObserverCall(director, 90, 90, 0, 90, 90);
verifyBrightnessObserverCall(director, 120, 90, 0, 120, 90);
}
@Test
public void testBrightnessObserverGetsUpdatedRefreshRatesForZone() {
DisplayModeDirector director =
createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0);
SensorManager sensorManager = createMockSensorManager(createLightSensor());
final int initialRefreshRate = 60;
mInjector.getDeviceConfig().setRefreshRateInLowZone(initialRefreshRate);
director.start(sensorManager);
assertThat(director.getBrightnessObserver().getRefreshRateInLowZone())
.isEqualTo(initialRefreshRate);
final int updatedRefreshRate = 90;
mInjector.getDeviceConfig().setRefreshRateInLowZone(updatedRefreshRate);
// Need to wait for the property change to propagate to the main thread.
waitForIdleSync();
assertThat(director.getBrightnessObserver().getRefreshRateInLowZone())
.isEqualTo(updatedRefreshRate);
}
@Test
public void testBrightnessObserverThresholdsInZone() {
DisplayModeDirector director =
createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0);
SensorManager sensorManager = createMockSensorManager(createLightSensor());
final int[] initialDisplayThresholds = { 10 };
final int[] initialAmbientThresholds = { 20 };
final FakeDeviceConfig config = mInjector.getDeviceConfig();
config.setLowDisplayBrightnessThresholds(initialDisplayThresholds);
config.setLowAmbientBrightnessThresholds(initialAmbientThresholds);
director.start(sensorManager);
assertThat(director.getBrightnessObserver().getLowDisplayBrightnessThresholds())
.isEqualTo(initialDisplayThresholds);
assertThat(director.getBrightnessObserver().getLowAmbientBrightnessThresholds())
.isEqualTo(initialAmbientThresholds);
final int[] updatedDisplayThresholds = { 9, 14 };
final int[] updatedAmbientThresholds = { -1, 19 };
config.setLowDisplayBrightnessThresholds(updatedDisplayThresholds);
config.setLowAmbientBrightnessThresholds(updatedAmbientThresholds);
// Need to wait for the property change to propagate to the main thread.
waitForIdleSync();
assertThat(director.getBrightnessObserver().getLowDisplayBrightnessThresholds())
.isEqualTo(updatedDisplayThresholds);
assertThat(director.getBrightnessObserver().getLowAmbientBrightnessThresholds())
.isEqualTo(updatedAmbientThresholds);
}
@Test
public void testLockFpsForLowZone() throws Exception {
DisplayModeDirector director =
createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
setPeakRefreshRate(90);
director.getSettingsObserver().setDefaultRefreshRate(90);
director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
final FakeDeviceConfig config = mInjector.getDeviceConfig();
config.setRefreshRateInLowZone(90);
config.setLowDisplayBrightnessThresholds(new int[] { 10 });
config.setLowAmbientBrightnessThresholds(new int[] { 20 });
Sensor lightSensor = createLightSensor();
SensorManager sensorManager = createMockSensorManager(lightSensor);
director.start(sensorManager);
ArgumentCaptor<SensorEventListener> listenerCaptor =
ArgumentCaptor.forClass(SensorEventListener.class);
Mockito.verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
.registerListener(
listenerCaptor.capture(),
eq(lightSensor),
anyInt(),
any(Handler.class));
SensorEventListener listener = listenerCaptor.getValue();
setBrightness(10);
// Sensor reads 20 lux,
listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 20 /*lux*/));
Vote vote = director.getVote(Display.DEFAULT_DISPLAY, PRIORITY_FLICKER);
assertVoteForRefreshRateLocked(vote, 90 /*fps*/);
setBrightness(125);
// Sensor reads 1000 lux,
listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 1000 /*lux*/));
vote = director.getVote(Display.DEFAULT_DISPLAY, PRIORITY_FLICKER);
assertThat(vote).isNull();
}
@Test
public void testLockFpsForHighZone() throws Exception {
DisplayModeDirector director =
createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
setPeakRefreshRate(90 /*fps*/);
director.getSettingsObserver().setDefaultRefreshRate(90);
director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
final FakeDeviceConfig config = mInjector.getDeviceConfig();
config.setRefreshRateInHighZone(60);
config.setHighDisplayBrightnessThresholds(new int[] { 255 });
config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
Sensor lightSensor = createLightSensor();
SensorManager sensorManager = createMockSensorManager(lightSensor);
director.start(sensorManager);
ArgumentCaptor<SensorEventListener> listenerCaptor =
ArgumentCaptor.forClass(SensorEventListener.class);
Mockito.verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
.registerListener(
listenerCaptor.capture(),
eq(lightSensor),
anyInt(),
any(Handler.class));
SensorEventListener listener = listenerCaptor.getValue();
setBrightness(100);
// Sensor reads 2000 lux,
listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 2000));
Vote vote = director.getVote(Display.DEFAULT_DISPLAY, PRIORITY_FLICKER);
assertThat(vote).isNull();
setBrightness(255);
// Sensor reads 9000 lux,
listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 9000));
vote = director.getVote(Display.DEFAULT_DISPLAY, PRIORITY_FLICKER);
assertVoteForRefreshRateLocked(vote, 60 /*fps*/);
}
@Test
public void testSensorRegistration() {
DisplayModeDirector director =
createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
setPeakRefreshRate(90 /*fps*/);
director.getSettingsObserver().setDefaultRefreshRate(90);
director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
Sensor lightSensor = createLightSensor();
SensorManager sensorManager = createMockSensorManager(lightSensor);
director.start(sensorManager);
ArgumentCaptor<SensorEventListener> listenerCaptor =
ArgumentCaptor.forClass(SensorEventListener.class);
Mockito.verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
.registerListener(
listenerCaptor.capture(),
eq(lightSensor),
anyInt(),
any(Handler.class));
// Dispaly state changed from On to Doze
director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_DOZE);
Mockito.verify(sensorManager)
.unregisterListener(listenerCaptor.capture());
// Dispaly state changed from Doze to On
director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
Mockito.verify(sensorManager, times(2))
.registerListener(
listenerCaptor.capture(),
eq(lightSensor),
anyInt(),
any(Handler.class));
}
private void assertVoteForRefreshRateLocked(Vote vote, float refreshRate) {
assertThat(vote).isNotNull();
final DisplayModeDirector.RefreshRateRange expectedRange =
new DisplayModeDirector.RefreshRateRange(refreshRate, refreshRate);
assertThat(vote.refreshRateRange).isEqualTo(expectedRange);
}
private static class FakeDeviceConfig extends FakeDeviceConfigInterface {
@Override
public String getProperty(String namespace, String name) {
Preconditions.checkArgument(DeviceConfig.NAMESPACE_DISPLAY_MANAGER.equals(namespace));
return super.getProperty(namespace, name);
}
@Override
public void addOnPropertiesChangedListener(
String namespace,
Executor executor,
DeviceConfig.OnPropertiesChangedListener listener) {
Preconditions.checkArgument(DeviceConfig.NAMESPACE_DISPLAY_MANAGER.equals(namespace));
super.addOnPropertiesChangedListener(namespace, executor, listener);
}
void setRefreshRateInLowZone(int fps) {
putPropertyAndNotify(
DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_REFRESH_RATE_IN_LOW_ZONE,
String.valueOf(fps));
}
void setLowDisplayBrightnessThresholds(int[] brightnessThresholds) {
String thresholds = toPropertyValue(brightnessThresholds);
if (DEBUG) {
Slog.e(TAG, "Brightness Thresholds = " + thresholds);
}
putPropertyAndNotify(
DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS,
thresholds);
}
void setLowAmbientBrightnessThresholds(int[] ambientThresholds) {
String thresholds = toPropertyValue(ambientThresholds);
if (DEBUG) {
Slog.e(TAG, "Ambient Thresholds = " + thresholds);
}
putPropertyAndNotify(
DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS,
thresholds);
}
void setRefreshRateInHighZone(int fps) {
putPropertyAndNotify(
DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_REFRESH_RATE_IN_HIGH_ZONE,
String.valueOf(fps));
}
void setHighDisplayBrightnessThresholds(int[] brightnessThresholds) {
String thresholds = toPropertyValue(brightnessThresholds);
if (DEBUG) {
Slog.e(TAG, "Brightness Thresholds = " + thresholds);
}
putPropertyAndNotify(
DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS,
thresholds);
}
void setHighAmbientBrightnessThresholds(int[] ambientThresholds) {
String thresholds = toPropertyValue(ambientThresholds);
if (DEBUG) {
Slog.e(TAG, "Ambient Thresholds = " + thresholds);
}
putPropertyAndNotify(
DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS,
thresholds);
}
@NonNull
private static String toPropertyValue(@NonNull int[] intArray) {
return Arrays.stream(intArray)
.mapToObj(Integer::toString)
.collect(Collectors.joining(","));
}
}
private void setBrightness(int brightness) {
Settings.System.putInt(mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS,
brightness);
mInjector.notifyBrightnessChanged();
waitForIdleSync();
}
private void setPeakRefreshRate(float fps) {
Settings.System.putFloat(mContext.getContentResolver(), Settings.System.PEAK_REFRESH_RATE,
fps);
mInjector.notifyPeakRefreshRateChanged();
waitForIdleSync();
}
private static SensorManager createMockSensorManager(Sensor... sensors) {
SensorManager sensorManager = Mockito.mock(SensorManager.class);
when(sensorManager.getSensorList(anyInt())).then((invocation) -> {
List<Sensor> requestedSensors = new ArrayList<>();
int type = invocation.getArgument(0);
for (Sensor sensor : sensors) {
if (sensor.getType() == type || type == Sensor.TYPE_ALL) {
requestedSensors.add(sensor);
}
}
return requestedSensors;
});
when(sensorManager.getDefaultSensor(anyInt())).then((invocation) -> {
int type = invocation.getArgument(0);
for (Sensor sensor : sensors) {
if (sensor.getType() == type) {
return sensor;
}
}
return null;
});
return sensorManager;
}
private static Sensor createLightSensor() {
try {
return TestUtils.createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
} catch (Exception e) {
// There's nothing we can do if this fails, just throw a RuntimeException so that we
// don't have to mark every function that might call this as throwing Exception
throw new RuntimeException("Failed to create a light sensor", e);
}
}
private void waitForIdleSync() {
mHandler.runWithScissors(() -> { }, 500 /*timeout*/);
}
static class FakesInjector implements DisplayModeDirector.Injector {
private final FakeDeviceConfig mDeviceConfig;
private ContentObserver mBrightnessObserver;
private ContentObserver mPeakRefreshRateObserver;
FakesInjector() {
mDeviceConfig = new FakeDeviceConfig();
}
@NonNull
public FakeDeviceConfig getDeviceConfig() {
return mDeviceConfig;
}
@Override
public void registerBrightnessObserver(@NonNull ContentResolver cr,
@NonNull ContentObserver observer) {
if (mBrightnessObserver != null) {
throw new IllegalStateException("Tried to register a second brightness observer");
}
mBrightnessObserver = observer;
}
@Override
public void unregisterBrightnessObserver(@NonNull ContentResolver cr,
@NonNull ContentObserver observer) {
mBrightnessObserver = null;
}
void notifyBrightnessChanged() {
if (mBrightnessObserver != null) {
mBrightnessObserver.dispatchChange(false /*selfChange*/, DISPLAY_BRIGHTNESS_URI);
}
}
@Override
public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
@NonNull ContentObserver observer) {
mPeakRefreshRateObserver = observer;
}
void notifyPeakRefreshRateChanged() {
if (mPeakRefreshRateObserver != null) {
mPeakRefreshRateObserver.dispatchChange(false /*selfChange*/,
PEAK_REFRESH_RATE_URI);
}
}
}
}