Move display mocking tests to DisplayServiceTests
- Move remaining display tests to new module
- Add necessary permissions
Bug: 286043820
Test: atest DisplayServiceTests
Change-Id: I242371f84185b1f770554a5ae0fbab6ed287a86f
(cherry picked from commit eeb677c9d5804dd99bc4eae11c06b170ce8767af)
Merged-In: I242371f84185b1f770554a5ae0fbab6ed287a86f
diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp
index f1ff338..4f555d9 100644
--- a/services/tests/displayservicetests/Android.bp
+++ b/services/tests/displayservicetests/Android.bp
@@ -28,14 +28,15 @@
static_libs: [
"androidx.test.ext.junit",
- "display-core-libs",
"frameworks-base-testutils",
"junit",
"junit-params",
+ "mockingservicestests-utils-mockito",
"platform-compat-test-rules",
"platform-test-annotations",
"services.core",
"servicestests-utils",
+ "testables",
],
defaults: [
@@ -56,10 +57,3 @@
enabled: false,
},
}
-
-java_library {
- name: "display-core-libs",
- srcs: [
- "src/com/android/server/display/TestUtils.java",
- ],
-}
diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml
index d2bd10d..55fde00 100644
--- a/services/tests/displayservicetests/AndroidManifest.xml
+++ b/services/tests/displayservicetests/AndroidManifest.xml
@@ -17,10 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.displayservicetests">
- <!--
- Insert permissions here. eg:
- <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
- -->
+ <!-- Permissions -->
<uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
<uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
<uses-permission android:name="android.permission.DEVICE_POWER" />
@@ -32,6 +29,10 @@
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <!-- Permissions needed for DisplayTransformManagerTest -->
+ <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
+ <uses-permission android:name="android.permission.HARDWARE_TEST"/>
+
<application android:debuggable="true"
android:testOnly="true">
<uses-library android:name="android.test.mock" android:required="true" />
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
new file mode 100644
index 0000000..2fd6e5f
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright 2022 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 org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.isA;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.database.ContentObserver;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+import android.view.Display;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.display.BrightnessSynchronizer;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BrightnessSynchronizerTest {
+ private static final float EPSILON = 0.00001f;
+ private static final Uri BRIGHTNESS_URI =
+ Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
+
+ private Context mContext;
+ private MockContentResolver mContentResolverSpy;
+ private OffsettableClock mClock;
+ private DisplayListener mDisplayListener;
+ private ContentObserver mContentObserver;
+ private TestLooper mTestLooper;
+
+ @Mock private DisplayManager mDisplayManagerMock;
+ @Captor private ArgumentCaptor<DisplayListener> mDisplayListenerCaptor;
+ @Captor private ArgumentCaptor<ContentObserver> mContentObserverCaptor;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+ mContentResolverSpy = spy(new MockContentResolver(mContext));
+ mContentResolverSpy.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ when(mContext.getContentResolver()).thenReturn(mContentResolverSpy);
+ when(mContext.getSystemService(DisplayManager.class)).thenReturn(mDisplayManagerMock);
+ mClock = new OffsettableClock.Stopped();
+ mTestLooper = new TestLooper(mClock::now);
+ }
+
+ @Test
+ public void testSetFloat() throws Exception {
+ putFloatSetting(0.5f);
+ putIntSetting(128);
+ start();
+
+ // Set float brightness to 0.4
+ putFloatSetting(0.4f);
+ advanceTime(10);
+ verifyIntWasSetTo(fToI(0.4f));
+ }
+
+ @Test
+ public void testSetInt() {
+ putFloatSetting(0.5f);
+ putIntSetting(128);
+ start();
+
+ // Set int brightness to 64
+ putIntSetting(64);
+ advanceTime(10);
+ verifyFloatWasSetTo(iToF(64));
+ }
+
+ @Test
+ public void testSetIntQuickSuccession() {
+ putFloatSetting(0.5f);
+ putIntSetting(128);
+ start();
+
+ putIntSetting(50);
+ putIntSetting(40);
+ advanceTime(10);
+
+ verifyFloatWasSetTo(iToF(50));
+
+ // now confirm the first value (via callback) so that we can process the second one.
+ putFloatSetting(iToF(50));
+ advanceTime(10);
+ verifyFloatWasSetTo(iToF(40));
+ }
+
+ @Test
+ public void testSetSameIntValue_nothingUpdated() {
+ putFloatSetting(0.5f);
+ putIntSetting(128);
+ start();
+
+ putIntSetting(128);
+ advanceTime(10);
+ verify(mDisplayManagerMock, times(0)).setBrightness(
+ eq(Display.DEFAULT_DISPLAY), eq(iToF(128)));
+ }
+
+ @Test
+ public void testUpdateDuringResponseIsNotOverwritten() {
+ putFloatSetting(0.5f);
+ putIntSetting(128);
+ start();
+
+ // First, change the float to 0.4f
+ putFloatSetting(0.4f);
+ advanceTime(10);
+
+ // Now set the int to something else (not equal to 0.4f)
+ putIntSetting(20);
+ advanceTime(10);
+
+ // Verify that this update did not get sent to float, because synchronizer
+ // is still waiting for confirmation of its first value.
+ verify(mDisplayManagerMock, times(0)).setBrightness(
+ eq(Display.DEFAULT_DISPLAY), eq(iToF(20)));
+
+ // Send the confirmation of the initial change. This should trigger the new value to
+ // finally be processed and we can verify that the new value (20) is sent.
+ putIntSetting(fToI(0.4f));
+ advanceTime(10);
+ verify(mDisplayManagerMock).setBrightness(
+ eq(Display.DEFAULT_DISPLAY), eq(iToF(20)));
+
+ }
+
+ @Test
+ public void testSetFloat_outOfTimeForResponse() {
+ putFloatSetting(0.5f);
+ putIntSetting(128);
+ start();
+ advanceTime(210);
+
+ // First, change the float to 0.4f
+ putFloatSetting(0.4f);
+ advanceTime(10);
+
+ // Now set the int to something else (not equal to 0.4f)
+ putIntSetting(20);
+
+ // Now, go beyond the timeout so that the last 20 event gets executed.
+ advanceTime(200);
+
+ // Verify that the new value gets sent because the timeout expired.
+ verify(mDisplayManagerMock).setBrightness(
+ eq(Display.DEFAULT_DISPLAY), eq(iToF(20)));
+
+ // Send a confirmation of the initial event, BrightnessSynchronizer should treat this as a
+ // new event because the timeout had already expired
+ putIntSetting(fToI(0.4f));
+ // Because the previous setting will be treated as a new event, we actually want to send
+ // confirmation of the setBrightness() we just verified so that it can be executed as well.
+ putFloatSetting(iToF(20));
+ advanceTime(10);
+
+ // Verify we sent what would have been the confirmation as a new event to displaymanager.
+ // We do both fToI and iToF because the conversions are not symmetric.
+ verify(mDisplayManagerMock).setBrightness(
+ eq(Display.DEFAULT_DISPLAY), eq(iToF(fToI(0.4f))));
+ }
+
+ private BrightnessSynchronizer start() {
+ BrightnessSynchronizer bs = new BrightnessSynchronizer(mContext, mTestLooper.getLooper(),
+ mClock::now);
+ bs.startSynchronizing();
+ verify(mDisplayManagerMock).registerDisplayListener(mDisplayListenerCaptor.capture(),
+ isA(Handler.class), eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+ mDisplayListener = mDisplayListenerCaptor.getValue();
+
+ verify(mContentResolverSpy).registerContentObserver(eq(BRIGHTNESS_URI), eq(false),
+ mContentObserverCaptor.capture(), eq(UserHandle.USER_ALL));
+ mContentObserver = mContentObserverCaptor.getValue();
+ return bs;
+ }
+
+ private int getIntSetting() throws Exception {
+ return Settings.System.getInt(mContentResolverSpy, Settings.System.SCREEN_BRIGHTNESS);
+ }
+
+ private void putIntSetting(int brightness) {
+ Settings.System.putInt(mContentResolverSpy, Settings.System.SCREEN_BRIGHTNESS, brightness);
+ if (mContentObserver != null) {
+ mContentObserver.onChange(false /*=selfChange*/, BRIGHTNESS_URI);
+ }
+ }
+
+ private void putFloatSetting(float brightness) {
+ when(mDisplayManagerMock.getBrightness(eq(Display.DEFAULT_DISPLAY))).thenReturn(brightness);
+ if (mDisplayListener != null) {
+ mDisplayListener.onDisplayChanged(Display.DEFAULT_DISPLAY);
+ }
+ }
+
+ private void verifyIntWasSetTo(int brightness) throws Exception {
+ assertEquals(brightness, getIntSetting());
+ }
+
+ private void verifyFloatWasSetTo(float brightness) {
+ verify(mDisplayManagerMock).setBrightness(eq(Display.DEFAULT_DISPLAY), eq(brightness));
+ }
+
+ private int fToI(float brightness) {
+ return BrightnessSynchronizer.brightnessFloatToInt(brightness);
+ }
+
+ private float iToF(int brightness) {
+ return BrightnessSynchronizer.brightnessIntToFloat(brightness);
+ }
+
+ private void advanceTime(long timeMs) {
+ mClock.fastForward(timeMs);
+ mTestLooper.dispatchAll();
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DensityMappingTest.java b/services/tests/displayservicetests/src/com/android/server/display/DensityMappingTest.java
new file mode 100644
index 0000000..ae7a2a4
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DensityMappingTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2021 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 com.android.server.display.DensityMapping.Entry;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DensityMappingTest {
+
+ @Test
+ public void testConstructor_withBadConfig_throwsException() {
+ assertThrows(IllegalStateException.class, () ->
+ DensityMapping.createByOwning(new Entry[]{
+ new Entry(1080, 1920, 320),
+ new Entry(1080, 1920, 320)})
+ );
+
+ assertThrows(IllegalStateException.class, () ->
+ DensityMapping.createByOwning(new Entry[]{
+ new Entry(1080, 1920, 320),
+ new Entry(1920, 1080, 120)})
+ );
+
+ assertThrows(IllegalStateException.class, () ->
+ DensityMapping.createByOwning(new Entry[]{
+ new Entry(1080, 1920, 320),
+ new Entry(2160, 3840, 120)})
+ );
+
+ assertThrows(IllegalStateException.class, () ->
+ DensityMapping.createByOwning(new Entry[]{
+ new Entry(1080, 1920, 320),
+ new Entry(3840, 2160, 120)})
+ );
+
+ // Two entries with the same diagonal
+ assertThrows(IllegalStateException.class, () ->
+ DensityMapping.createByOwning(new Entry[]{
+ new Entry(500, 500, 123),
+ new Entry(100, 700, 456)})
+ );
+ }
+
+ @Test
+ public void testGetDensityForResolution_withResolutionMatch_returnsDensityFromConfig() {
+ DensityMapping densityMapping = DensityMapping.createByOwning(new Entry[]{
+ new Entry(720, 1280, 213),
+ new Entry(1080, 1920, 320),
+ new Entry(2160, 3840, 640)});
+
+ assertEquals(213, densityMapping.getDensityForResolution(720, 1280));
+ assertEquals(213, densityMapping.getDensityForResolution(1280, 720));
+
+ assertEquals(320, densityMapping.getDensityForResolution(1080, 1920));
+ assertEquals(320, densityMapping.getDensityForResolution(1920, 1080));
+
+ assertEquals(640, densityMapping.getDensityForResolution(2160, 3840));
+ assertEquals(640, densityMapping.getDensityForResolution(3840, 2160));
+ }
+
+ @Test
+ public void testGetDensityForResolution_withDiagonalMatch_returnsDensityFromConfig() {
+ DensityMapping densityMapping = DensityMapping.createByOwning(
+ new Entry[]{ new Entry(500, 500, 123)});
+
+ // 500x500 has the same diagonal as 100x700
+ assertEquals(123, densityMapping.getDensityForResolution(100, 700));
+ }
+
+ @Test
+ public void testGetDensityForResolution_withOneEntry_withNoMatch_returnsExtrapolatedDensity() {
+ DensityMapping densityMapping = DensityMapping.createByOwning(
+ new Entry[]{ new Entry(1080, 1920, 320)});
+
+ assertEquals(320, densityMapping.getDensityForResolution(1081, 1920));
+ assertEquals(320, densityMapping.getDensityForResolution(1080, 1921));
+
+ assertEquals(640, densityMapping.getDensityForResolution(2160, 3840));
+ assertEquals(640, densityMapping.getDensityForResolution(3840, 2160));
+
+ assertEquals(213, densityMapping.getDensityForResolution(720, 1280));
+ assertEquals(213, densityMapping.getDensityForResolution(1280, 720));
+ }
+
+ @Test
+ public void testGetDensityForResolution_withTwoEntries_withNoMatch_returnExtrapolatedDensity() {
+ DensityMapping densityMapping = DensityMapping.createByOwning(new Entry[]{
+ new Entry(1080, 1920, 320),
+ new Entry(2160, 3840, 320)});
+
+ // Resolution is smaller than all entries
+ assertEquals(213, densityMapping.getDensityForResolution(720, 1280));
+ assertEquals(213, densityMapping.getDensityForResolution(1280, 720));
+
+ // Resolution is bigger than all entries
+ assertEquals(320 * 2, densityMapping.getDensityForResolution(2160 * 2, 3840 * 2));
+ assertEquals(320 * 2, densityMapping.getDensityForResolution(3840 * 2, 2160 * 2));
+ }
+
+ @Test
+ public void testGetDensityForResolution_withNoMatch_returnsInterpolatedDensity() {
+ {
+ DensityMapping densityMapping = DensityMapping.createByOwning(new Entry[]{
+ new Entry(1080, 1920, 320),
+ new Entry(2160, 3840, 320)});
+
+ assertEquals(320, densityMapping.getDensityForResolution(2000, 2000));
+ }
+
+ {
+ DensityMapping densityMapping = DensityMapping.createByOwning(new Entry[]{
+ new Entry(720, 1280, 213),
+ new Entry(2160, 3840, 640)});
+
+ assertEquals(320, densityMapping.getDensityForResolution(1080, 1920));
+ assertEquals(320, densityMapping.getDensityForResolution(1920, 1080));
+ }
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
new file mode 100644
index 0000000..95c62ae
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 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 org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DisplayBrightnessStateTest {
+ private static final float FLOAT_DELTA = 0.001f;
+
+ private DisplayBrightnessState.Builder mDisplayBrightnessStateBuilder;
+
+ @Before
+ public void before() {
+ mDisplayBrightnessStateBuilder = new DisplayBrightnessState.Builder();
+ }
+
+ @Test
+ public void validateAllDisplayBrightnessStateFieldsAreSetAsExpected() {
+ float brightness = 0.3f;
+ float sdrBrightness = 0.2f;
+ boolean shouldUseAutoBrightness = true;
+ BrightnessReason brightnessReason = new BrightnessReason();
+ brightnessReason.setReason(BrightnessReason.REASON_AUTOMATIC);
+ brightnessReason.setModifier(BrightnessReason.MODIFIER_DIMMED);
+ DisplayBrightnessState displayBrightnessState = mDisplayBrightnessStateBuilder
+ .setBrightness(brightness)
+ .setSdrBrightness(sdrBrightness)
+ .setBrightnessReason(brightnessReason)
+ .setShouldUseAutoBrightness(shouldUseAutoBrightness)
+ .build();
+
+ assertEquals(displayBrightnessState.getBrightness(), brightness, FLOAT_DELTA);
+ assertEquals(displayBrightnessState.getSdrBrightness(), sdrBrightness, FLOAT_DELTA);
+ assertEquals(displayBrightnessState.getBrightnessReason(), brightnessReason);
+ assertEquals(displayBrightnessState.getShouldUseAutoBrightness(), shouldUseAutoBrightness);
+ assertEquals(displayBrightnessState.toString(), getString(displayBrightnessState));
+ }
+
+ @Test
+ public void testFrom() {
+ BrightnessReason reason = new BrightnessReason();
+ reason.setReason(BrightnessReason.REASON_MANUAL);
+ reason.setModifier(BrightnessReason.MODIFIER_DIMMED);
+ DisplayBrightnessState state1 = new DisplayBrightnessState.Builder()
+ .setBrightnessReason(reason)
+ .setBrightness(0.26f)
+ .setSdrBrightness(0.23f)
+ .setShouldUseAutoBrightness(false)
+ .build();
+ DisplayBrightnessState state2 = DisplayBrightnessState.Builder.from(state1).build();
+ assertEquals(state1, state2);
+ }
+
+ private String getString(DisplayBrightnessState displayBrightnessState) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("DisplayBrightnessState:")
+ .append("\n brightness:")
+ .append(displayBrightnessState.getBrightness())
+ .append("\n sdrBrightness:")
+ .append(displayBrightnessState.getSdrBrightness())
+ .append("\n brightnessReason:")
+ .append(displayBrightnessState.getBrightnessReason())
+ .append("\n shouldUseAutoBrightness:")
+ .append(displayBrightnessState.getShouldUseAutoBrightness());
+ return sb.toString();
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
new file mode 100644
index 0000000..d54eca2
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -0,0 +1,1401 @@
+/*
+ * Copyright (C) 2022 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 com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.SystemProperties;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.testing.TestableContext;
+import android.util.FloatProperty;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+import com.android.server.LocalServices;
+import com.android.server.am.BatteryStatsService;
+import com.android.server.display.RampAnimator.DualRampAnimator;
+import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.color.ColorDisplayService;
+import com.android.server.display.layout.Layout;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+
+import java.util.List;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayPowerController2Test {
+ private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
+ private static final String UNIQUE_ID = "unique_id_test123";
+ private static final int FOLLOWER_DISPLAY_ID = DISPLAY_ID + 1;
+ private static final String FOLLOWER_UNIQUE_ID = "unique_id_456";
+ private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
+ private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
+ private static final float PROX_SENSOR_MAX_RANGE = 5;
+ private static final float BRIGHTNESS_RAMP_RATE_FAST_DECREASE = 0.3f;
+ private static final float BRIGHTNESS_RAMP_RATE_FAST_INCREASE = 0.4f;
+ private static final float BRIGHTNESS_RAMP_RATE_SLOW_DECREASE = 0.1f;
+ private static final float BRIGHTNESS_RAMP_RATE_SLOW_INCREASE = 0.2f;
+
+ private OffsettableClock mClock;
+ private TestLooper mTestLooper;
+ private Handler mHandler;
+ private DisplayPowerControllerHolder mHolder;
+ private Sensor mProxSensor;
+
+ @Mock
+ private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
+ @Mock
+ private SensorManager mSensorManagerMock;
+ @Mock
+ private DisplayBlanker mDisplayBlankerMock;
+ @Mock
+ private BrightnessTracker mBrightnessTrackerMock;
+ @Mock
+ private WindowManagerPolicy mWindowManagerPolicyMock;
+ @Mock
+ private PowerManager mPowerManagerMock;
+ @Mock
+ private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
+ @Mock
+ private DisplayWhiteBalanceController mDisplayWhiteBalanceControllerMock;
+ @Captor
+ private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
+
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getContext());
+
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule =
+ new ExtendedMockitoRule.Builder(this)
+ .setStrictness(Strictness.LENIENT)
+ .spyStatic(SystemProperties.class)
+ .spyStatic(BatteryStatsService.class)
+ .build();
+
+ @Before
+ public void setUp() throws Exception {
+ mClock = new OffsettableClock.Stopped();
+ mTestLooper = new TestLooper(mClock::now);
+ mHandler = new Handler(mTestLooper.getLooper());
+
+ // Put the system into manual brightness by default, just to minimize unexpected events and
+ // have a consistent starting state
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+
+ addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
+ addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class,
+ mCdsiMock);
+
+ mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
+
+ doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
+ SystemProperties.set(anyString(), any()));
+ doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
+
+ setUpSensors();
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ }
+
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(WindowManagerPolicy.class);
+ LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
+ }
+
+ @Test
+ public void testReleaseProxSuspendBlockersOnExit() throws Exception {
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ // Send a display power request
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ dpr.useProximitySensor = true;
+ mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+ // Run updatePowerState to start listener for the prox sensor
+ advanceTime(1);
+
+ SensorEventListener listener = getSensorEventListener(mProxSensor);
+ assertNotNull(listener);
+
+ listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 5));
+ advanceTime(1);
+
+ // two times, one for unfinished business and one for proximity
+ verify(mHolder.wakelockController, times(2)).acquireWakelock(
+ WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
+ verify(mHolder.wakelockController).acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
+
+ mHolder.dpc.stop();
+ advanceTime(1);
+ // two times, one for unfinished business and one for proximity
+ verify(mHolder.wakelockController, times(2)).acquireWakelock(
+ WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
+ verify(mHolder.wakelockController).acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
+ }
+
+ @Test
+ public void testScreenOffBecauseOfProximity() throws Exception {
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ // Send a display power request
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ dpr.useProximitySensor = true;
+ mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+ // Run updatePowerState to start listener for the prox sensor
+ advanceTime(1);
+
+ SensorEventListener listener = getSensorEventListener(mProxSensor);
+ assertNotNull(listener);
+
+ // Send a positive proximity event
+ listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
+ advanceTime(1);
+
+ // The display should have been turned off
+ verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
+
+ clearInvocations(mHolder.displayPowerState);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+ // Send a negative proximity event
+ listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor,
+ (int) PROX_SENSOR_MAX_RANGE + 1));
+ // Advance time by less than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
+ advanceTime(1);
+
+ // The prox sensor is debounced so the display should not have been turned back on yet
+ verify(mHolder.displayPowerState, never()).setScreenState(Display.STATE_ON);
+
+ // Advance time by more than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
+ advanceTime(1000);
+
+ // The display should have been turned back on
+ verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
+ }
+
+ @Test
+ public void testScreenOffBecauseOfProximity_ProxSensorGone() throws Exception {
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ // Send a display power request
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ dpr.useProximitySensor = true;
+ mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+ // Run updatePowerState to start listener for the prox sensor
+ advanceTime(1);
+
+ SensorEventListener listener = getSensorEventListener(mProxSensor);
+ assertNotNull(listener);
+
+ // Send a positive proximity event
+ listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
+ advanceTime(1);
+
+ // The display should have been turned off
+ verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
+
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+ // The display device changes and we no longer have a prox sensor
+ reset(mSensorManagerMock);
+ setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+ mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+ mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+
+ advanceTime(1); // Run updatePowerState
+
+ // The display should have been turned back on and the listener should have been
+ // unregistered
+ verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
+ verify(mSensorManagerMock).unregisterListener(listener);
+ }
+
+ @Test
+ public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() {
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ // send a display power request
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ dpr.useProximitySensor = true;
+ final DisplayPowerControllerHolder followerDpc =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+ followerDpc.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+ // Run updatePowerState
+ advanceTime(1);
+
+ verify(mSensorManagerMock, never()).registerListener(any(SensorEventListener.class),
+ eq(mProxSensor), anyInt(), any(Handler.class));
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowers_BothDpcsSupportNits() {
+ DisplayPowerControllerHolder followerDpc =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+ // Test different float scale values
+ float leadBrightness = 0.3f;
+ float followerBrightness = 0.4f;
+ float nits = 300;
+ when(mHolder.automaticBrightnessController.convertToNits(leadBrightness)).thenReturn(nits);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(followerBrightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(leadBrightness);
+ listener.onBrightnessChanged(leadBrightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(), anyFloat());
+ verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
+ anyFloat());
+
+ clearInvocations(mHolder.animator, followerDpc.animator);
+
+ // Test the same float scale value
+ float brightness = 0.6f;
+ nits = 600;
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() {
+ DisplayPowerControllerHolder followerDpc =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+ float brightness = 0.3f;
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(300f);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
+ .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() {
+ DisplayPowerControllerHolder followerDpc =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+ float brightness = 0.3f;
+ when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() {
+ DisplayPowerControllerHolder followerDpc =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+ float brightness = 0.3f;
+ when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
+ .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowers_AutomaticBrightness() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ DisplayPowerControllerHolder followerDpc =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ float leadBrightness = 0.1f;
+ float rawLeadBrightness = 0.3f;
+ float followerBrightness = 0.4f;
+ float nits = 300;
+ float ambientLux = 3000;
+ when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness())
+ .thenReturn(rawLeadBrightness);
+ when(mHolder.automaticBrightnessController
+ .getAutomaticScreenBrightness(any(BrightnessEvent.class)))
+ .thenReturn(leadBrightness);
+ when(mHolder.automaticBrightnessController.convertToNits(rawLeadBrightness))
+ .thenReturn(nits);
+ when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(followerBrightness);
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ // One triggered by handleBrightnessModeChange, another triggered by setBrightnessToFollow
+ verify(followerDpc.hbmController, times(2)).onAmbientLuxChange(ambientLux);
+ verify(followerDpc.animator, times(2)).animateTo(eq(followerBrightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(leadBrightness);
+ when(followerDpc.displayPowerState.getScreenBrightness()).thenReturn(followerBrightness);
+ clearInvocations(mHolder.animator, followerDpc.animator);
+
+ leadBrightness = 0.05f;
+ rawLeadBrightness = 0.2f;
+ followerBrightness = 0.3f;
+ nits = 200;
+ ambientLux = 2000;
+ when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness())
+ .thenReturn(rawLeadBrightness);
+ when(mHolder.automaticBrightnessController
+ .getAutomaticScreenBrightness(any(BrightnessEvent.class)))
+ .thenReturn(leadBrightness);
+ when(mHolder.automaticBrightnessController.convertToNits(rawLeadBrightness))
+ .thenReturn(nits);
+ when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(followerBrightness);
+
+ mHolder.dpc.updateBrightness();
+ advanceTime(1); // Run updatePowerState
+
+ // The second time, the animation rate should be slow
+ verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE));
+ verify(followerDpc.hbmController).onAmbientLuxChange(ambientLux);
+ verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE));
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowersRemoval_RemoveSingleFollower() {
+ DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID,
+ FOLLOWER_UNIQUE_ID);
+ DisplayPowerControllerHolder secondFollowerDpc = createDisplayPowerController(
+ SECOND_FOLLOWER_DISPLAY_ID, SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(secondFollowerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ secondFollowerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ // Set the initial brightness on the DPC we're going to remove so we have a fixed value for
+ // it to return to.
+ listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(followerDpc.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
+ final float initialFollowerBrightness = 0.3f;
+ when(followerDpc.brightnessSetting.getBrightness()).thenReturn(initialFollowerBrightness);
+ followerListener.onBrightnessChanged(initialFollowerBrightness);
+ advanceTime(1);
+ verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+ when(followerDpc.displayPowerState.getScreenBrightness())
+ .thenReturn(initialFollowerBrightness);
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+ mHolder.dpc.addDisplayBrightnessFollower(secondFollowerDpc.dpc);
+ clearInvocations(followerDpc.animator);
+
+ // Validate both followers are correctly registered and receiving brightness updates
+ float brightness = 0.6f;
+ float nits = 600;
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(secondFollowerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+ when(followerDpc.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+ when(secondFollowerDpc.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+ clearInvocations(mHolder.animator, followerDpc.animator, secondFollowerDpc.animator);
+
+ // Remove the first follower and validate it goes back to its original brightness.
+ mHolder.dpc.removeDisplayBrightnessFollower(followerDpc.dpc);
+ advanceTime(1);
+ verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE));
+
+ when(followerDpc.displayPowerState.getScreenBrightness())
+ .thenReturn(initialFollowerBrightness);
+ clearInvocations(followerDpc.animator);
+
+ // Change the brightness of the lead display and validate only the second follower responds
+ brightness = 0.7f;
+ nits = 700;
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(secondFollowerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ verify(followerDpc.animator, never()).animateTo(anyFloat(), anyFloat(), anyFloat());
+ verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowersRemoval_RemoveAllFollowers() {
+ DisplayPowerControllerHolder followerHolder =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+ DisplayPowerControllerHolder secondFollowerHolder =
+ createDisplayPowerController(SECOND_FOLLOWER_DISPLAY_ID,
+ SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(followerHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(secondFollowerHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ secondFollowerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ // Set the initial brightness on the DPCs we're going to remove so we have a fixed value for
+ // it to return to.
+ listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(followerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
+ listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(secondFollowerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener secondFollowerListener =
+ listenerCaptor.getValue();
+ final float initialFollowerBrightness = 0.3f;
+ when(followerHolder.brightnessSetting.getBrightness()).thenReturn(
+ initialFollowerBrightness);
+ when(secondFollowerHolder.brightnessSetting.getBrightness()).thenReturn(
+ initialFollowerBrightness);
+ followerListener.onBrightnessChanged(initialFollowerBrightness);
+ secondFollowerListener.onBrightnessChanged(initialFollowerBrightness);
+ advanceTime(1);
+ verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+ when(followerHolder.displayPowerState.getScreenBrightness())
+ .thenReturn(initialFollowerBrightness);
+ when(secondFollowerHolder.displayPowerState.getScreenBrightness())
+ .thenReturn(initialFollowerBrightness);
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerHolder.dpc);
+ mHolder.dpc.addDisplayBrightnessFollower(secondFollowerHolder.dpc);
+ clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
+
+ // Validate both followers are correctly registered and receiving brightness updates
+ float brightness = 0.6f;
+ float nits = 600;
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+ when(followerHolder.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(secondFollowerHolder.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ verify(followerHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+ when(followerHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+ when(secondFollowerHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+ clearInvocations(mHolder.animator, followerHolder.animator, secondFollowerHolder.animator);
+
+ // Stop the lead DPC and validate that the followers go back to their original brightness.
+ mHolder.dpc.stop();
+ advanceTime(1);
+ verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE));
+ verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE));
+ clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
+ }
+
+ @Test
+ public void testDoesNotSetScreenStateForNonDefaultDisplayUntilBootCompleted() {
+ // We should still set screen state for the default display
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+ verify(mHolder.displayPowerState, times(2)).setScreenState(anyInt());
+
+ mHolder = createDisplayPowerController(42, UNIQUE_ID);
+
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+ verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
+
+ mHolder.dpc.onBootCompleted();
+ advanceTime(1); // Run updatePowerState
+ verify(mHolder.displayPowerState).setScreenState(anyInt());
+ }
+
+ @Test
+ public void testSetScreenOffBrightnessSensorEnabled_DisplayIsOff() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_OFF;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+ .setLightSensorEnabled(true);
+
+ // The display turns on and we use the brightness value recommended by
+ // ScreenOffBrightnessSensorController
+ clearInvocations(mHolder.screenOffBrightnessSensorController);
+ float brightness = 0.14f;
+ when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
+ .thenReturn(brightness);
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+ any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+ .getAutomaticScreenBrightness();
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ }
+
+ @Test
+ public void testSetScreenOffBrightnessSensorEnabled_DisplayIsInDoze() {
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+ .setLightSensorEnabled(true);
+
+ // The display turns on and we use the brightness value recommended by
+ // ScreenOffBrightnessSensorController
+ clearInvocations(mHolder.screenOffBrightnessSensorController);
+ float brightness = 0.14f;
+ when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
+ .thenReturn(brightness);
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+ any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+ .getAutomaticScreenBrightness();
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ }
+
+ @Test
+ public void testSetScreenOffBrightnessSensorDisabled_AutoBrightnessIsDisabled() {
+ // Tests are set up with manual brightness by default, so no need to set it here.
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_OFF;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+ .setLightSensorEnabled(false);
+ }
+
+ @Test
+ public void testSetScreenOffBrightnessSensorDisabled_DisplayIsDisabled() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+ .setLightSensorEnabled(false);
+ }
+
+ @Test
+ public void testSetScreenOffBrightnessSensorDisabled_DisplayIsOn() {
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+ .setLightSensorEnabled(false);
+ }
+
+ @Test
+ public void testSetScreenOffBrightnessSensorDisabled_DisplayIsAFollower() {
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_OFF;
+
+ mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, /* leadDisplayId= */ 42);
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+ .setLightSensorEnabled(false);
+ }
+
+ @Test
+ public void testStopScreenOffBrightnessSensorControllerWhenDisplayDeviceChanges() {
+ // New display device
+ setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+ mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+
+ mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.screenOffBrightnessSensorController).stop();
+ }
+
+ @Test
+ public void testAutoBrightnessEnabled_DisplayIsOn() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.automaticBrightnessController).configure(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+ /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ /* userChangedBrightness= */ false, /* adjustment= */ 0,
+ /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ false
+ );
+ verify(mHolder.hbmController)
+ .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
+ }
+
+ @Test
+ public void testAutoBrightnessEnabled_DisplayIsInDoze() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.automaticBrightnessController).configure(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+ /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ /* userChangedBrightness= */ false, /* adjustment= */ 0,
+ /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_DOZE,
+ /* shouldResetShortTermModel= */ false
+ );
+ verify(mHolder.hbmController)
+ .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
+ }
+
+ @Test
+ public void testAutoBrightnessDisabled_ManualBrightnessMode() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ // One triggered by the test, the other by handleBrightnessModeChange
+ verify(mHolder.automaticBrightnessController, times(2)).configure(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
+ /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ /* userChangedBrightness= */ false, /* adjustment= */ 0,
+ /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ false
+ );
+ verify(mHolder.hbmController, times(2))
+ .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
+ }
+
+ @Test
+ public void testAutoBrightnessDisabled_DisplayIsOff() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_OFF;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.automaticBrightnessController).configure(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+ /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ /* userChangedBrightness= */ false, /* adjustment= */ 0,
+ /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_OFF,
+ /* shouldResetShortTermModel= */ false
+ );
+ verify(mHolder.hbmController).setAutoBrightnessEnabled(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
+ }
+
+ @Test
+ public void testAutoBrightnessDisabled_DisplayIsInDoze() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.automaticBrightnessController).configure(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+ /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ /* userChangedBrightness= */ false, /* adjustment= */ 0,
+ /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_DOZE,
+ /* shouldResetShortTermModel= */ false
+ );
+ verify(mHolder.hbmController).setAutoBrightnessEnabled(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
+ }
+
+ @Test
+ public void testAutoBrightnessDisabled_FollowerDisplay() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ mHolder.dpc.setBrightnessToFollow(0.3f, -1, 0, /* slowChange= */ false);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ // One triggered by the test, the other by handleBrightnessModeChange
+ verify(mHolder.automaticBrightnessController, times(2)).configure(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
+ /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ /* userChangedBrightness= */ false, /* adjustment= */ 0,
+ /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ false
+ );
+
+ // HBM should be allowed for the follower display
+ verify(mHolder.hbmController)
+ .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
+ }
+
+ @Test
+ public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
+ float brightness = 0.3f;
+ float nits = 500;
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay,
+ true);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+
+ mHolder.dpc.setBrightness(brightness);
+ verify(mHolder.brightnessSetting).setBrightnessNitsForDefaultDisplay(nits);
+
+ float newBrightness = 0.4f;
+ when(mHolder.brightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
+ when(mHolder.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(newBrightness);
+ // New display device
+ setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+ mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+ mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+ // One triggered by handleBrightnessModeChange, another triggered by onDisplayChanged
+ verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat());
+ }
+
+ @Test
+ public void testShortTermModelPersistsWhenDisplayDeviceChanges() {
+ float lux = 2000;
+ float brightness = 0.4f;
+ float nits = 500;
+ when(mHolder.brightnessMappingStrategy.getUserLux()).thenReturn(lux);
+ when(mHolder.brightnessMappingStrategy.getUserBrightness()).thenReturn(brightness);
+ when(mHolder.brightnessMappingStrategy.convertToNits(brightness)).thenReturn(nits);
+ when(mHolder.brightnessMappingStrategy.convertToFloatScale(nits)).thenReturn(brightness);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1);
+ clearInvocations(mHolder.injector);
+
+ // New display device
+ setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+ mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+ mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+ advanceTime(1);
+
+ verify(mHolder.injector).getAutomaticBrightnessController(
+ any(AutomaticBrightnessController.Callbacks.class),
+ any(Looper.class),
+ eq(mSensorManagerMock),
+ any(),
+ eq(mHolder.brightnessMappingStrategy),
+ anyInt(),
+ anyFloat(),
+ anyFloat(),
+ anyFloat(),
+ anyInt(),
+ anyInt(),
+ anyLong(),
+ anyLong(),
+ anyBoolean(),
+ any(HysteresisLevels.class),
+ any(HysteresisLevels.class),
+ any(HysteresisLevels.class),
+ any(HysteresisLevels.class),
+ eq(mContext),
+ any(BrightnessRangeController.class),
+ any(BrightnessThrottler.class),
+ isNull(),
+ anyInt(),
+ anyInt(),
+ eq(lux),
+ eq(brightness)
+ );
+ }
+
+ @Test
+ public void testUpdateBrightnessThrottlingDataId() {
+ mHolder.display.getDisplayInfoLocked().thermalBrightnessThrottlingDataId =
+ "throttling-data-id";
+ clearInvocations(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig());
+
+ mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig())
+ .getThermalBrightnessThrottlingDataMapByThrottlingId();
+ }
+
+ @Test
+ public void testDwbcCallsHappenOnHandler() {
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+ mHolder.dpc.setAutomaticScreenBrightnessMode(true);
+ verify(mDisplayWhiteBalanceControllerMock, never()).setStrongModeEnabled(true);
+
+ // dispatch handler looper
+ advanceTime(1);
+ verify(mDisplayWhiteBalanceControllerMock, times(1)).setStrongModeEnabled(true);
+ }
+
+ /**
+ * Creates a mock and registers it to {@link LocalServices}.
+ */
+ private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+ LocalServices.removeServiceForTest(clazz);
+ LocalServices.addService(clazz, mock);
+ }
+
+ private void advanceTime(long timeMs) {
+ mClock.fastForward(timeMs);
+ mTestLooper.dispatchAll();
+ }
+
+ private void setUpSensors() throws Exception {
+ mProxSensor = TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY,
+ PROX_SENSOR_MAX_RANGE);
+ Sensor screenOffBrightnessSensor = TestUtils.createSensor(
+ Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+ when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL)))
+ .thenReturn(List.of(mProxSensor, screenOffBrightnessSensor));
+ }
+
+ private SensorEventListener getSensorEventListener(Sensor sensor) {
+ verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(),
+ eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class));
+ return mSensorEventListenerCaptor.getValue();
+ }
+
+ private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock,
+ DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock,
+ boolean isEnabled) {
+ DisplayInfo info = new DisplayInfo();
+ DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
+ deviceInfo.uniqueId = uniqueId;
+
+ when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
+ when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock);
+ when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
+ when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled);
+ when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
+ when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
+ when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
+ when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
+ when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData() {
+ {
+ type = Sensor.STRING_TYPE_PROXIMITY;
+ name = null;
+ }
+ });
+ when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
+ when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
+ when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData());
+ when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData() {
+ {
+ type = Sensor.STRING_TYPE_LIGHT;
+ name = null;
+ }
+ });
+ when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
+ .thenReturn(new int[0]);
+ when(displayDeviceConfigMock.getBrightnessRampFastDecrease())
+ .thenReturn(BRIGHTNESS_RAMP_RATE_FAST_DECREASE);
+ when(displayDeviceConfigMock.getBrightnessRampFastIncrease())
+ .thenReturn(BRIGHTNESS_RAMP_RATE_FAST_INCREASE);
+ when(displayDeviceConfigMock.getBrightnessRampSlowDecrease())
+ .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE);
+ when(displayDeviceConfigMock.getBrightnessRampSlowIncrease())
+ .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE);
+ }
+
+ private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
+ String uniqueId) {
+ return createDisplayPowerController(displayId, uniqueId, /* isEnabled= */ true);
+ }
+
+ private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
+ String uniqueId, boolean isEnabled) {
+ final DisplayPowerState displayPowerState = mock(DisplayPowerState.class);
+ final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class);
+ final AutomaticBrightnessController automaticBrightnessController =
+ mock(AutomaticBrightnessController.class);
+ final WakelockController wakelockController = mock(WakelockController.class);
+ final BrightnessMappingStrategy brightnessMappingStrategy =
+ mock(BrightnessMappingStrategy.class);
+ final HysteresisLevels hysteresisLevels = mock(HysteresisLevels.class);
+ final ScreenOffBrightnessSensorController screenOffBrightnessSensorController =
+ mock(ScreenOffBrightnessSensorController.class);
+ final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
+
+ when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
+
+ TestInjector injector = spy(new TestInjector(displayPowerState, animator,
+ automaticBrightnessController, wakelockController, brightnessMappingStrategy,
+ hysteresisLevels, screenOffBrightnessSensorController, hbmController));
+
+ final LogicalDisplay display = mock(LogicalDisplay.class);
+ final DisplayDevice device = mock(DisplayDevice.class);
+ final HighBrightnessModeMetadata hbmMetadata = mock(HighBrightnessModeMetadata.class);
+ final BrightnessSetting brightnessSetting = mock(BrightnessSetting.class);
+ final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class);
+
+ setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
+
+ final DisplayPowerController2 dpc = new DisplayPowerController2(
+ mContext, injector, mDisplayPowerCallbacksMock, mHandler,
+ mSensorManagerMock, mDisplayBlankerMock, display,
+ mBrightnessTrackerMock, brightnessSetting, () -> {},
+ hbmMetadata, /* bootCompleted= */ false);
+
+ return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
+ animator, automaticBrightnessController, wakelockController,
+ screenOffBrightnessSensorController, hbmController, hbmMetadata,
+ brightnessMappingStrategy, injector);
+ }
+
+ /**
+ * A class for holding a DisplayPowerController under test and all the mocks specifically
+ * related to it.
+ */
+ private static class DisplayPowerControllerHolder {
+ public final DisplayPowerController2 dpc;
+ public final LogicalDisplay display;
+ public final DisplayPowerState displayPowerState;
+ public final BrightnessSetting brightnessSetting;
+ public final DualRampAnimator<DisplayPowerState> animator;
+ public final AutomaticBrightnessController automaticBrightnessController;
+ public final WakelockController wakelockController;
+ public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController;
+ public final HighBrightnessModeController hbmController;
+ public final HighBrightnessModeMetadata hbmMetadata;
+ public final BrightnessMappingStrategy brightnessMappingStrategy;
+ public final DisplayPowerController2.Injector injector;
+
+ DisplayPowerControllerHolder(DisplayPowerController2 dpc, LogicalDisplay display,
+ DisplayPowerState displayPowerState, BrightnessSetting brightnessSetting,
+ DualRampAnimator<DisplayPowerState> animator,
+ AutomaticBrightnessController automaticBrightnessController,
+ WakelockController wakelockController,
+ ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
+ HighBrightnessModeController hbmController,
+ HighBrightnessModeMetadata hbmMetadata,
+ BrightnessMappingStrategy brightnessMappingStrategy,
+ DisplayPowerController2.Injector injector) {
+ this.dpc = dpc;
+ this.display = display;
+ this.displayPowerState = displayPowerState;
+ this.brightnessSetting = brightnessSetting;
+ this.animator = animator;
+ this.automaticBrightnessController = automaticBrightnessController;
+ this.wakelockController = wakelockController;
+ this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
+ this.hbmController = hbmController;
+ this.hbmMetadata = hbmMetadata;
+ this.brightnessMappingStrategy = brightnessMappingStrategy;
+ this.injector = injector;
+ }
+ }
+
+ private class TestInjector extends DisplayPowerController2.Injector {
+ private final DisplayPowerState mDisplayPowerState;
+ private final DualRampAnimator<DisplayPowerState> mAnimator;
+ private final AutomaticBrightnessController mAutomaticBrightnessController;
+ private final WakelockController mWakelockController;
+ private final BrightnessMappingStrategy mBrightnessMappingStrategy;
+ private final HysteresisLevels mHysteresisLevels;
+ private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
+ private final HighBrightnessModeController mHighBrightnessModeController;
+
+ TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
+ AutomaticBrightnessController automaticBrightnessController,
+ WakelockController wakelockController,
+ BrightnessMappingStrategy brightnessMappingStrategy,
+ HysteresisLevels hysteresisLevels,
+ ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
+ HighBrightnessModeController highBrightnessModeController) {
+ mDisplayPowerState = dps;
+ mAnimator = animator;
+ mAutomaticBrightnessController = automaticBrightnessController;
+ mWakelockController = wakelockController;
+ mBrightnessMappingStrategy = brightnessMappingStrategy;
+ mHysteresisLevels = hysteresisLevels;
+ mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
+ mHighBrightnessModeController = highBrightnessModeController;
+ }
+
+ @Override
+ DisplayPowerController2.Clock getClock() {
+ return mClock::now;
+ }
+
+ @Override
+ DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
+ int displayId, int displayState) {
+ return mDisplayPowerState;
+ }
+
+ @Override
+ DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
+ FloatProperty<DisplayPowerState> firstProperty,
+ FloatProperty<DisplayPowerState> secondProperty) {
+ return mAnimator;
+ }
+
+ @Override
+ WakelockController getWakelockController(int displayId,
+ DisplayPowerCallbacks displayPowerCallbacks) {
+ return mWakelockController;
+ }
+
+ @Override
+ DisplayPowerProximityStateController getDisplayPowerProximityStateController(
+ WakelockController wakelockController, DisplayDeviceConfig displayDeviceConfig,
+ Looper looper, Runnable nudgeUpdatePowerState, int displayId,
+ SensorManager sensorManager) {
+ return new DisplayPowerProximityStateController(wakelockController,
+ displayDeviceConfig, looper, nudgeUpdatePowerState, displayId,
+ sensorManager,
+ new DisplayPowerProximityStateController.Injector() {
+ @Override
+ DisplayPowerProximityStateController.Clock createClock() {
+ return mClock::now;
+ }
+ });
+ }
+
+ @Override
+ AutomaticBrightnessController getAutomaticBrightnessController(
+ AutomaticBrightnessController.Callbacks callbacks, Looper looper,
+ SensorManager sensorManager, Sensor lightSensor,
+ BrightnessMappingStrategy interactiveModeBrightnessMapper,
+ int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+ float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+ long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+ boolean resetAmbientLuxAfterWarmUpConfig,
+ HysteresisLevels ambientBrightnessThresholds,
+ HysteresisLevels screenBrightnessThresholds,
+ HysteresisLevels ambientBrightnessThresholdsIdle,
+ HysteresisLevels screenBrightnessThresholdsIdle, Context context,
+ BrightnessRangeController brightnessRangeController,
+ BrightnessThrottler brightnessThrottler,
+ BrightnessMappingStrategy idleModeBrightnessMapper,
+ int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux,
+ float userBrightness) {
+ return mAutomaticBrightnessController;
+ }
+
+ @Override
+ BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
+ DisplayDeviceConfig displayDeviceConfig,
+ DisplayWhiteBalanceController displayWhiteBalanceController) {
+ return mBrightnessMappingStrategy;
+ }
+
+ @Override
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold) {
+ return mHysteresisLevels;
+ }
+
+ @Override
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
+ return mHysteresisLevels;
+ }
+
+ @Override
+ ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
+ SensorManager sensorManager, Sensor lightSensor, Handler handler,
+ ScreenOffBrightnessSensorController.Clock clock, int[] sensorValueToLux,
+ BrightnessMappingStrategy brightnessMapper) {
+ return mScreenOffBrightnessSensorController;
+ }
+
+ @Override
+ HighBrightnessModeController getHighBrightnessModeController(Handler handler, int width,
+ int height, IBinder displayToken, String displayUniqueId, float brightnessMin,
+ float brightnessMax, DisplayDeviceConfig.HighBrightnessModeData hbmData,
+ HighBrightnessModeController.HdrBrightnessDeviceConfig hdrBrightnessCfg,
+ Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata,
+ Context context) {
+ return mHighBrightnessModeController;
+ }
+
+ @Override
+ DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
+ SensorManager sensorManager, Resources resources) {
+ return mDisplayWhiteBalanceControllerMock;
+ }
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
new file mode 100644
index 0000000..5de1876
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -0,0 +1,1374 @@
+/*
+ * Copyright (C) 2022 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 com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.SystemProperties;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.testing.TestableContext;
+import android.util.FloatProperty;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+import com.android.server.LocalServices;
+import com.android.server.am.BatteryStatsService;
+import com.android.server.display.RampAnimator.DualRampAnimator;
+import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.color.ColorDisplayService;
+import com.android.server.display.layout.Layout;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+
+import java.util.List;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayPowerControllerTest {
+ private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
+ private static final String UNIQUE_ID = "unique_id_test123";
+ private static final int FOLLOWER_DISPLAY_ID = DISPLAY_ID + 1;
+ private static final String FOLLOWER_UNIQUE_ID = "unique_id_456";
+ private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
+ private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
+ private static final float PROX_SENSOR_MAX_RANGE = 5;
+ private static final float BRIGHTNESS_RAMP_RATE_FAST_DECREASE = 0.3f;
+ private static final float BRIGHTNESS_RAMP_RATE_FAST_INCREASE = 0.4f;
+ private static final float BRIGHTNESS_RAMP_RATE_SLOW_DECREASE = 0.1f;
+ private static final float BRIGHTNESS_RAMP_RATE_SLOW_INCREASE = 0.2f;
+
+ private OffsettableClock mClock;
+ private TestLooper mTestLooper;
+ private Handler mHandler;
+ private DisplayPowerControllerHolder mHolder;
+ private Sensor mProxSensor;
+
+ @Mock
+ private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
+ @Mock
+ private SensorManager mSensorManagerMock;
+ @Mock
+ private DisplayBlanker mDisplayBlankerMock;
+ @Mock
+ private BrightnessTracker mBrightnessTrackerMock;
+ @Mock
+ private WindowManagerPolicy mWindowManagerPolicyMock;
+ @Mock
+ private PowerManager mPowerManagerMock;
+ @Mock
+ private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
+ @Mock
+ private DisplayWhiteBalanceController mDisplayWhiteBalanceControllerMock;
+ @Captor
+ private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
+
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getContext());
+
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule =
+ new ExtendedMockitoRule.Builder(this)
+ .setStrictness(Strictness.LENIENT)
+ .spyStatic(SystemProperties.class)
+ .spyStatic(BatteryStatsService.class)
+ .build();
+
+ @Before
+ public void setUp() throws Exception {
+ mClock = new OffsettableClock.Stopped();
+ mTestLooper = new TestLooper(mClock::now);
+ mHandler = new Handler(mTestLooper.getLooper());
+
+ // Put the system into manual brightness by default, just to minimize unexpected events and
+ // have a consistent starting state
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+
+
+ addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
+ addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class,
+ mCdsiMock);
+
+ mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
+
+ doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
+ SystemProperties.set(anyString(), any()));
+ doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
+
+ setUpSensors();
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ }
+
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(WindowManagerPolicy.class);
+ LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
+ }
+
+ @Test
+ public void testReleaseProxSuspendBlockersOnExit() throws Exception {
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ // Send a display power request
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ dpr.useProximitySensor = true;
+ mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+ // Run updatePowerState to start listener for the prox sensor
+ advanceTime(1);
+
+ SensorEventListener listener = getSensorEventListener(mProxSensor);
+ assertNotNull(listener);
+
+ listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 5));
+ advanceTime(1);
+
+ // two times, one for unfinished business and one for proximity
+ verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
+ mHolder.dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
+ verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
+ mHolder.dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
+
+ mHolder.dpc.stop();
+ advanceTime(1);
+
+ // two times, one for unfinished business and one for proximity
+ verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
+ mHolder.dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
+ verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
+ mHolder.dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
+ }
+
+ @Test
+ public void testScreenOffBecauseOfProximity() throws Exception {
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ // Send a display power request
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ dpr.useProximitySensor = true;
+ mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+ // Run updatePowerState to start listener for the prox sensor
+ advanceTime(1);
+
+ SensorEventListener listener = getSensorEventListener(mProxSensor);
+ assertNotNull(listener);
+
+ // Send a positive proximity event
+ listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
+ advanceTime(1);
+
+ // The display should have been turned off
+ verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
+
+ clearInvocations(mHolder.displayPowerState);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+ // Send a negative proximity event
+ listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor,
+ (int) PROX_SENSOR_MAX_RANGE + 1));
+ // Advance time by less than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
+ advanceTime(1);
+
+ // The prox sensor is debounced so the display should not have been turned back on yet
+ verify(mHolder.displayPowerState, never()).setScreenState(Display.STATE_ON);
+
+ // Advance time by more than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
+ advanceTime(1000);
+
+ // The display should have been turned back on
+ verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
+ }
+
+ @Test
+ public void testScreenOffBecauseOfProximity_ProxSensorGone() throws Exception {
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ // Send a display power request
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ dpr.useProximitySensor = true;
+ mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+ // Run updatePowerState to start listener for the prox sensor
+ advanceTime(1);
+
+ SensorEventListener listener = getSensorEventListener(mProxSensor);
+ assertNotNull(listener);
+
+ // Send a positive proximity event
+ listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
+ advanceTime(1);
+
+ // The display should have been turned off
+ verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
+
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+ // The display device changes and we no longer have a prox sensor
+ reset(mSensorManagerMock);
+ setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+ mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+ mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+
+ advanceTime(1); // Run updatePowerState
+
+ // The display should have been turned back on and the listener should have been
+ // unregistered
+ verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
+ verify(mSensorManagerMock).unregisterListener(listener);
+ }
+
+ @Test
+ public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() {
+ DisplayPowerControllerHolder followerDpc =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ // send a display power request
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ dpr.useProximitySensor = true;
+ followerDpc.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+ // Run updatePowerState
+ advanceTime(1);
+
+ verify(mSensorManagerMock, never()).registerListener(any(SensorEventListener.class),
+ eq(mProxSensor), anyInt(), any(Handler.class));
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowers_BothDpcsSupportNits() {
+ DisplayPowerControllerHolder followerDpc =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+ // Test different float scale values
+ float leadBrightness = 0.3f;
+ float followerBrightness = 0.4f;
+ float nits = 300;
+ when(mHolder.automaticBrightnessController.convertToNits(leadBrightness)).thenReturn(nits);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(followerBrightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(leadBrightness);
+ listener.onBrightnessChanged(leadBrightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(leadBrightness);
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(followerBrightness);
+ clearInvocations(mHolder.animator, followerDpc.animator);
+
+ // Test the same float scale value
+ float brightness = 0.6f;
+ nits = 600;
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() {
+ DisplayPowerControllerHolder followerDpc =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+ float brightness = 0.3f;
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(300f);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
+ .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() {
+ DisplayPowerControllerHolder followerDpc =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+ float brightness = 0.3f;
+ when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() {
+ DisplayPowerControllerHolder followerDpc =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+ float brightness = 0.3f;
+ when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
+ .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowers_AutomaticBrightness() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ DisplayPowerControllerHolder followerDpc =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ float leadBrightness = 0.1f;
+ float rawLeadBrightness = 0.3f;
+ float followerBrightness = 0.4f;
+ float nits = 300;
+ float ambientLux = 3000;
+ when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness())
+ .thenReturn(rawLeadBrightness);
+ when(mHolder.automaticBrightnessController
+ .getAutomaticScreenBrightness(any(BrightnessEvent.class)))
+ .thenReturn(leadBrightness);
+ when(mHolder.automaticBrightnessController.convertToNits(rawLeadBrightness))
+ .thenReturn(nits);
+ when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(followerBrightness);
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ // One triggered by handleBrightnessModeChange, another triggered by setBrightnessToFollow
+ verify(followerDpc.hbmController, times(2)).onAmbientLuxChange(ambientLux);
+ verify(followerDpc.animator, times(2)).animateTo(eq(followerBrightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(leadBrightness);
+ when(followerDpc.displayPowerState.getScreenBrightness()).thenReturn(followerBrightness);
+ clearInvocations(mHolder.animator, followerDpc.animator);
+
+ leadBrightness = 0.05f;
+ rawLeadBrightness = 0.2f;
+ followerBrightness = 0.3f;
+ nits = 200;
+ ambientLux = 2000;
+ when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness())
+ .thenReturn(rawLeadBrightness);
+ when(mHolder.automaticBrightnessController
+ .getAutomaticScreenBrightness(any(BrightnessEvent.class)))
+ .thenReturn(leadBrightness);
+ when(mHolder.automaticBrightnessController.convertToNits(rawLeadBrightness))
+ .thenReturn(nits);
+ when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(followerBrightness);
+
+ mHolder.dpc.updateBrightness();
+ advanceTime(1); // Run updatePowerState
+
+ // The second time, the animation rate should be slow
+ verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE));
+ verify(followerDpc.hbmController).onAmbientLuxChange(ambientLux);
+ verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE));
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowersRemoval_RemoveSingleFollower() {
+ DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID,
+ FOLLOWER_UNIQUE_ID);
+ DisplayPowerControllerHolder secondFollowerDpc = createDisplayPowerController(
+ SECOND_FOLLOWER_DISPLAY_ID, SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(secondFollowerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ secondFollowerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ // Set the initial brightness on the DPC we're going to remove so we have a fixed value for
+ // it to return to.
+ listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(followerDpc.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
+ final float initialFollowerBrightness = 0.3f;
+ when(followerDpc.brightnessSetting.getBrightness()).thenReturn(initialFollowerBrightness);
+ followerListener.onBrightnessChanged(initialFollowerBrightness);
+ advanceTime(1);
+ verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+ when(followerDpc.displayPowerState.getScreenBrightness())
+ .thenReturn(initialFollowerBrightness);
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+ mHolder.dpc.addDisplayBrightnessFollower(secondFollowerDpc.dpc);
+ clearInvocations(followerDpc.animator);
+
+ // Validate both followers are correctly registered and receiving brightness updates
+ float brightness = 0.6f;
+ float nits = 600;
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(secondFollowerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+ when(followerDpc.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+ when(secondFollowerDpc.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+ clearInvocations(mHolder.animator, followerDpc.animator, secondFollowerDpc.animator);
+
+ // Remove the first follower and validate it goes back to its original brightness.
+ mHolder.dpc.removeDisplayBrightnessFollower(followerDpc.dpc);
+ advanceTime(1);
+ verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE));
+
+ when(followerDpc.displayPowerState.getScreenBrightness())
+ .thenReturn(initialFollowerBrightness);
+ clearInvocations(followerDpc.animator);
+
+ // Change the brightness of the lead display and validate only the second follower responds
+ brightness = 0.7f;
+ nits = 700;
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+ when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(secondFollowerDpc.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ verify(followerDpc.animator, never()).animateTo(anyFloat(), anyFloat(), anyFloat());
+ verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ }
+
+ @Test
+ public void testDisplayBrightnessFollowersRemoval_RemoveAllFollowers() {
+ DisplayPowerControllerHolder followerHolder =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+ DisplayPowerControllerHolder secondFollowerHolder =
+ createDisplayPowerController(SECOND_FOLLOWER_DISPLAY_ID,
+ SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(followerHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(secondFollowerHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ secondFollowerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ // Set the initial brightness on the DPCs we're going to remove so we have a fixed value for
+ // it to return to.
+ listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(followerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
+ listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(secondFollowerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener secondFollowerListener =
+ listenerCaptor.getValue();
+ final float initialFollowerBrightness = 0.3f;
+ when(followerHolder.brightnessSetting.getBrightness()).thenReturn(
+ initialFollowerBrightness);
+ when(secondFollowerHolder.brightnessSetting.getBrightness()).thenReturn(
+ initialFollowerBrightness);
+ followerListener.onBrightnessChanged(initialFollowerBrightness);
+ secondFollowerListener.onBrightnessChanged(initialFollowerBrightness);
+ advanceTime(1);
+ verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+ when(followerHolder.displayPowerState.getScreenBrightness())
+ .thenReturn(initialFollowerBrightness);
+ when(secondFollowerHolder.displayPowerState.getScreenBrightness())
+ .thenReturn(initialFollowerBrightness);
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerHolder.dpc);
+ mHolder.dpc.addDisplayBrightnessFollower(secondFollowerHolder.dpc);
+ clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
+
+ // Validate both followers are correctly registered and receiving brightness updates
+ float brightness = 0.6f;
+ float nits = 600;
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+ when(followerHolder.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(secondFollowerHolder.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ verify(followerHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+ verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+ when(followerHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+ when(secondFollowerHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+ clearInvocations(mHolder.animator, followerHolder.animator, secondFollowerHolder.animator);
+
+ // Stop the lead DPC and validate that the followers go back to their original brightness.
+ mHolder.dpc.stop();
+ advanceTime(1);
+ verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE));
+ verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE));
+ clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
+ }
+
+ @Test
+ public void testDoesNotSetScreenStateForNonDefaultDisplayUntilBootCompleted() {
+ // We should still set screen state for the default display
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+ verify(mHolder.displayPowerState, times(2)).setScreenState(anyInt());
+
+ mHolder = createDisplayPowerController(42, UNIQUE_ID);
+
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+ verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
+
+ mHolder.dpc.onBootCompleted();
+ advanceTime(1); // Run updatePowerState
+ verify(mHolder.displayPowerState).setScreenState(anyInt());
+ }
+
+ @Test
+ public void testSetScreenOffBrightnessSensorEnabled_DisplayIsOff() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_OFF;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+ .setLightSensorEnabled(true);
+
+ // The display turns on and we use the brightness value recommended by
+ // ScreenOffBrightnessSensorController
+ clearInvocations(mHolder.screenOffBrightnessSensorController);
+ float brightness = 0.14f;
+ when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
+ .thenReturn(brightness);
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+ any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+ .getAutomaticScreenBrightness();
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ }
+
+ @Test
+ public void testSetScreenOffBrightnessSensorEnabled_DisplayIsInDoze() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+ .setLightSensorEnabled(true);
+
+ // The display turns on and we use the brightness value recommended by
+ // ScreenOffBrightnessSensorController
+ clearInvocations(mHolder.screenOffBrightnessSensorController);
+ float brightness = 0.14f;
+ when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
+ .thenReturn(brightness);
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+ any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+ .getAutomaticScreenBrightness();
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ }
+
+ @Test
+ public void testSetScreenOffBrightnessSensorDisabled_AutoBrightnessIsDisabled() {
+ // Tests are set up with manual brightness by default, so no need to set it here.
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_OFF;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+ .setLightSensorEnabled(false);
+ }
+
+ @Test
+ public void testSetScreenOffBrightnessSensorDisabled_DisplayIsDisabled() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+ .setLightSensorEnabled(false);
+ }
+
+ @Test
+ public void testSetScreenOffBrightnessSensorDisabled_DisplayIsOn() {
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+ .setLightSensorEnabled(false);
+ }
+
+ @Test
+ public void testSetScreenOffBrightnessSensorDisabled_DisplayIsAFollower() {
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_OFF;
+
+ mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, /* leadDisplayId= */ 42);
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+ .setLightSensorEnabled(false);
+ }
+
+ @Test
+ public void testStopScreenOffBrightnessSensorControllerWhenDisplayDeviceChanges() {
+ // New display device
+ setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+ mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+
+ mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.screenOffBrightnessSensorController).stop();
+ }
+
+ @Test
+ public void testAutoBrightnessEnabled_DisplayIsOn() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.automaticBrightnessController).configure(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+ /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ /* userChangedBrightness= */ false, /* adjustment= */ 0,
+ /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ false
+ );
+ verify(mHolder.hbmController)
+ .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
+ }
+
+ @Test
+ public void testAutoBrightnessEnabled_DisplayIsInDoze() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.automaticBrightnessController).configure(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+ /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ /* userChangedBrightness= */ false, /* adjustment= */ 0,
+ /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_DOZE,
+ /* shouldResetShortTermModel= */ false
+ );
+ verify(mHolder.hbmController)
+ .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
+ }
+
+ @Test
+ public void testAutoBrightnessDisabled_ManualBrightnessMode() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ // One triggered by the test, the other by handleBrightnessModeChange
+ verify(mHolder.automaticBrightnessController, times(2)).configure(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
+ /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ /* userChangedBrightness= */ false, /* adjustment= */ 0,
+ /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ false
+ );
+ verify(mHolder.hbmController, times(2))
+ .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
+ }
+
+ @Test
+ public void testAutoBrightnessDisabled_DisplayIsOff() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_OFF;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.automaticBrightnessController).configure(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+ /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ /* userChangedBrightness= */ false, /* adjustment= */ 0,
+ /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_OFF,
+ /* shouldResetShortTermModel= */ false
+ );
+ verify(mHolder.hbmController).setAutoBrightnessEnabled(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
+ }
+
+ @Test
+ public void testAutoBrightnessDisabled_DisplayIsInDoze() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.automaticBrightnessController).configure(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+ /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ /* userChangedBrightness= */ false, /* adjustment= */ 0,
+ /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_DOZE,
+ /* shouldResetShortTermModel= */ false
+ );
+ verify(mHolder.hbmController).setAutoBrightnessEnabled(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
+ }
+
+ @Test
+ public void testAutoBrightnessDisabled_FollowerDisplay() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ mHolder.dpc.setBrightnessToFollow(0.3f, -1, 0, /* slowChange= */ false);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ // One triggered by the test, the other by handleBrightnessModeChange
+ verify(mHolder.automaticBrightnessController, times(2)).configure(
+ AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
+ /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+ /* userChangedBrightness= */ false, /* adjustment= */ 0,
+ /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ false
+ );
+
+ // HBM should be allowed for the follower display
+ verify(mHolder.hbmController)
+ .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
+ }
+
+ @Test
+ public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
+ float brightness = 0.3f;
+ float nits = 500;
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay,
+ true);
+
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+
+ mHolder.dpc.setBrightness(brightness);
+ verify(mHolder.brightnessSetting).setBrightnessNitsForDefaultDisplay(nits);
+
+ float newBrightness = 0.4f;
+ when(mHolder.brightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
+ when(mHolder.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(newBrightness);
+ // New display device
+ setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+ mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+ mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+ // One triggered by handleBrightnessModeChange, another triggered by onDisplayChanged
+ verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat());
+ }
+
+ @Test
+ public void testShortTermModelPersistsWhenDisplayDeviceChanges() {
+ float lux = 2000;
+ float brightness = 0.4f;
+ float nits = 500;
+ when(mHolder.brightnessMappingStrategy.getUserLux()).thenReturn(lux);
+ when(mHolder.brightnessMappingStrategy.getUserBrightness()).thenReturn(brightness);
+ when(mHolder.brightnessMappingStrategy.convertToNits(brightness)).thenReturn(nits);
+ when(mHolder.brightnessMappingStrategy.convertToFloatScale(nits)).thenReturn(brightness);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1);
+ clearInvocations(mHolder.injector);
+
+ // New display device
+ setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+ mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+ mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+ advanceTime(1);
+
+ verify(mHolder.injector).getAutomaticBrightnessController(
+ any(AutomaticBrightnessController.Callbacks.class),
+ any(Looper.class),
+ eq(mSensorManagerMock),
+ any(),
+ eq(mHolder.brightnessMappingStrategy),
+ anyInt(),
+ anyFloat(),
+ anyFloat(),
+ anyFloat(),
+ anyInt(),
+ anyInt(),
+ anyLong(),
+ anyLong(),
+ anyBoolean(),
+ any(HysteresisLevels.class),
+ any(HysteresisLevels.class),
+ any(HysteresisLevels.class),
+ any(HysteresisLevels.class),
+ eq(mContext),
+ any(BrightnessRangeController.class),
+ any(BrightnessThrottler.class),
+ isNull(),
+ anyInt(),
+ anyInt(),
+ eq(lux),
+ eq(brightness)
+ );
+ }
+
+ @Test
+ public void testUpdateBrightnessThrottlingDataId() {
+ mHolder.display.getDisplayInfoLocked().thermalBrightnessThrottlingDataId =
+ "throttling-data-id";
+ clearInvocations(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig());
+
+ mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig())
+ .getThermalBrightnessThrottlingDataMapByThrottlingId();
+ }
+
+ @Test
+ public void testDwbcCallsHappenOnHandler() {
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+ mHolder.dpc.setAutomaticScreenBrightnessMode(true);
+ verify(mDisplayWhiteBalanceControllerMock, never()).setStrongModeEnabled(true);
+
+ // dispatch handler looper
+ advanceTime(1);
+ verify(mDisplayWhiteBalanceControllerMock, times(1)).setStrongModeEnabled(true);
+ }
+
+ /**
+ * Creates a mock and registers it to {@link LocalServices}.
+ */
+ private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+ LocalServices.removeServiceForTest(clazz);
+ LocalServices.addService(clazz, mock);
+ }
+
+ private void advanceTime(long timeMs) {
+ mClock.fastForward(timeMs);
+ mTestLooper.dispatchAll();
+ }
+
+ private void setUpSensors() throws Exception {
+ mProxSensor = TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY,
+ PROX_SENSOR_MAX_RANGE);
+ Sensor screenOffBrightnessSensor = TestUtils.createSensor(
+ Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+ when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL)))
+ .thenReturn(List.of(mProxSensor, screenOffBrightnessSensor));
+ }
+
+ private SensorEventListener getSensorEventListener(Sensor sensor) {
+ verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(),
+ eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class));
+ return mSensorEventListenerCaptor.getValue();
+ }
+
+ private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock,
+ DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock,
+ boolean isEnabled) {
+ DisplayInfo info = new DisplayInfo();
+ DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
+ deviceInfo.uniqueId = uniqueId;
+
+ when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
+ when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock);
+ when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
+ when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled);
+ when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
+ when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
+ when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
+ when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
+ when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData() {
+ {
+ type = Sensor.STRING_TYPE_PROXIMITY;
+ name = null;
+ }
+ });
+ when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
+ when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
+ when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData());
+ when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData() {
+ {
+ type = Sensor.STRING_TYPE_LIGHT;
+ name = null;
+ }
+ });
+ when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
+ .thenReturn(new int[0]);
+ when(displayDeviceConfigMock.getBrightnessRampFastDecrease())
+ .thenReturn(BRIGHTNESS_RAMP_RATE_FAST_DECREASE);
+ when(displayDeviceConfigMock.getBrightnessRampFastIncrease())
+ .thenReturn(BRIGHTNESS_RAMP_RATE_FAST_INCREASE);
+ when(displayDeviceConfigMock.getBrightnessRampSlowDecrease())
+ .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE);
+ when(displayDeviceConfigMock.getBrightnessRampSlowIncrease())
+ .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE);
+ }
+
+ private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
+ String uniqueId) {
+ return createDisplayPowerController(displayId, uniqueId, /* isEnabled= */ true);
+ }
+
+ private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
+ String uniqueId, boolean isEnabled) {
+ final DisplayPowerState displayPowerState = mock(DisplayPowerState.class);
+ final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class);
+ final AutomaticBrightnessController automaticBrightnessController =
+ mock(AutomaticBrightnessController.class);
+ final BrightnessMappingStrategy brightnessMappingStrategy =
+ mock(BrightnessMappingStrategy.class);
+ final HysteresisLevels hysteresisLevels = mock(HysteresisLevels.class);
+ final ScreenOffBrightnessSensorController screenOffBrightnessSensorController =
+ mock(ScreenOffBrightnessSensorController.class);
+ final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
+
+ when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
+
+ DisplayPowerController.Injector injector = spy(new TestInjector(displayPowerState, animator,
+ automaticBrightnessController, brightnessMappingStrategy, hysteresisLevels,
+ screenOffBrightnessSensorController, hbmController));
+
+ final LogicalDisplay display = mock(LogicalDisplay.class);
+ final DisplayDevice device = mock(DisplayDevice.class);
+ final HighBrightnessModeMetadata hbmMetadata = mock(HighBrightnessModeMetadata.class);
+ final BrightnessSetting brightnessSetting = mock(BrightnessSetting.class);
+ final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class);
+
+ setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
+
+ final DisplayPowerController dpc = new DisplayPowerController(
+ mContext, injector, mDisplayPowerCallbacksMock, mHandler,
+ mSensorManagerMock, mDisplayBlankerMock, display,
+ mBrightnessTrackerMock, brightnessSetting, () -> {},
+ hbmMetadata, /* bootCompleted= */ false);
+
+ return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
+ animator, automaticBrightnessController, screenOffBrightnessSensorController,
+ hbmController, hbmMetadata, brightnessMappingStrategy, injector);
+ }
+
+ /**
+ * A class for holding a DisplayPowerController under test and all the mocks specifically
+ * related to it.
+ */
+ private static class DisplayPowerControllerHolder {
+ public final DisplayPowerController dpc;
+ public final LogicalDisplay display;
+ public final DisplayPowerState displayPowerState;
+ public final BrightnessSetting brightnessSetting;
+ public final DualRampAnimator<DisplayPowerState> animator;
+ public final AutomaticBrightnessController automaticBrightnessController;
+ public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController;
+ public final HighBrightnessModeController hbmController;
+ public final HighBrightnessModeMetadata hbmMetadata;
+ public final BrightnessMappingStrategy brightnessMappingStrategy;
+ public final DisplayPowerController.Injector injector;
+
+ DisplayPowerControllerHolder(DisplayPowerController dpc, LogicalDisplay display,
+ DisplayPowerState displayPowerState, BrightnessSetting brightnessSetting,
+ DualRampAnimator<DisplayPowerState> animator,
+ AutomaticBrightnessController automaticBrightnessController,
+ ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
+ HighBrightnessModeController hbmController,
+ HighBrightnessModeMetadata hbmMetadata,
+ BrightnessMappingStrategy brightnessMappingStrategy,
+ DisplayPowerController.Injector injector) {
+ this.dpc = dpc;
+ this.display = display;
+ this.displayPowerState = displayPowerState;
+ this.brightnessSetting = brightnessSetting;
+ this.animator = animator;
+ this.automaticBrightnessController = automaticBrightnessController;
+ this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
+ this.hbmController = hbmController;
+ this.hbmMetadata = hbmMetadata;
+ this.brightnessMappingStrategy = brightnessMappingStrategy;
+ this.injector = injector;
+ }
+ }
+
+ private class TestInjector extends DisplayPowerController.Injector {
+ private final DisplayPowerState mDisplayPowerState;
+ private final DualRampAnimator<DisplayPowerState> mAnimator;
+ private final AutomaticBrightnessController mAutomaticBrightnessController;
+ private final BrightnessMappingStrategy mBrightnessMappingStrategy;
+ private final HysteresisLevels mHysteresisLevels;
+ private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
+ private final HighBrightnessModeController mHighBrightnessModeController;
+
+ TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
+ AutomaticBrightnessController automaticBrightnessController,
+ BrightnessMappingStrategy brightnessMappingStrategy,
+ HysteresisLevels hysteresisLevels,
+ ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
+ HighBrightnessModeController highBrightnessModeController) {
+ mDisplayPowerState = dps;
+ mAnimator = animator;
+ mAutomaticBrightnessController = automaticBrightnessController;
+ mBrightnessMappingStrategy = brightnessMappingStrategy;
+ mHysteresisLevels = hysteresisLevels;
+ mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
+ mHighBrightnessModeController = highBrightnessModeController;
+ }
+
+ @Override
+ DisplayPowerController.Clock getClock() {
+ return mClock::now;
+ }
+
+ @Override
+ DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
+ int displayId, int displayState) {
+ return mDisplayPowerState;
+ }
+
+ @Override
+ DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
+ FloatProperty<DisplayPowerState> firstProperty,
+ FloatProperty<DisplayPowerState> secondProperty) {
+ return mAnimator;
+ }
+
+ @Override
+ AutomaticBrightnessController getAutomaticBrightnessController(
+ AutomaticBrightnessController.Callbacks callbacks, Looper looper,
+ SensorManager sensorManager, Sensor lightSensor,
+ BrightnessMappingStrategy interactiveModeBrightnessMapper,
+ int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+ float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+ long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+ boolean resetAmbientLuxAfterWarmUpConfig,
+ HysteresisLevels ambientBrightnessThresholds,
+ HysteresisLevels screenBrightnessThresholds,
+ HysteresisLevels ambientBrightnessThresholdsIdle,
+ HysteresisLevels screenBrightnessThresholdsIdle, Context context,
+ BrightnessRangeController brightnessRangeController,
+ BrightnessThrottler brightnessThrottler,
+ BrightnessMappingStrategy idleModeBrightnessMapper,
+ int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux,
+ float userBrightness) {
+ return mAutomaticBrightnessController;
+ }
+
+ @Override
+ BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
+ DisplayDeviceConfig displayDeviceConfig,
+ DisplayWhiteBalanceController displayWhiteBalanceController) {
+ return mBrightnessMappingStrategy;
+ }
+
+ @Override
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold) {
+ return mHysteresisLevels;
+ }
+
+ @Override
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
+ return mHysteresisLevels;
+ }
+
+ @Override
+ ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
+ SensorManager sensorManager, Sensor lightSensor, Handler handler,
+ ScreenOffBrightnessSensorController.Clock clock, int[] sensorValueToLux,
+ BrightnessMappingStrategy brightnessMapper) {
+ return mScreenOffBrightnessSensorController;
+ }
+
+ @Override
+ HighBrightnessModeController getHighBrightnessModeController(Handler handler, int width,
+ int height, IBinder displayToken, String displayUniqueId, float brightnessMin,
+ float brightnessMax, DisplayDeviceConfig.HighBrightnessModeData hbmData,
+ HighBrightnessModeController.HdrBrightnessDeviceConfig hdrBrightnessCfg,
+ Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata,
+ Context context) {
+ return mHighBrightnessModeController;
+ }
+
+ @Override
+ DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
+ SensorManager sensorManager, Resources resources) {
+ return mDisplayWhiteBalanceControllerMock;
+ }
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
new file mode 100644
index 0000000..534a708
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) 2022 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 com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.test.TestLooper;
+import android.view.Display;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayPowerProximityStateControllerTest {
+ @Mock
+ WakelockController mWakelockController;
+
+ @Mock
+ DisplayDeviceConfig mDisplayDeviceConfig;
+
+ @Mock
+ Runnable mNudgeUpdatePowerState;
+
+ @Mock
+ SensorManager mSensorManager;
+
+ private Sensor mProximitySensor;
+ private OffsettableClock mClock;
+ private TestLooper mTestLooper;
+ private SensorEventListener mSensorEventListener;
+ private DisplayPowerProximityStateController mDisplayPowerProximityStateController;
+
+ @Before
+ public void before() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mClock = new OffsettableClock.Stopped();
+ mTestLooper = new TestLooper(mClock::now);
+ when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData() {
+ {
+ type = Sensor.STRING_TYPE_PROXIMITY;
+ // This is kept null because currently there is no way to define a sensor
+ // name in TestUtils
+ name = null;
+ }
+ });
+ setUpProxSensor();
+ DisplayPowerProximityStateController.Injector injector =
+ new DisplayPowerProximityStateController.Injector() {
+ @Override
+ DisplayPowerProximityStateController.Clock createClock() {
+ return mClock::now;
+ }
+ };
+ mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+ mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+ mNudgeUpdatePowerState, Display.DEFAULT_DISPLAY,
+ mSensorManager, injector);
+ mSensorEventListener = mDisplayPowerProximityStateController.getProximitySensorListener();
+ }
+
+ @Test
+ public void updatePendingProximityRequestsWorksAsExpectedWhenPending() {
+ // Set the system to pending wait for proximity
+ assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+ true));
+ assertTrue(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+ // Update the pending proximity wait request
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+ assertTrue(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+ }
+
+ @Test
+ public void updatePendingProximityRequestsWorksAsExpectedWhenNotPending() {
+ // Will not wait or be in the pending wait state of not already pending
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+ }
+
+ @Test
+ public void updatePendingProximityRequestsWorksAsExpectedWhenPendingAndProximityIgnored()
+ throws Exception {
+ // Set the system to the state where it will ignore proximity unless changed
+ enableProximitySensor();
+ emitAndValidatePositiveProximityEvent();
+ mDisplayPowerProximityStateController.ignoreProximitySensorUntilChangedInternal();
+ advanceTime();
+ assertTrue(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ verify(mNudgeUpdatePowerState, times(2)).run();
+
+ // Do not set the system to pending wait for proximity
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+ // Set the system to pending wait for proximity. But because the proximity is being
+ // ignored, it will not wait or not set the pending wait
+ assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+ true));
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+ }
+
+ @Test
+ public void cleanupDisablesTheProximitySensor() {
+ enableProximitySensor();
+ mDisplayPowerProximityStateController.cleanup();
+ verify(mSensorManager).unregisterListener(
+ mSensorEventListener);
+ assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertEquals(mDisplayPowerProximityStateController.getProximity(),
+ DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+ when(mWakelockController.releaseWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+ assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+ }
+
+ @Test
+ public void isProximitySensorAvailableReturnsTrueWhenAvailable() {
+ assertTrue(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+ }
+
+ @Test
+ public void isProximitySensorAvailableReturnsFalseWhenNotAvailableAndNoDefault() {
+ when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData() {
+ {
+ type = null;
+ name = null;
+ }
+ });
+ mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+ mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+ mNudgeUpdatePowerState, Display.DEFAULT_DISPLAY,
+ mSensorManager, null);
+ assertFalse(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+ }
+
+ @Test
+ public void isProximitySensorAvailableReturnsTrueWhenNotAvailableAndHasDefault()
+ throws Exception {
+ when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData() {
+ {
+ type = null;
+ name = null;
+ }
+ });
+ when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(
+ TestUtils.createSensor(Sensor.TYPE_PROXIMITY, "proximity"));
+ mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+ mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+ mNudgeUpdatePowerState, Display.DEFAULT_DISPLAY,
+ mSensorManager, null);
+ assertTrue(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+ }
+
+ @Test
+ public void isProximitySensorAvailableReturnsFalseWhenNotAvailableHasDefaultNonDefaultDisplay()
+ throws Exception {
+ when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData() {
+ {
+ type = null;
+ name = null;
+ }
+ });
+ when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(
+ TestUtils.createSensor(Sensor.TYPE_PROXIMITY, "proximity"));
+ mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+ mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+ mNudgeUpdatePowerState, 1,
+ mSensorManager, null);
+ assertFalse(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+ }
+
+ @Test
+ public void isProximitySensorAvailableReturnsTrueWhenNoSensorConfigured() throws Exception {
+ when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(null);
+ when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(
+ TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY));
+
+ mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+ mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+ mNudgeUpdatePowerState, Display.DEFAULT_DISPLAY,
+ mSensorManager, null);
+ assertFalse(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+ }
+
+ @Test
+ public void notifyDisplayDeviceChangedReloadsTheProximitySensor() throws Exception {
+ DisplayDeviceConfig updatedDisplayDeviceConfig = mock(DisplayDeviceConfig.class);
+ when(updatedDisplayDeviceConfig.getProximitySensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData() {
+ {
+ type = Sensor.STRING_TYPE_PROXIMITY;
+ name = null;
+ }
+ });
+ Sensor newProxSensor = TestUtils.createSensor(
+ Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 4.0f);
+ when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
+ .thenReturn(List.of(newProxSensor));
+ mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(
+ updatedDisplayDeviceConfig);
+ assertTrue(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+ }
+
+ @Test
+ public void setPendingWaitForNegativeProximityLockedWorksAsExpected() {
+ // Doesn't do anything not asked to wait
+ assertFalse(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+ false));
+ assertFalse(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+ // Sets pending wait negative proximity if not already waiting
+ assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+ true));
+ assertTrue(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+ // Will not set pending wait negative proximity if already waiting
+ assertFalse(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+ true));
+ assertTrue(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+ }
+
+ @Test
+ public void evaluateProximityStateWhenRequestedUseOfProximitySensor() throws Exception {
+ // Enable the proximity sensor
+ enableProximitySensor();
+
+ // Emit a positive proximity event to move the system to a state to mimic a scenario
+ // where the system is in positive proximity
+ emitAndValidatePositiveProximityEvent();
+
+ // Again evaluate the proximity state, with system having positive proximity
+ setScreenOffBecauseOfPositiveProximityState();
+ }
+
+ @Test
+ public void evaluateProximityStateWhenScreenOffBecauseOfPositiveProximity() throws Exception {
+ // Enable the proximity sensor
+ enableProximitySensor();
+
+ // Emit a positive proximity event to move the system to a state to mimic a scenario
+ // where the system is in positive proximity
+ emitAndValidatePositiveProximityEvent();
+
+ // Again evaluate the proximity state, with system having positive proximity
+ setScreenOffBecauseOfPositiveProximityState();
+
+ // Set the system to pending wait for proximity
+ mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(true);
+ // Update the pending proximity wait request
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+
+ // Start ignoring proximity sensor
+ mDisplayPowerProximityStateController.ignoreProximitySensorUntilChangedInternal();
+ // Re-evaluate the proximity state, such that the system is detecting the positive
+ // proximity, and screen is off because of that
+ when(mWakelockController.getOnProximityNegativeRunnable()).thenReturn(mock(Runnable.class));
+ mDisplayPowerProximityStateController.updateProximityState(mock(
+ DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_ON);
+ assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+ assertTrue(
+ mDisplayPowerProximityStateController
+ .shouldSkipRampBecauseOfProximityChangeToNegative());
+ verify(mWakelockController).acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE);
+ }
+
+ @Test
+ public void evaluateProximityStateWhenDisplayIsTurningOff() throws Exception {
+ // Enable the proximity sensor
+ enableProximitySensor();
+
+ // Emit a positive proximity event to move the system to a state to mimic a scenario
+ // where the system is in positive proximity
+ emitAndValidatePositiveProximityEvent();
+
+ // Again evaluate the proximity state, with system having positive proximity
+ setScreenOffBecauseOfPositiveProximityState();
+
+ // Re-evaluate the proximity state, such that the system is detecting the positive
+ // proximity, and screen is off because of that
+ mDisplayPowerProximityStateController.updateProximityState(mock(
+ DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_OFF);
+ verify(mSensorManager).unregisterListener(
+ mSensorEventListener);
+ assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertEquals(mDisplayPowerProximityStateController.getProximity(),
+ DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+ when(mWakelockController.releaseWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+ assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+ }
+
+ @Test
+ public void evaluateProximityStateNotWaitingForNegativeProximityAndNotUsingProxSensor()
+ throws Exception {
+ // Enable the proximity sensor
+ enableProximitySensor();
+
+ // Emit a positive proximity event to move the system to a state to mimic a scenario
+ // where the system is in positive proximity
+ emitAndValidatePositiveProximityEvent();
+
+ // Re-evaluate the proximity state, such that the system is detecting the positive
+ // proximity, and screen is off because of that
+ mDisplayPowerProximityStateController.updateProximityState(mock(
+ DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_ON);
+ verify(mSensorManager).unregisterListener(
+ mSensorEventListener);
+ assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertEquals(mDisplayPowerProximityStateController.getProximity(),
+ DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+ when(mWakelockController.releaseWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+ assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+ }
+
+ private void advanceTime() {
+ mClock.fastForward(1);
+ mTestLooper.dispatchAll();
+ }
+
+ private void setUpProxSensor() throws Exception {
+ mProximitySensor = TestUtils.createSensor(
+ Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 5.0f);
+ when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
+ .thenReturn(List.of(mProximitySensor));
+ }
+
+ private void emitAndValidatePositiveProximityEvent() throws Exception {
+ // Emit a positive proximity event to move the system to a state to mimic a scenario
+ // where the system is in positive proximity
+ when(mWakelockController.releaseWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+ mSensorEventListener.onSensorChanged(TestUtils.createSensorEvent(mProximitySensor, 4));
+ verify(mSensorManager).registerListener(mSensorEventListener,
+ mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+ mDisplayPowerProximityStateController.getHandler());
+ verify(mWakelockController).acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
+ assertEquals(mDisplayPowerProximityStateController.getPendingProximity(),
+ DisplayPowerProximityStateController.PROXIMITY_POSITIVE);
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertEquals(mDisplayPowerProximityStateController.getProximity(),
+ DisplayPowerProximityStateController.PROXIMITY_POSITIVE);
+ verify(mNudgeUpdatePowerState).run();
+ assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+ }
+
+ // Call evaluateProximityState with the request for using the proximity sensor. This will
+ // register the proximity sensor listener, which will be needed for mocking positive
+ // proximity scenarios.
+ private void enableProximitySensor() {
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+ displayPowerRequest.useProximitySensor = true;
+ mDisplayPowerProximityStateController.updateProximityState(displayPowerRequest,
+ Display.STATE_ON);
+ verify(mSensorManager).registerListener(
+ mSensorEventListener,
+ mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+ mDisplayPowerProximityStateController.getHandler());
+ assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertFalse(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+ verifyZeroInteractions(mWakelockController);
+ }
+
+ private void setScreenOffBecauseOfPositiveProximityState() {
+ // Prepare a request to indicate that the proximity sensor is to be used
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+ displayPowerRequest.useProximitySensor = true;
+
+ Runnable onProximityPositiveRunnable = mock(Runnable.class);
+ when(mWakelockController.getOnProximityPositiveRunnable()).thenReturn(
+ onProximityPositiveRunnable);
+
+ mDisplayPowerProximityStateController.updateProximityState(displayPowerRequest,
+ Display.STATE_ON);
+ verify(mSensorManager).registerListener(
+ mSensorEventListener,
+ mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+ mDisplayPowerProximityStateController.getHandler());
+ assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertTrue(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+ verify(mWakelockController).acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE);
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
new file mode 100644
index 0000000..d9fbba5
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class HighBrightnessModeMetadataMapperTest {
+
+ private HighBrightnessModeMetadataMapper mHighBrightnessModeMetadataMapper;
+
+ @Before
+ public void setUp() {
+ mHighBrightnessModeMetadataMapper = new HighBrightnessModeMetadataMapper();
+ }
+
+ @Test
+ public void testGetHighBrightnessModeMetadata() {
+ // Display device is null
+ final LogicalDisplay display = mock(LogicalDisplay.class);
+ when(display.getPrimaryDisplayDeviceLocked()).thenReturn(null);
+ assertNull(mHighBrightnessModeMetadataMapper.getHighBrightnessModeMetadataLocked(display));
+
+ // No HBM metadata stored for this display yet
+ final DisplayDevice device = mock(DisplayDevice.class);
+ when(display.getPrimaryDisplayDeviceLocked()).thenReturn(device);
+ HighBrightnessModeMetadata hbmMetadata =
+ mHighBrightnessModeMetadataMapper.getHighBrightnessModeMetadataLocked(display);
+ assertTrue(hbmMetadata.getHbmEventQueue().isEmpty());
+ assertTrue(hbmMetadata.getRunningStartTimeMillis() < 0);
+
+ // Modify the metadata
+ long startTimeMillis = 100;
+ long endTimeMillis = 200;
+ long setTime = 300;
+ hbmMetadata.addHbmEvent(new HbmEvent(startTimeMillis, endTimeMillis));
+ hbmMetadata.setRunningStartTimeMillis(setTime);
+ hbmMetadata =
+ mHighBrightnessModeMetadataMapper.getHighBrightnessModeMetadataLocked(display);
+ assertEquals(1, hbmMetadata.getHbmEventQueue().size());
+ assertEquals(startTimeMillis,
+ hbmMetadata.getHbmEventQueue().getFirst().getStartTimeMillis());
+ assertEquals(endTimeMillis, hbmMetadata.getHbmEventQueue().getFirst().getEndTimeMillis());
+ assertEquals(setTime, hbmMetadata.getRunningStartTimeMillis());
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
new file mode 100644
index 0000000..aa0a2fe
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -0,0 +1,1317 @@
+/*
+ * 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 com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.view.Display;
+import android.view.DisplayAddress;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.RefreshRateRange;
+import android.view.SurfaceControl.RefreshRateRanges;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.internal.R;
+import com.android.server.LocalServices;
+import com.android.server.display.LocalDisplayAdapter.BacklightAdapter;
+import com.android.server.display.mode.DisplayModeDirector;
+import com.android.server.lights.LightsManager;
+import com.android.server.lights.LogicalLight;
+
+import com.google.common.truth.Truth;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LocalDisplayAdapterTest {
+ private static final Long DISPLAY_MODEL = Long.valueOf(0xAAAAAAAAL);
+ private static final int PORT_A = 0;
+ private static final int PORT_B = 0x80;
+ private static final int PORT_C = 0xFF;
+ private static final float REFRESH_RATE = 60f;
+ private static final RefreshRateRange REFRESH_RATE_RANGE =
+ new RefreshRateRange(REFRESH_RATE, REFRESH_RATE);
+ private static final RefreshRateRanges REFRESH_RATE_RANGES =
+ new RefreshRateRanges(REFRESH_RATE_RANGE, REFRESH_RATE_RANGE);
+
+ private static final long HANDLER_WAIT_MS = 100;
+
+ private static final int[] HDR_TYPES = new int[]{1, 2};
+
+ private StaticMockitoSession mMockitoSession;
+
+ private LocalDisplayAdapter mAdapter;
+
+ @Mock
+ private DisplayManagerService.SyncRoot mMockedSyncRoot;
+ @Mock
+ private Context mMockedContext;
+ @Mock
+ private Resources mMockedResources;
+ @Mock
+ private LightsManager mMockedLightsManager;
+ @Mock
+ private LogicalLight mMockedBacklight;
+
+ private Handler mHandler;
+
+ private TestListener mListener = new TestListener();
+
+ private LinkedList<DisplayAddress.Physical> mAddresses = new LinkedList<>();
+
+ private Injector mInjector;
+
+ @Mock
+ private LocalDisplayAdapter.SurfaceControlProxy mSurfaceControlProxy;
+ private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f };
+ private static final int[] BACKLIGHT_RANGE = { 1, 255 };
+ private static final float[] BACKLIGHT_RANGE_ZERO_TO_ONE = { 0.0f, 1.0f };
+
+ @Before
+ public void setUp() throws Exception {
+ mMockitoSession = mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ mHandler = new Handler(Looper.getMainLooper());
+ doReturn(mMockedResources).when(mMockedContext).getResources();
+ LocalServices.removeServiceForTest(LightsManager.class);
+ LocalServices.addService(LightsManager.class, mMockedLightsManager);
+ mInjector = new Injector();
+ when(mSurfaceControlProxy.getBootDisplayModeSupport()).thenReturn(true);
+ mAdapter = new LocalDisplayAdapter(mMockedSyncRoot, mMockedContext, mHandler,
+ mListener, mInjector);
+ spyOn(mAdapter);
+ doReturn(mMockedContext).when(mAdapter).getOverlayContext();
+
+ TypedArray mockNitsRange = createFloatTypedArray(DISPLAY_RANGE_NITS);
+ when(mMockedResources.obtainTypedArray(R.array.config_screenBrightnessNits))
+ .thenReturn(mockNitsRange);
+ when(mMockedResources.getIntArray(R.array.config_screenBrightnessBacklight))
+ .thenReturn(BACKLIGHT_RANGE);
+ when(mMockedResources.getFloat(com.android.internal.R.dimen
+ .config_screenBrightnessSettingMinimumFloat))
+ .thenReturn(BACKLIGHT_RANGE_ZERO_TO_ONE[0]);
+ when(mMockedResources.getFloat(com.android.internal.R.dimen
+ .config_screenBrightnessSettingMaximumFloat))
+ .thenReturn(BACKLIGHT_RANGE_ZERO_TO_ONE[1]);
+ when(mMockedResources.getStringArray(R.array.config_displayUniqueIdArray))
+ .thenReturn(new String[]{});
+ TypedArray mockArray = mock(TypedArray.class);
+ when(mockArray.length()).thenReturn(0);
+ when(mMockedResources.obtainTypedArray(R.array.config_maskBuiltInDisplayCutoutArray))
+ .thenReturn(mockArray);
+ when(mMockedResources.obtainTypedArray(R.array.config_waterfallCutoutArray))
+ .thenReturn(mockArray);
+ when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerRadiusArray))
+ .thenReturn(mockArray);
+ when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerTopRadiusArray))
+ .thenReturn(mockArray);
+ when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerBottomRadiusArray))
+ .thenReturn(mockArray);
+ when(mMockedResources.obtainTypedArray(
+ com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
+ .thenReturn(mockArray);
+ when(mMockedResources.getIntArray(
+ com.android.internal.R.array.config_autoBrightnessLevels))
+ .thenReturn(new int[]{});
+ when(mMockedResources.obtainTypedArray(R.array.config_displayShapeArray))
+ .thenReturn(mockArray);
+ when(mMockedResources.obtainTypedArray(R.array.config_builtInDisplayIsRoundArray))
+ .thenReturn(mockArray);
+ when(mMockedResources.getIntArray(
+ com.android.internal.R.array.config_brightnessThresholdsOfPeakRefreshRate))
+ .thenReturn(new int[]{});
+ when(mMockedResources.getIntArray(
+ com.android.internal.R.array.config_ambientThresholdsOfPeakRefreshRate))
+ .thenReturn(new int[]{});
+ when(mMockedResources.getIntArray(
+ com.android.internal.R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
+ .thenReturn(new int[]{});
+ when(mMockedResources.getIntArray(
+ com.android.internal.R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
+ .thenReturn(new int[]{});
+ }
+
+ @After
+ public void tearDown() {
+ if (mMockitoSession != null) {
+ mMockitoSession.finishMocking();
+ }
+ }
+
+ /**
+ * Confirm that display is marked as private when it is listed in
+ * com.android.internal.R.array.config_localPrivateDisplayPorts.
+ */
+ @Test
+ public void testPrivateDisplay() throws Exception {
+ setUpDisplay(new FakeDisplay(PORT_A));
+ setUpDisplay(new FakeDisplay(PORT_B));
+ setUpDisplay(new FakeDisplay(PORT_C));
+ updateAvailableDisplays();
+
+ doReturn(new int[]{ PORT_B }).when(mMockedResources)
+ .getIntArray(com.android.internal.R.array.config_localPrivateDisplayPorts);
+ mAdapter.registerLocked();
+
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ // This should be public
+ assertDisplayPrivateFlag(mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(),
+ PORT_A, false);
+ // This should be private
+ assertDisplayPrivateFlag(mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(),
+ PORT_B, true);
+ // This should be public
+ assertDisplayPrivateFlag(mListener.addedDisplays.get(2).getDisplayDeviceInfoLocked(),
+ PORT_C, false);
+ }
+
+ @Test
+ public void testSupportedDisplayModesGetOverriddenWhenDisplayIsUpdated()
+ throws InterruptedException {
+ SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 0);
+ displayMode.supportedHdrTypes = new int[0];
+ FakeDisplay display = new FakeDisplay(PORT_A, new SurfaceControl.DisplayMode[]{displayMode},
+ 0, 0);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+ displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+ Display.Mode[] supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes;
+ Assert.assertEquals(1, supportedModes.length);
+ Assert.assertEquals(0, supportedModes[0].getSupportedHdrTypes().length);
+
+ displayMode.supportedHdrTypes = new int[]{3, 2};
+ display.dynamicInfo.supportedDisplayModes = new SurfaceControl.DisplayMode[]{displayMode};
+ setUpDisplay(display);
+ mInjector.getTransmitter().sendHotplug(display, true);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ displayDevice = mListener.changedDisplays.get(0);
+ displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+ supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes;
+
+ Assert.assertEquals(1, supportedModes.length);
+ assertArrayEquals(new int[]{2, 3}, supportedModes[0].getSupportedHdrTypes());
+ }
+
+ /**
+ * Confirm that all local displays are public when config_localPrivateDisplayPorts is empty.
+ */
+ @Test
+ public void testPublicDisplaysForNoConfigLocalPrivateDisplayPorts() throws Exception {
+ setUpDisplay(new FakeDisplay(PORT_A));
+ setUpDisplay(new FakeDisplay(PORT_C));
+ updateAvailableDisplays();
+
+ // config_localPrivateDisplayPorts is null
+ mAdapter.registerLocked();
+
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ // This should be public
+ assertDisplayPrivateFlag(mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(),
+ PORT_A, false);
+ // This should be public
+ assertDisplayPrivateFlag(mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(),
+ PORT_C, false);
+ }
+
+ private static void assertDisplayPrivateFlag(
+ DisplayDeviceInfo info, int expectedPort, boolean shouldBePrivate) {
+ final DisplayAddress.Physical address = (DisplayAddress.Physical) info.address;
+ assertNotNull(address);
+ assertEquals(expectedPort, address.getPort());
+ assertEquals(DISPLAY_MODEL, address.getModel());
+ assertEquals(shouldBePrivate, (info.flags & DisplayDeviceInfo.FLAG_PRIVATE) != 0);
+ }
+
+ /**
+ * Confirm that external display uses physical density.
+ */
+ @Test
+ public void testDpiValues() throws Exception {
+ // needs default one always
+ setUpDisplay(new FakeDisplay(PORT_A));
+ setUpDisplay(new FakeDisplay(PORT_B));
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertDisplayDpi(
+ mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(), PORT_A, 100, 100,
+ 16000);
+ assertDisplayDpi(
+ mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(), PORT_B, 100, 100,
+ 16000);
+ }
+
+ private static class DisplayModeWrapper {
+ public SurfaceControl.DisplayMode mode;
+ public float[] expectedAlternativeRefreshRates;
+
+ DisplayModeWrapper(SurfaceControl.DisplayMode mode,
+ float[] expectedAlternativeRefreshRates) {
+ this.mode = mode;
+ this.expectedAlternativeRefreshRates = expectedAlternativeRefreshRates;
+ }
+ }
+
+ /**
+ * Updates the <code>display</code> using the given <code>modes</code> and then checks if the
+ * <code>expectedAlternativeRefreshRates</code> are present for each of the
+ * <code>modes</code>.
+ */
+ private void testAlternativeRefreshRatesCommon(FakeDisplay display,
+ DisplayModeWrapper[] wrappedModes)
+ throws InterruptedException {
+ // Update the display.
+ SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[wrappedModes.length];
+ for (int i = 0; i < wrappedModes.length; i++) {
+ modes[i] = wrappedModes[i].mode;
+ }
+ display.dynamicInfo.supportedDisplayModes = modes;
+ setUpDisplay(display);
+ mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ assertTrue(mListener.traversalRequested);
+ assertThat(mListener.changedDisplays.size()).isGreaterThan(0);
+
+ // Verify the supported modes are updated accordingly.
+ DisplayDevice displayDevice =
+ mListener.changedDisplays.get(mListener.changedDisplays.size() - 1);
+ displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+ Display.Mode[] supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes;
+ assertThat(supportedModes.length).isEqualTo(modes.length);
+
+ for (int i = 0; i < wrappedModes.length; i++) {
+ assertModeIsSupported(supportedModes, modes[i],
+ wrappedModes[i].expectedAlternativeRefreshRates);
+ }
+ }
+
+ @Test
+ public void testAfterDisplayChange_AlternativeRefreshRatesAreUpdated() throws Exception {
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ testAlternativeRefreshRatesCommon(display, new DisplayModeWrapper[] {
+ new DisplayModeWrapper(
+ createFakeDisplayMode(0, 1920, 1080, 60f, 0), new float[]{24f, 50f}),
+ new DisplayModeWrapper(
+ createFakeDisplayMode(1, 1920, 1080, 50f, 0), new float[]{24f, 60f}),
+ new DisplayModeWrapper(
+ createFakeDisplayMode(2, 1920, 1080, 24f, 0), new float[]{50f, 60f}),
+ new DisplayModeWrapper(
+ createFakeDisplayMode(3, 3840, 2160, 60f, 0), new float[]{24f, 50f}),
+ new DisplayModeWrapper(
+ createFakeDisplayMode(4, 3840, 2160, 50f, 0), new float[]{24f, 60f}),
+ new DisplayModeWrapper(
+ createFakeDisplayMode(5, 3840, 2160, 24f, 0), new float[]{50f, 60f}),
+ });
+
+ testAlternativeRefreshRatesCommon(display, new DisplayModeWrapper[] {
+ new DisplayModeWrapper(
+ createFakeDisplayMode(0, 1920, 1080, 60f, 0), new float[]{50f}),
+ new DisplayModeWrapper(
+ createFakeDisplayMode(1, 1920, 1080, 50f, 0), new float[]{60f}),
+ new DisplayModeWrapper(
+ createFakeDisplayMode(2, 1920, 1080, 24f, 1), new float[0]),
+ new DisplayModeWrapper(
+ createFakeDisplayMode(3, 3840, 2160, 60f, 2), new float[0]),
+ new DisplayModeWrapper(
+ createFakeDisplayMode(4, 3840, 2160, 50f, 3), new float[]{24f}),
+ new DisplayModeWrapper(
+ createFakeDisplayMode(5, 3840, 2160, 24f, 3), new float[]{50f}),
+ });
+
+ testAlternativeRefreshRatesCommon(display, new DisplayModeWrapper[] {
+ new DisplayModeWrapper(
+ createFakeDisplayMode(0, 1920, 1080, 60f, 0), new float[0]),
+ new DisplayModeWrapper(
+ createFakeDisplayMode(1, 1920, 1080, 50f, 1), new float[0]),
+ new DisplayModeWrapper(
+ createFakeDisplayMode(2, 1920, 1080, 24f, 2), new float[0]),
+ new DisplayModeWrapper(
+ createFakeDisplayMode(3, 3840, 2160, 60f, 3), new float[0]),
+ new DisplayModeWrapper(
+ createFakeDisplayMode(4, 3840, 2160, 50f, 4), new float[0]),
+ new DisplayModeWrapper(
+ createFakeDisplayMode(5, 3840, 2160, 24f, 5), new float[0]),
+ });
+ }
+
+ @Test
+ public void testAfterDisplayChange_DefaultDisplayModeIsUpdated() throws Exception {
+ SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
+ SurfaceControl.DisplayMode[] modes =
+ new SurfaceControl.DisplayMode[]{displayMode};
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays).isEmpty();
+
+ DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
+ 0).getDisplayDeviceInfoLocked();
+
+ assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+ assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode);
+
+ Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+ assertThat(matches(defaultMode, displayMode)).isTrue();
+
+ // Set the display mode to an unsupported mode
+ SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 1920, 1080, 120f);
+ mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(
+ new Display.Mode(displayMode2.width, displayMode2.height,
+ displayMode2.refreshRate));
+ updateAvailableDisplays();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+ assertThat(matches(defaultMode, displayMode2)).isFalse();
+
+ // Change the display
+ modes = new SurfaceControl.DisplayMode[]{displayMode, displayMode2};
+ display.dynamicInfo.supportedDisplayModes = modes;
+ setUpDisplay(display);
+ mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertTrue(mListener.traversalRequested);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+ DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+ displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+ displayDeviceInfo = mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked();
+
+ assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+ assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode);
+ assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode2);
+
+ defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+ assertThat(matches(defaultMode, displayMode2)).isTrue();
+ }
+
+ @Test
+ public void testAfterDisplayChange_DisplayModesAreUpdated() throws Exception {
+ SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
+ SurfaceControl.DisplayMode[] modes =
+ new SurfaceControl.DisplayMode[]{displayMode};
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays).isEmpty();
+
+ DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
+ 0).getDisplayDeviceInfoLocked();
+
+ assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+ assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode);
+
+ Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+ assertThat(matches(defaultMode, displayMode)).isTrue();
+
+ Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+ assertThat(matches(activeMode, displayMode)).isTrue();
+
+ // Change the display
+ SurfaceControl.DisplayMode addedDisplayInfo = createFakeDisplayMode(1, 3840, 2160, 60f);
+ modes = new SurfaceControl.DisplayMode[]{displayMode, addedDisplayInfo};
+ display.dynamicInfo.supportedDisplayModes = modes;
+ display.dynamicInfo.activeDisplayModeId = 1;
+ setUpDisplay(display);
+ mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertTrue(mListener.traversalRequested);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+ DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+ displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+ displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+ assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+ assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode);
+ assertModeIsSupported(displayDeviceInfo.supportedModes, addedDisplayInfo);
+
+ activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+ assertThat(matches(activeMode, addedDisplayInfo)).isTrue();
+
+ defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+ assertThat(matches(defaultMode, addedDisplayInfo)).isTrue();
+ }
+
+ @Test
+ public void testAfterDisplayChange_ActiveModeIsUpdated() throws Exception {
+ SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{
+ createFakeDisplayMode(0, 1920, 1080, 60f),
+ createFakeDisplayMode(1, 1920, 1080, 50f)
+ };
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0, 60f);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays).isEmpty();
+
+ DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
+ .getDisplayDeviceInfoLocked();
+
+ Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+ assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
+
+ // Change the display
+ display.dynamicInfo.activeDisplayModeId = 1;
+ setUpDisplay(display);
+ mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertTrue(mListener.traversalRequested);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+ DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+ displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+ displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+ activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+ assertThat(activeMode.matches(1920, 1080, 50f)).isTrue();
+ }
+
+ @Test
+ public void testAfterDisplayChange_RenderFrameRateIsUpdated() throws Exception {
+ SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{
+ createFakeDisplayMode(0, 1920, 1080, 60f),
+ };
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0,
+ /* renderFrameRate */30f);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays).isEmpty();
+
+ DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
+ .getDisplayDeviceInfoLocked();
+
+ Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+ assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
+ assertEquals(Float.floatToIntBits(30f),
+ Float.floatToIntBits(displayDeviceInfo.renderFrameRate));
+
+ // Change the render frame rate
+ display.dynamicInfo.renderFrameRate = 60f;
+ setUpDisplay(display);
+ mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertTrue(mListener.traversalRequested);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+ DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+ displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+ displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+ activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+ assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
+ assertEquals(Float.floatToIntBits(60f),
+ Float.floatToIntBits(displayDeviceInfo.renderFrameRate));
+ }
+
+ @Test
+ public void testAfterDisplayChange_HdrCapabilitiesAreUpdated() throws Exception {
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ Display.HdrCapabilities initialHdrCapabilities = new Display.HdrCapabilities(new int[0],
+ 1000, 1000, 0);
+ display.dynamicInfo.hdrCapabilities = initialHdrCapabilities;
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays).isEmpty();
+
+ DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
+ 0).getDisplayDeviceInfoLocked();
+
+ assertThat(displayDeviceInfo.hdrCapabilities).isEqualTo(initialHdrCapabilities);
+
+ // Change the display
+ Display.HdrCapabilities changedHdrCapabilities = new Display.HdrCapabilities(
+ new int[Display.HdrCapabilities.HDR_TYPE_HDR10_PLUS], 1000, 1000, 0);
+ display.dynamicInfo.hdrCapabilities = changedHdrCapabilities;
+ setUpDisplay(display);
+ mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertTrue(mListener.traversalRequested);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+ DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+ displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+ displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+ assertThat(displayDeviceInfo.hdrCapabilities).isEqualTo(changedHdrCapabilities);
+ }
+
+ @Test
+ public void testAfterDisplayChange_AllmSupportIsUpdated() throws Exception {
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ display.dynamicInfo.autoLowLatencyModeSupported = true;
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays).isEmpty();
+
+ DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
+ .getDisplayDeviceInfoLocked();
+
+ assertThat(displayDeviceInfo.allmSupported).isTrue();
+
+ // Change the display
+ display.dynamicInfo.autoLowLatencyModeSupported = false;
+ setUpDisplay(display);
+ mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertTrue(mListener.traversalRequested);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+ DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+ displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+ displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+ assertThat(displayDeviceInfo.allmSupported).isFalse();
+ }
+
+ @Test
+ public void testAfterDisplayStateChanges_committedSetAfterState() throws Exception {
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+ // Turn off.
+ Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_OFF, 0,
+ 0);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+ mListener.changedDisplays.clear();
+ assertThat(displayDevice.getDisplayDeviceInfoLocked().state).isEqualTo(Display.STATE_OFF);
+ assertThat(displayDevice.getDisplayDeviceInfoLocked().committedState).isNotEqualTo(
+ Display.STATE_OFF);
+ verify(mSurfaceControlProxy, never()).setDisplayPowerMode(display.token, Display.STATE_OFF);
+
+ // Execute powerstate change.
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+
+ // Verify that committed triggered a new change event and is set correctly.
+ verify(mSurfaceControlProxy, never()).setDisplayPowerMode(display.token, Display.STATE_OFF);
+ // We expect at least 1 update for the state change, but
+ // could get a second update for the initial brightness change if a nits mapping
+ // is available
+ assertThat(mListener.changedDisplays.size()).isAnyOf(1, 2);
+ assertThat(displayDevice.getDisplayDeviceInfoLocked().state).isEqualTo(Display.STATE_OFF);
+ assertThat(displayDevice.getDisplayDeviceInfoLocked().committedState).isEqualTo(
+ Display.STATE_OFF);
+ }
+
+ @Test
+ public void testAfterDisplayChange_GameContentTypeSupportIsUpdated() throws Exception {
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ display.dynamicInfo.gameContentTypeSupported = true;
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays).isEmpty();
+
+ DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
+ .getDisplayDeviceInfoLocked();
+
+ assertThat(displayDeviceInfo.gameContentTypeSupported).isTrue();
+
+ // Change the display
+ display.dynamicInfo.gameContentTypeSupported = false;
+ setUpDisplay(display);
+ mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertTrue(mListener.traversalRequested);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+ DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+ displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+ displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+ assertThat(displayDeviceInfo.gameContentTypeSupported).isFalse();
+ }
+
+ @Test
+ public void testAfterDisplayChange_ColorModesAreUpdated() throws Exception {
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ final int[] initialColorModes = new int[]{Display.COLOR_MODE_BT709};
+ display.dynamicInfo.supportedColorModes = initialColorModes;
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays).isEmpty();
+
+ DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
+ .getDisplayDeviceInfoLocked();
+
+ assertThat(displayDeviceInfo.colorMode).isEqualTo(Display.COLOR_MODE_BT709);
+ assertThat(displayDeviceInfo.supportedColorModes).isEqualTo(initialColorModes);
+
+ // Change the display
+ final int[] changedColorModes = new int[]{Display.COLOR_MODE_DEFAULT};
+ display.dynamicInfo.supportedColorModes = changedColorModes;
+ setUpDisplay(display);
+ mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertTrue(mListener.traversalRequested);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+ DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+ displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+ displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+ assertThat(displayDeviceInfo.colorMode).isEqualTo(Display.COLOR_MODE_DEFAULT);
+ assertThat(displayDeviceInfo.supportedColorModes).isEqualTo(changedColorModes);
+ }
+
+ @Test
+ public void testDisplayChange_withStaleDesiredDisplayModeSpecs() throws Exception {
+ SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{
+ createFakeDisplayMode(0, 1920, 1080, 60f),
+ createFakeDisplayMode(1, 1920, 1080, 50f)
+ };
+ final int activeMode = 0;
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, activeMode, 60f);
+ display.desiredDisplayModeSpecs.defaultMode = 1;
+
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+ int baseModeId = Arrays.stream(displayDevice.getDisplayDeviceInfoLocked().supportedModes)
+ .filter(mode -> mode.getRefreshRate() == 60f)
+ .findFirst()
+ .get()
+ .getModeId();
+
+ displayDevice.setDesiredDisplayModeSpecsLocked(
+ new DisplayModeDirector.DesiredDisplayModeSpecs(
+ /*baseModeId*/ baseModeId,
+ /*allowGroupSwitching*/ false,
+ REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
+ ));
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ verify(mSurfaceControlProxy).setDesiredDisplayModeSpecs(display.token,
+ new SurfaceControl.DesiredDisplayModeSpecs(
+ /* baseModeId */ 0,
+ /* allowGroupSwitching */ false,
+ REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
+ ));
+
+ // Change the display
+ display.dynamicInfo.supportedDisplayModes = new SurfaceControl.DisplayMode[]{
+ createFakeDisplayMode(2, 1920, 1080, 60f)
+ };
+ display.dynamicInfo.activeDisplayModeId = 2;
+ // SurfaceFlinger can return a stale defaultMode. Make sure this doesn't crash.
+ display.desiredDisplayModeSpecs.defaultMode = 1;
+
+ setUpDisplay(display);
+ mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertTrue(mListener.traversalRequested);
+
+ displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+
+ baseModeId = displayDevice.getDisplayDeviceInfoLocked().supportedModes[0].getModeId();
+
+ // The traversal request will call setDesiredDisplayModeSpecsLocked on the display device
+ displayDevice.setDesiredDisplayModeSpecsLocked(
+ new DisplayModeDirector.DesiredDisplayModeSpecs(
+ /*baseModeId*/ baseModeId,
+ /*allowGroupSwitching*/ false,
+ REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
+ ));
+
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ // Verify that this will reapply the desired modes.
+ verify(mSurfaceControlProxy).setDesiredDisplayModeSpecs(display.token,
+ new SurfaceControl.DesiredDisplayModeSpecs(
+ /* baseModeId */ 2,
+ /* allowGroupSwitching */ false,
+ REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
+ ));
+ }
+
+ @Test
+ public void testBacklightAdapter_withSurfaceControlSupport() {
+ final Binder displayToken = new Binder();
+
+ when(mSurfaceControlProxy.getDisplayBrightnessSupport(displayToken)).thenReturn(true);
+
+ // Test as default display
+ BacklightAdapter ba = new BacklightAdapter(displayToken, true /*isDefault*/,
+ mSurfaceControlProxy);
+ ba.setBacklight(0.514f, 100f, 0.614f, 500f);
+ verify(mSurfaceControlProxy).setDisplayBrightness(displayToken, 0.514f, 100f, 0.614f, 500f);
+
+ // Test as not default display
+ BacklightAdapter ba2 = new BacklightAdapter(displayToken, false /*isDefault*/,
+ mSurfaceControlProxy);
+ ba2.setBacklight(0.323f, 101f, 0.723f, 601f);
+ verify(mSurfaceControlProxy).setDisplayBrightness(displayToken, 0.323f, 101f, 0.723f, 601f);
+ }
+
+ @Test
+ public void testBacklightAdapter_withoutSourceControlSupport_defaultDisplay() {
+ final Binder displayToken = new Binder();
+ when(mSurfaceControlProxy.getDisplayBrightnessSupport(displayToken)).thenReturn(false);
+ doReturn(mMockedBacklight).when(mMockedLightsManager)
+ .getLight(LightsManager.LIGHT_ID_BACKLIGHT);
+
+ BacklightAdapter ba = new BacklightAdapter(displayToken, true /*isDefault*/,
+ mSurfaceControlProxy);
+ ba.setBacklight(1f, 1f, 0.123f, 1f);
+ verify(mMockedBacklight).setBrightness(0.123f);
+ }
+
+ @Test
+ public void testBacklightAdapter_withoutSourceControlSupport_nonDefaultDisplay() {
+ final Binder displayToken = new Binder();
+ when(mSurfaceControlProxy.getDisplayBrightnessSupport(displayToken)).thenReturn(false);
+ doReturn(mMockedBacklight).when(mMockedLightsManager)
+ .getLight(LightsManager.LIGHT_ID_BACKLIGHT);
+
+ BacklightAdapter ba = new BacklightAdapter(displayToken, false /*isDefault*/,
+ mSurfaceControlProxy);
+ ba.setBacklight(0.456f, 1f, 1f, 1f);
+
+ // Adapter does not forward any brightness in this case.
+ verify(mMockedBacklight, never()).setBrightness(anyFloat());
+ }
+
+ @Test
+ public void testGetSystemPreferredDisplayMode() throws Exception {
+ SurfaceControl.DisplayMode displayMode1 = createFakeDisplayMode(0, 1920, 1080, 60f);
+ // system preferred mode
+ SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 3840, 2160, 60f);
+ // user preferred mode
+ SurfaceControl.DisplayMode displayMode3 = createFakeDisplayMode(2, 1920, 1080, 30f);
+
+ SurfaceControl.DisplayMode[] modes =
+ new SurfaceControl.DisplayMode[]{displayMode1, displayMode2, displayMode3};
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, 1);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays).isEmpty();
+
+ DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
+ 0).getDisplayDeviceInfoLocked();
+ assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+ Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+ assertThat(matches(defaultMode, displayMode1)).isTrue();
+
+ // Set the user preferred display mode
+ mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(
+ new Display.Mode(
+ displayMode3.width, displayMode3.height, displayMode3.refreshRate));
+ updateAvailableDisplays();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ displayDeviceInfo = mListener.addedDisplays.get(
+ 0).getDisplayDeviceInfoLocked();
+ defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+ assertThat(matches(defaultMode, displayMode3)).isTrue();
+
+ // clear the user preferred mode
+ mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(null);
+ updateAvailableDisplays();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ displayDeviceInfo = mListener.addedDisplays.get(
+ 0).getDisplayDeviceInfoLocked();
+ defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+ assertThat(matches(defaultMode, displayMode2)).isTrue();
+
+ // Change the display and add new system preferred mode
+ SurfaceControl.DisplayMode addedDisplayInfo = createFakeDisplayMode(3, 2340, 1080, 20f);
+ modes = new SurfaceControl.DisplayMode[]{
+ displayMode1, displayMode2, displayMode3, addedDisplayInfo};
+ display.dynamicInfo.supportedDisplayModes = modes;
+ display.dynamicInfo.preferredBootDisplayMode = 3;
+ setUpDisplay(display);
+ mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertTrue(mListener.traversalRequested);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays.size()).isEqualTo(3);
+
+ DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+ displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+ displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+ assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+ assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode1);
+ assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode2);
+ assertModeIsSupported(displayDeviceInfo.supportedModes, addedDisplayInfo);
+
+ assertThat(matches(displayDevice.getSystemPreferredDisplayModeLocked(), addedDisplayInfo))
+ .isTrue();
+ }
+
+ @Test
+ public void testHdrSdrRatio_notifiesOnChange() throws Exception {
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+ // Turn on / initialize
+ assumeTrue(displayDevice.getDisplayDeviceConfig().hasSdrToHdrRatioSpline());
+ Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+ 0);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ mListener.changedDisplays.clear();
+
+ assertEquals(1.0f, displayDevice.getDisplayDeviceInfoLocked().hdrSdrRatio, 0.001f);
+
+ // HDR time!
+ Runnable goHdrRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 1f,
+ 0);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ // Display state didn't change, no listeners should have happened
+ assertThat(mListener.changedDisplays.size()).isEqualTo(0);
+
+ // Execute hdr change.
+ goHdrRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ // Display state didn't change, expect to only get the HDR/SDR ratio change notification
+ assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+ final float expectedRatio = DISPLAY_RANGE_NITS[1] / DISPLAY_RANGE_NITS[0];
+ assertEquals(expectedRatio, displayDevice.getDisplayDeviceInfoLocked().hdrSdrRatio,
+ 0.001f);
+ }
+
+ @Test
+ public void test_getDisplayDeviceInfoLocked_internalDisplay_usesCutoutAndCorners()
+ throws Exception {
+ setupCutoutAndRoundedCorners();
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ display.info.isInternal = true;
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+ // Turn on / initialize
+ Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+ 0);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ mListener.changedDisplays.clear();
+
+ DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+
+ assertThat(info.displayCutout).isNotNull();
+ assertThat(info.displayCutout.getBoundingRectTop()).isEqualTo(new Rect(507, 33, 573, 99));
+ assertThat(info.roundedCorners).isNotNull();
+ assertThat(info.roundedCorners.getRoundedCorner(0).getRadius()).isEqualTo(5);
+ }
+
+ @Test public void test_getDisplayDeviceInfoLocked_externalDisplay_doesNotUseCutoutOrCorners()
+ throws Exception {
+ setupCutoutAndRoundedCorners();
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ display.info.isInternal = false;
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+ // Turn on / initialize
+ Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+ 0);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ mListener.changedDisplays.clear();
+
+ DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+
+ assertThat(info.displayCutout).isNull();
+ assertThat(info.roundedCorners).isNull();
+ }
+
+ private void setupCutoutAndRoundedCorners() {
+ String sampleCutout = "M 507,66\n"
+ + "a 33,33 0 1 0 66,0 33,33 0 1 0 -66,0\n"
+ + "Z\n"
+ + "@left\n";
+ // Setup some default cutout
+ when(mMockedResources.getString(
+ com.android.internal.R.string.config_mainBuiltInDisplayCutout))
+ .thenReturn(sampleCutout);
+ when(mMockedResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.rounded_corner_radius)).thenReturn(5);
+ }
+
+ private void assertDisplayDpi(DisplayDeviceInfo info, int expectedPort,
+ float expectedXdpi,
+ float expectedYDpi,
+ int expectedDensityDpi) {
+ final DisplayAddress.Physical physical = (DisplayAddress.Physical) info.address;
+ assertNotNull(physical);
+ assertEquals(expectedPort, physical.getPort());
+ assertEquals(expectedXdpi, info.xDpi, 0.01);
+ assertEquals(expectedYDpi, info.yDpi, 0.01);
+ assertEquals(expectedDensityDpi, info.densityDpi);
+ }
+
+ private Display.Mode getModeById(DisplayDeviceInfo displayDeviceInfo, int modeId) {
+ return Arrays.stream(displayDeviceInfo.supportedModes)
+ .filter(mode -> mode.getModeId() == modeId)
+ .findFirst()
+ .get();
+ }
+
+ private void assertModeIsSupported(Display.Mode[] supportedModes,
+ SurfaceControl.DisplayMode mode) {
+ assertThat(Arrays.stream(supportedModes).anyMatch(
+ x -> x.matches(mode.width, mode.height, mode.refreshRate))).isTrue();
+ }
+
+ private void assertModeIsSupported(Display.Mode[] supportedModes,
+ SurfaceControl.DisplayMode mode, float[] alternativeRefreshRates) {
+ float[] sortedAlternativeRates =
+ Arrays.copyOf(alternativeRefreshRates, alternativeRefreshRates.length);
+ Arrays.sort(sortedAlternativeRates);
+
+ String message = "Expected " + mode + " with alternativeRefreshRates = "
+ + Arrays.toString(alternativeRefreshRates) + " to be in list of supported modes = "
+ + Arrays.toString(supportedModes);
+ Truth.assertWithMessage(message)
+ .that(Arrays.stream(supportedModes)
+ .anyMatch(x -> x.matches(mode.width, mode.height, mode.refreshRate)
+ && Arrays.equals(x.getAlternativeRefreshRates(), sortedAlternativeRates)))
+ .isTrue();
+ }
+
+
+ private static class FakeDisplay {
+ public final DisplayAddress.Physical address;
+ public final IBinder token = new Binder();
+ public final SurfaceControl.StaticDisplayInfo info;
+ public SurfaceControl.DynamicDisplayInfo dynamicInfo =
+ new SurfaceControl.DynamicDisplayInfo();
+ {
+ dynamicInfo.supportedColorModes = new int[]{ Display.COLOR_MODE_DEFAULT };
+ dynamicInfo.hdrCapabilities = new Display.HdrCapabilities(new int[0],
+ 1000, 1000, 0);
+ }
+
+ public SurfaceControl.DesiredDisplayModeSpecs desiredDisplayModeSpecs =
+ new SurfaceControl.DesiredDisplayModeSpecs(
+ /* defaultMode */ 0,
+ /* allowGroupSwitching */ false,
+ REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
+ );
+
+ private FakeDisplay(int port) {
+ address = createDisplayAddress(port);
+ info = createFakeDisplayInfo();
+ dynamicInfo.supportedDisplayModes = new SurfaceControl.DisplayMode[]{
+ createFakeDisplayMode(0, 800, 600, 60f)
+ };
+ dynamicInfo.activeDisplayModeId = 0;
+ }
+
+ private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode,
+ float renderFrameRate) {
+ address = createDisplayAddress(port);
+ info = createFakeDisplayInfo();
+ dynamicInfo.supportedDisplayModes = modes;
+ dynamicInfo.activeDisplayModeId = activeMode;
+ dynamicInfo.renderFrameRate = renderFrameRate;
+ }
+
+ private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode,
+ int preferredMode) {
+ address = createDisplayAddress(port);
+ info = createFakeDisplayInfo();
+ dynamicInfo.supportedDisplayModes = modes;
+ dynamicInfo.activeDisplayModeId = activeMode;
+ dynamicInfo.preferredBootDisplayMode = preferredMode;
+ }
+
+ }
+
+ private void setUpDisplay(FakeDisplay display) {
+ mAddresses.add(display.address);
+ when(mSurfaceControlProxy.getPhysicalDisplayToken(display.address.getPhysicalDisplayId()))
+ .thenReturn(display.token);
+ when(mSurfaceControlProxy.getStaticDisplayInfo(display.address.getPhysicalDisplayId()))
+ .thenReturn(display.info);
+ when(mSurfaceControlProxy.getDynamicDisplayInfo(display.address.getPhysicalDisplayId()))
+ .thenReturn(display.dynamicInfo);
+ when(mSurfaceControlProxy.getDesiredDisplayModeSpecs(display.token))
+ .thenReturn(display.desiredDisplayModeSpecs);
+ }
+
+ private void updateAvailableDisplays() {
+ long[] ids = new long[mAddresses.size()];
+ int i = 0;
+ for (DisplayAddress.Physical address : mAddresses) {
+ ids[i] = address.getPhysicalDisplayId();
+ i++;
+ }
+ when(mSurfaceControlProxy.getPhysicalDisplayIds()).thenReturn(ids);
+ }
+
+ private static DisplayAddress.Physical createDisplayAddress(int port) {
+ return DisplayAddress.fromPortAndModel(port, DISPLAY_MODEL);
+ }
+
+ private static SurfaceControl.StaticDisplayInfo createFakeDisplayInfo() {
+ final SurfaceControl.StaticDisplayInfo info = new SurfaceControl.StaticDisplayInfo();
+ info.density = 100;
+ return info;
+ }
+
+ private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
+ float refreshRate) {
+ return createFakeDisplayMode(id, width, height, refreshRate, /* group */ 0);
+ }
+
+ private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
+ float refreshRate, int group) {
+ final SurfaceControl.DisplayMode mode = new SurfaceControl.DisplayMode();
+ mode.id = id;
+ mode.width = width;
+ mode.height = height;
+ mode.refreshRate = refreshRate;
+ mode.xDpi = 100;
+ mode.yDpi = 100;
+ mode.group = group;
+ mode.supportedHdrTypes = HDR_TYPES;
+ return mode;
+ }
+
+ private static void waitForHandlerToComplete(Handler handler, long waitTimeMs)
+ throws InterruptedException {
+ final CountDownLatch fence = new CountDownLatch(1);
+ handler.post(fence::countDown);
+ assertTrue(fence.await(waitTimeMs, TimeUnit.MILLISECONDS));
+ }
+
+ private class HotplugTransmitter {
+ private final Handler mHandler;
+ private final LocalDisplayAdapter.DisplayEventListener mListener;
+
+ HotplugTransmitter(Looper looper, LocalDisplayAdapter.DisplayEventListener listener) {
+ mHandler = new Handler(looper);
+ mListener = listener;
+ }
+
+ public void sendHotplug(FakeDisplay display, boolean connected)
+ throws InterruptedException {
+ mHandler.post(() -> mListener.onHotplug(/* timestampNanos = */ 0,
+ display.address.getPhysicalDisplayId(), connected));
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ }
+ }
+
+ private class Injector extends LocalDisplayAdapter.Injector {
+ private HotplugTransmitter mTransmitter;
+ @Override
+ public void setDisplayEventListenerLocked(Looper looper,
+ LocalDisplayAdapter.DisplayEventListener listener) {
+ mTransmitter = new HotplugTransmitter(looper, listener);
+ }
+
+ public HotplugTransmitter getTransmitter() {
+ return mTransmitter;
+ }
+
+ @Override
+ public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
+ return mSurfaceControlProxy;
+ }
+
+ // Instead of using DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay)
+ // we should use DisplayDeviceConfig.create(context, isFirstDisplay) for the test to ensure
+ // that real device DisplayDeviceConfig is not loaded for FakeDisplay and we are getting
+ // consistent behaviour. Please also note that context passed to this method, is
+ // mMockContext and values will be loaded from mMockResources.
+ @Override
+ public DisplayDeviceConfig createDisplayDeviceConfig(Context context,
+ long physicalDisplayId, boolean isFirstDisplay) {
+ return DisplayDeviceConfig.create(context, isFirstDisplay);
+ }
+ }
+
+ private class TestListener implements DisplayAdapter.Listener {
+ public ArrayList<DisplayDevice> addedDisplays = new ArrayList<>();
+ public ArrayList<DisplayDevice> changedDisplays = new ArrayList<>();
+ public boolean traversalRequested = false;
+
+ @Override
+ public void onDisplayDeviceEvent(DisplayDevice device, int event) {
+ if (event == DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED) {
+ addedDisplays.add(device);
+ } else if (event == DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED) {
+ changedDisplays.add(device);
+ }
+ }
+
+ @Override
+ public void onTraversalRequested() {
+ traversalRequested = true;
+ }
+ }
+
+ private TypedArray createFloatTypedArray(float[] vals) {
+ TypedArray mockArray = mock(TypedArray.class);
+ when(mockArray.length()).thenAnswer(invocation -> {
+ return vals.length;
+ });
+ when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> {
+ final float def = (float) invocation.getArguments()[1];
+ if (vals == null) {
+ return def;
+ }
+ int idx = (int) invocation.getArguments()[0];
+ if (idx >= 0 && idx < vals.length) {
+ return vals[idx];
+ } else {
+ return def;
+ }
+ });
+ return mockArray;
+ }
+
+ private boolean matches(Display.Mode a, SurfaceControl.DisplayMode b) {
+ return a.getPhysicalWidth() == b.width && a.getPhysicalHeight() == b.height
+ && Float.floatToIntBits(a.getRefreshRate()) == Float.floatToIntBits(b.refreshRate);
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
new file mode 100644
index 0000000..c23d4b1
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2022 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.hardware.display.DisplayManagerInternal;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.Callable;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class WakelockControllerTest {
+ private static final int DISPLAY_ID = 1;
+
+ @Mock
+ private DisplayManagerInternal.DisplayPowerCallbacks mDisplayPowerCallbacks;
+
+ private WakelockController mWakelockController;
+
+ @Before
+ public void before() {
+ MockitoAnnotations.initMocks(this);
+ mWakelockController = new WakelockController(DISPLAY_ID, mDisplayPowerCallbacks);
+ }
+
+ @Test
+ public void validateSuspendBlockerIdsAreExpected() {
+ assertEquals(mWakelockController.getSuspendBlockerUnfinishedBusinessId(),
+ "[" + DISPLAY_ID + "]unfinished business");
+ assertEquals(mWakelockController.getSuspendBlockerOnStateChangedId(),
+ "[" + DISPLAY_ID + "]on state changed");
+ assertEquals(mWakelockController.getSuspendBlockerProxPositiveId(),
+ "[" + DISPLAY_ID + "]prox positive");
+ assertEquals(mWakelockController.getSuspendBlockerProxNegativeId(),
+ "[" + DISPLAY_ID + "]prox negative");
+ assertEquals(mWakelockController.getSuspendBlockerProxDebounceId(),
+ "[" + DISPLAY_ID + "]prox debounce");
+ }
+
+ @Test
+ public void acquireStateChangedSuspendBlockerAcquiresIfNotAcquired() throws Exception {
+ // Acquire
+ verifyWakelockAcquisitionAndReaquisition(WakelockController.WAKE_LOCK_STATE_CHANGED,
+ () -> mWakelockController.isOnStateChangedPending());
+
+ // Verify acquire happened only once
+ verify(mDisplayPowerCallbacks, times(1))
+ .acquireSuspendBlocker(mWakelockController.getSuspendBlockerOnStateChangedId());
+
+ // Release
+ verifyWakelockReleaseAndRerelease(WakelockController.WAKE_LOCK_STATE_CHANGED,
+ () -> mWakelockController.isOnStateChangedPending());
+
+ // Verify release happened only once
+ verify(mDisplayPowerCallbacks, times(1))
+ .releaseSuspendBlocker(mWakelockController.getSuspendBlockerOnStateChangedId());
+ }
+
+ @Test
+ public void acquireUnfinishedBusinessSuspendBlockerAcquiresIfNotAcquired() throws Exception {
+ // Acquire
+ verifyWakelockAcquisitionAndReaquisition(WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS,
+ () -> mWakelockController.hasUnfinishedBusiness());
+
+ // Verify acquire happened only once
+ verify(mDisplayPowerCallbacks, times(1))
+ .acquireSuspendBlocker(mWakelockController.getSuspendBlockerUnfinishedBusinessId());
+
+ // Release
+ verifyWakelockReleaseAndRerelease(WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS,
+ () -> mWakelockController.hasUnfinishedBusiness());
+
+ // Verify release happened only once
+ verify(mDisplayPowerCallbacks, times(1))
+ .releaseSuspendBlocker(mWakelockController.getSuspendBlockerUnfinishedBusinessId());
+ }
+
+ @Test
+ public void acquireProxPositiveSuspendBlockerAcquiresIfNotAcquired() throws Exception {
+ // Acquire
+ verifyWakelockAcquisitionAndReaquisition(WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE,
+ () -> mWakelockController.isProximityPositiveAcquired());
+
+ // Verify acquire happened only once
+ verify(mDisplayPowerCallbacks, times(1))
+ .acquireSuspendBlocker(mWakelockController.getSuspendBlockerProxPositiveId());
+
+ // Release
+ verifyWakelockReleaseAndRerelease(WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE,
+ () -> mWakelockController.isProximityPositiveAcquired());
+
+ // Verify release happened only once
+ verify(mDisplayPowerCallbacks, times(1))
+ .releaseSuspendBlocker(mWakelockController.getSuspendBlockerProxPositiveId());
+ }
+
+ @Test
+ public void acquireProxNegativeSuspendBlockerAcquiresIfNotAcquired() throws Exception {
+ // Acquire
+ verifyWakelockAcquisitionAndReaquisition(WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE,
+ () -> mWakelockController.isProximityNegativeAcquired());
+
+ // Verify acquire happened only once
+ verify(mDisplayPowerCallbacks, times(1))
+ .acquireSuspendBlocker(mWakelockController.getSuspendBlockerProxNegativeId());
+
+ // Release
+ verifyWakelockReleaseAndRerelease(WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE,
+ () -> mWakelockController.isProximityNegativeAcquired());
+
+ // Verify release happened only once
+ verify(mDisplayPowerCallbacks, times(1))
+ .releaseSuspendBlocker(mWakelockController.getSuspendBlockerProxNegativeId());
+ }
+
+ @Test
+ public void acquireProxDebounceSuspendBlockerAcquiresIfNotAcquired() throws Exception {
+ // Acquire the suspend blocker
+ verifyWakelockAcquisitionAndReaquisition(WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE,
+ () -> mWakelockController.hasProximitySensorDebounced());
+
+ // Verify acquire happened only once
+ verify(mDisplayPowerCallbacks, times(1))
+ .acquireSuspendBlocker(mWakelockController.getSuspendBlockerProxDebounceId());
+
+ // Release the suspend blocker
+ verifyWakelockReleaseAndRerelease(WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE,
+ () -> mWakelockController.hasProximitySensorDebounced());
+
+ // Verify suspend blocker was released only once
+ verify(mDisplayPowerCallbacks, times(1))
+ .releaseSuspendBlocker(mWakelockController.getSuspendBlockerProxDebounceId());
+ }
+
+ @Test
+ public void proximityPositiveRunnableWorksAsExpected() {
+ // Acquire the suspend blocker twice
+ assertTrue(mWakelockController.acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE));
+
+ // Execute the runnable
+ Runnable proximityPositiveRunnable = mWakelockController.getOnProximityPositiveRunnable();
+ proximityPositiveRunnable.run();
+
+ // Validate one suspend blocker was released
+ assertFalse(mWakelockController.isProximityPositiveAcquired());
+ verify(mDisplayPowerCallbacks).onProximityPositive();
+ verify(mDisplayPowerCallbacks).releaseSuspendBlocker(
+ mWakelockController.getSuspendBlockerProxPositiveId());
+ }
+
+ @Test
+ public void proximityPositiveRunnableDoesNothingIfNotAcquired() {
+ // Execute the runnable
+ Runnable proximityPositiveRunnable = mWakelockController.getOnProximityPositiveRunnable();
+ proximityPositiveRunnable.run();
+
+ // Validate one suspend blocker was released
+ assertFalse(mWakelockController.isProximityPositiveAcquired());
+ verifyZeroInteractions(mDisplayPowerCallbacks);
+ }
+
+ @Test
+ public void proximityNegativeRunnableWorksAsExpected() {
+ // Acquire the suspend blocker twice
+ assertTrue(mWakelockController.acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE));
+
+ // Execute the runnable
+ Runnable proximityNegativeRunnable = mWakelockController.getOnProximityNegativeRunnable();
+ proximityNegativeRunnable.run();
+
+ // Validate one suspend blocker was released
+ assertFalse(mWakelockController.isProximityNegativeAcquired());
+ verify(mDisplayPowerCallbacks).onProximityNegative();
+ verify(mDisplayPowerCallbacks).releaseSuspendBlocker(
+ mWakelockController.getSuspendBlockerProxNegativeId());
+ }
+
+ @Test
+ public void proximityNegativeRunnableDoesNothingIfNotAcquired() {
+ // Execute the runnable
+ Runnable proximityNegativeRunnable = mWakelockController.getOnProximityNegativeRunnable();
+ proximityNegativeRunnable.run();
+
+ // Validate one suspend blocker was released
+ assertFalse(mWakelockController.isProximityNegativeAcquired());
+ verifyZeroInteractions(mDisplayPowerCallbacks);
+ }
+
+ @Test
+ public void onStateChangeRunnableWorksAsExpected() {
+ // Acquire the suspend blocker twice
+ assertTrue(mWakelockController.acquireWakelock(WakelockController.WAKE_LOCK_STATE_CHANGED));
+
+ // Execute the runnable
+ Runnable stateChangeRunnable = mWakelockController.getOnStateChangedRunnable();
+ stateChangeRunnable.run();
+
+ // Validate one suspend blocker was released
+ assertFalse(mWakelockController.isOnStateChangedPending());
+ verify(mDisplayPowerCallbacks).onStateChanged();
+ verify(mDisplayPowerCallbacks).releaseSuspendBlocker(
+ mWakelockController.getSuspendBlockerOnStateChangedId());
+ }
+
+ @Test
+ public void onStateChangeRunnableDoesNothingIfNotAcquired() {
+ // Execute the runnable
+ Runnable stateChangeRunnable = mWakelockController.getOnStateChangedRunnable();
+ stateChangeRunnable.run();
+
+ // Validate one suspend blocker was released
+ assertFalse(mWakelockController.isOnStateChangedPending());
+ verifyZeroInteractions(mDisplayPowerCallbacks);
+ }
+
+ @Test
+ public void testReleaseAll() throws Exception {
+ // Use WAKE_LOCK_MAX to verify it has been correctly set and used in releaseAll().
+ verifyWakelockAcquisition(WakelockController.WAKE_LOCK_MAX,
+ () -> mWakelockController.hasUnfinishedBusiness());
+ mWakelockController.releaseAll();
+ assertFalse(mWakelockController.hasUnfinishedBusiness());
+ }
+
+ private void verifyWakelockAcquisitionAndReaquisition(int wakelockId,
+ Callable<Boolean> isWakelockAcquiredCallable)
+ throws Exception {
+ verifyWakelockAcquisition(wakelockId, isWakelockAcquiredCallable);
+ verifyWakelockReacquisition(wakelockId, isWakelockAcquiredCallable);
+ }
+
+ private void verifyWakelockReleaseAndRerelease(int wakelockId,
+ Callable<Boolean> isWakelockAcquiredCallable)
+ throws Exception {
+ verifyWakelockRelease(wakelockId, isWakelockAcquiredCallable);
+ verifyWakelockRerelease(wakelockId, isWakelockAcquiredCallable);
+ }
+
+ private void verifyWakelockAcquisition(int wakelockId,
+ Callable<Boolean> isWakelockAcquiredCallable)
+ throws Exception {
+ assertTrue(mWakelockController.acquireWakelock(wakelockId));
+ assertTrue(isWakelockAcquiredCallable.call());
+ }
+
+ private void verifyWakelockReacquisition(int wakelockId,
+ Callable<Boolean> isWakelockAcquiredCallable)
+ throws Exception {
+ assertFalse(mWakelockController.acquireWakelock(wakelockId));
+ assertTrue(isWakelockAcquiredCallable.call());
+ }
+
+ private void verifyWakelockRelease(int wakelockId, Callable<Boolean> isWakelockAcquiredCallable)
+ throws Exception {
+ assertTrue(mWakelockController.releaseWakelock(wakelockId));
+ assertFalse(isWakelockAcquiredCallable.call());
+ }
+
+ private void verifyWakelockRerelease(int wakelockId,
+ Callable<Boolean> isWakelockAcquiredCallable)
+ throws Exception {
+ assertFalse(mWakelockController.releaseWakelock(wakelockId));
+ assertFalse(isWakelockAcquiredCallable.call());
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
new file mode 100644
index 0000000..a785300
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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.color;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.server.display.color.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
+import static com.android.server.display.color.DisplayTransformManager.PERSISTENT_PROPERTY_COMPOSITION_COLOR_MODE;
+import static com.android.server.display.color.DisplayTransformManager.PERSISTENT_PROPERTY_DISPLAY_COLOR;
+import static com.android.server.display.color.DisplayTransformManager.PERSISTENT_PROPERTY_SATURATION;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+
+import android.hardware.display.ColorDisplayManager;
+import android.os.SystemProperties;
+import android.view.Display;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+
+import java.util.HashMap;
+
+@RunWith(AndroidJUnit4.class)
+public class DisplayTransformManagerTest {
+
+ private MockitoSession mSession;
+ private DisplayTransformManager mDtm;
+ private float[] mNightDisplayMatrix;
+ private HashMap<String, String> mSystemProperties;
+
+ @Before
+ public void setUp() {
+ mDtm = new DisplayTransformManager();
+ mNightDisplayMatrix = mDtm.getColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY);
+
+ mSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .spyStatic(SystemProperties.class)
+ .startMocking();
+ mSystemProperties = new HashMap<>();
+
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ mSystemProperties.put(invocationOnMock.getArgument(0),
+ invocationOnMock.getArgument(1));
+ return null;
+ }
+ ).when(() -> SystemProperties.set(anyString(), any()));
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mSession.finishMocking();
+ mSystemProperties.clear();
+ }
+
+ @Test
+ public void setColorMode_natural() {
+ mDtm.setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL, mNightDisplayMatrix, -1);
+ assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_DISPLAY_COLOR))
+ .isEqualTo("0" /* managed */);
+ assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_SATURATION))
+ .isEqualTo("1.0" /* natural */);
+ }
+
+ @Test
+ public void setColorMode_boosted() {
+ mDtm.setColorMode(ColorDisplayManager.COLOR_MODE_BOOSTED, mNightDisplayMatrix, -1);
+
+ assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_DISPLAY_COLOR))
+ .isEqualTo("0" /* managed */);
+ assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_SATURATION))
+ .isEqualTo("1.1" /* boosted */);
+ }
+
+ @Test
+ public void setColorMode_saturated() {
+ mDtm.setColorMode(ColorDisplayManager.COLOR_MODE_SATURATED, mNightDisplayMatrix, -1);
+ assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_DISPLAY_COLOR))
+ .isEqualTo("1" /* unmanaged */);
+ assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_SATURATION))
+ .isEqualTo("1.0" /* natural */);
+ }
+
+ @Test
+ public void setColorMode_automatic() {
+ mDtm.setColorMode(ColorDisplayManager.COLOR_MODE_AUTOMATIC, mNightDisplayMatrix, -1);
+ assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_DISPLAY_COLOR))
+ .isEqualTo("2" /* enhanced */);
+ assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_SATURATION))
+ .isEqualTo("1.0" /* natural */);
+ }
+
+ @Test
+ public void setColorMode_vendor() {
+ mDtm.setColorMode(0x100, mNightDisplayMatrix, -1);
+ assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_DISPLAY_COLOR))
+ .isEqualTo(Integer.toString(0x100) /* pass-through */);
+ assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_SATURATION))
+ .isEqualTo("1.0" /* natural */);
+ }
+
+ @Test
+ public void setColorMode_outOfBounds() {
+ mDtm.setColorMode(0x50, mNightDisplayMatrix, -1);
+ assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_DISPLAY_COLOR))
+ .isEqualTo(null);
+ assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_SATURATION))
+ .isEqualTo(null);
+ }
+
+ @Test
+ public void setColorMode_withoutColorSpace() {
+ String magicPropertyValue = "magic";
+
+ // Start with a known state, which we expect to remain unmodified
+ SystemProperties.set(PERSISTENT_PROPERTY_COMPOSITION_COLOR_MODE, magicPropertyValue);
+
+ mDtm.setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL, mNightDisplayMatrix,
+ Display.COLOR_MODE_INVALID);
+ assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_COMPOSITION_COLOR_MODE))
+ .isEqualTo(magicPropertyValue);
+ }
+
+ @Test
+ public void setColorMode_withColorSpace() {
+ String magicPropertyValue = "magic";
+ int testPropertyValue = Display.COLOR_MODE_SRGB;
+
+ // Start with a known state, which we expect to get modified
+ SystemProperties.set(PERSISTENT_PROPERTY_COMPOSITION_COLOR_MODE, magicPropertyValue);
+
+ mDtm.setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL, mNightDisplayMatrix,
+ testPropertyValue);
+ assertThat(mSystemProperties.get(PERSISTENT_PROPERTY_COMPOSITION_COLOR_MODE))
+ .isEqualTo(Integer.toString(testPropertyValue));
+ }
+
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
index e0bef1a..c280349 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
@@ -16,22 +16,49 @@
package com.android.server.display.color;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertArrayEquals;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import android.content.Context;
+import android.content.res.Resources;
import android.hardware.display.DisplayManagerInternal;
+import android.os.Binder;
+import android.os.IBinder;
+import android.view.SurfaceControl;
import androidx.test.InstrumentationRegistry;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.R;
+
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
import java.util.Arrays;
public class DisplayWhiteBalanceTintControllerTest {
+ @Mock
+ private Context mMockedContext;
+ @Mock
+ private Resources mMockedResources;
+ @Mock
+ private DisplayManagerInternal mDisplayManagerInternal;
- private DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController;
+ private MockitoSession mSession;
+ private Resources mResources;
+ IBinder mDisplayToken;
+ DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController;
@Before
public void setUp() {
@@ -40,6 +67,47 @@
new DisplayWhiteBalanceTintController(displayManagerInternal);
mDisplayWhiteBalanceTintController.setUp(InstrumentationRegistry.getContext(), true);
mDisplayWhiteBalanceTintController.setActivated(true);
+
+ mSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .mockStatic(SurfaceControl.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ mResources = InstrumentationRegistry.getContext().getResources();
+ // These Resources are common to all tests.
+ doReturn(4000)
+ .when(mMockedResources)
+ .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMin);
+ doReturn(8000)
+ .when(mMockedResources)
+ .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMax);
+ doReturn(6500)
+ .when(mMockedResources)
+ .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault);
+ doReturn(new String[] {"0.950456", "1.000000", "1.089058"})
+ .when(mMockedResources)
+ .getStringArray(R.array.config_displayWhiteBalanceDisplayNominalWhite);
+ doReturn(6500)
+ .when(mMockedResources)
+ .getInteger(R.integer.config_displayWhiteBalanceDisplayNominalWhiteCct);
+ doReturn(new int[] {0})
+ .when(mMockedResources)
+ .getIntArray(R.array.config_displayWhiteBalanceDisplaySteps);
+ doReturn(new int[] {20})
+ .when(mMockedResources)
+ .getIntArray(R.array.config_displayWhiteBalanceDisplayRangeMinimums);
+
+ doReturn(mMockedResources).when(mMockedContext).getResources();
+
+ mDisplayToken = new Binder();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mSession != null) {
+ mSession.finishMocking();
+ }
}
@Test
@@ -98,4 +166,204 @@
})
).isTrue();
}
+
+
+ /**
+ * Setup should succeed when SurfaceControl setup results in a valid color transform.
+ */
+ @Test
+ public void displayWhiteBalance_setupWithSurfaceControl() {
+ // Make SurfaceControl return sRGB primaries
+ SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+ displayPrimaries.red = new SurfaceControl.CieXyz();
+ displayPrimaries.red.X = 0.412315f;
+ displayPrimaries.red.Y = 0.212600f;
+ displayPrimaries.red.Z = 0.019327f;
+ displayPrimaries.green = new SurfaceControl.CieXyz();
+ displayPrimaries.green.X = 0.357600f;
+ displayPrimaries.green.Y = 0.715200f;
+ displayPrimaries.green.Z = 0.119200f;
+ displayPrimaries.blue = new SurfaceControl.CieXyz();
+ displayPrimaries.blue.X = 0.180500f;
+ displayPrimaries.blue.Y = 0.072200f;
+ displayPrimaries.blue.Z = 0.950633f;
+ displayPrimaries.white = new SurfaceControl.CieXyz();
+ displayPrimaries.white.X = 0.950456f;
+ displayPrimaries.white.Y = 1.000000f;
+ displayPrimaries.white.Z = 1.089058f;
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+ .thenReturn(displayPrimaries);
+
+ setUpTintController();
+ assertWithMessage("Setup with valid SurfaceControl failed")
+ .that(mDisplayWhiteBalanceTintController.mSetUp)
+ .isTrue();
+ }
+
+ /**
+ * Setup should fail when SurfaceControl setup results in an invalid color transform.
+ */
+ @Test
+ public void displayWhiteBalance_setupWithInvalidSurfaceControlData() {
+ // Make SurfaceControl return invalid display primaries
+ SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+ displayPrimaries.red = new SurfaceControl.CieXyz();
+ displayPrimaries.green = new SurfaceControl.CieXyz();
+ displayPrimaries.blue = new SurfaceControl.CieXyz();
+ displayPrimaries.white = new SurfaceControl.CieXyz();
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+ .thenReturn(displayPrimaries);
+
+ setUpTintController();
+ assertWithMessage("Setup with invalid SurfaceControl succeeded")
+ .that(mDisplayWhiteBalanceTintController.mSetUp)
+ .isFalse();
+ }
+
+ /**
+ * Setup should succeed when SurfaceControl setup fails and Resources result in a valid color
+ * transform.
+ */
+ @Test
+ public void displayWhiteBalance_setupWithResources() {
+ // Use default (valid) Resources
+ doReturn(mResources.getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries))
+ .when(mMockedResources)
+ .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
+ // Make SurfaceControl setup fail
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
+
+ setUpTintController();
+ assertWithMessage("Setup with valid Resources failed")
+ .that(mDisplayWhiteBalanceTintController.mSetUp)
+ .isTrue();
+ }
+
+ /**
+ * Setup should fail when SurfaceControl setup fails and Resources result in an invalid color
+ * transform.
+ */
+ @Test
+ public void displayWhiteBalance_setupWithInvalidResources() {
+ // Use Resources with invalid color data
+ doReturn(new String[] {
+ "0", "0", "0", // Red X, Y, Z
+ "0", "0", "0", // Green X, Y, Z
+ "0", "0", "0", // Blue X, Y, Z
+ "0", "0", "0", // White X, Y, Z
+ })
+ .when(mMockedResources)
+ .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
+ // Make SurfaceControl setup fail
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
+
+ setUpTintController();
+ assertWithMessage("Setup with invalid Resources succeeded")
+ .that(mDisplayWhiteBalanceTintController.mSetUp)
+ .isFalse();
+ }
+
+ /**
+ * Matrix should match the precalculated one for given cct and display primaries.
+ */
+ @Test
+ public void displayWhiteBalance_getAndSetMatrix_validateTransformMatrix() {
+ SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+ displayPrimaries.red = new SurfaceControl.CieXyz();
+ displayPrimaries.red.X = 0.412315f;
+ displayPrimaries.red.Y = 0.212600f;
+ displayPrimaries.red.Z = 0.019327f;
+ displayPrimaries.green = new SurfaceControl.CieXyz();
+ displayPrimaries.green.X = 0.357600f;
+ displayPrimaries.green.Y = 0.715200f;
+ displayPrimaries.green.Z = 0.119200f;
+ displayPrimaries.blue = new SurfaceControl.CieXyz();
+ displayPrimaries.blue.X = 0.180500f;
+ displayPrimaries.blue.Y = 0.072200f;
+ displayPrimaries.blue.Z = 0.950633f;
+ displayPrimaries.white = new SurfaceControl.CieXyz();
+ displayPrimaries.white.X = 0.950456f;
+ displayPrimaries.white.Y = 1.000000f;
+ displayPrimaries.white.Z = 1.089058f;
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+ .thenReturn(displayPrimaries);
+
+ setUpTintController();
+ assertWithMessage("Setup with valid SurfaceControl failed")
+ .that(mDisplayWhiteBalanceTintController.mSetUp)
+ .isTrue();
+
+ final int cct = 6500;
+ mDisplayWhiteBalanceTintController.setMatrix(cct);
+ mDisplayWhiteBalanceTintController.setAppliedCct(
+ mDisplayWhiteBalanceTintController.getTargetCct());
+
+ assertWithMessage("Failed to set temperature")
+ .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
+ .isEqualTo(cct);
+ float[] matrixDwb = mDisplayWhiteBalanceTintController.getMatrix();
+ final float[] expectedMatrixDwb = {
+ 0.971848f, -0.001421f, 0.000491f, 0.0f,
+ 0.028193f, 0.945798f, 0.003207f, 0.0f,
+ -0.000042f, -0.000989f, 0.988659f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f
+ };
+ assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
+ 1e-6f /* tolerance */);
+ }
+
+ /**
+ * Matrix should match the precalculated one for given cct and display primaries.
+ */
+ @Test
+ public void displayWhiteBalance_targetApplied_validateTransformMatrix() {
+ SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+ displayPrimaries.red = new SurfaceControl.CieXyz();
+ displayPrimaries.red.X = 0.412315f;
+ displayPrimaries.red.Y = 0.212600f;
+ displayPrimaries.red.Z = 0.019327f;
+ displayPrimaries.green = new SurfaceControl.CieXyz();
+ displayPrimaries.green.X = 0.357600f;
+ displayPrimaries.green.Y = 0.715200f;
+ displayPrimaries.green.Z = 0.119200f;
+ displayPrimaries.blue = new SurfaceControl.CieXyz();
+ displayPrimaries.blue.X = 0.180500f;
+ displayPrimaries.blue.Y = 0.072200f;
+ displayPrimaries.blue.Z = 0.950633f;
+ displayPrimaries.white = new SurfaceControl.CieXyz();
+ displayPrimaries.white.X = 0.950456f;
+ displayPrimaries.white.Y = 1.000000f;
+ displayPrimaries.white.Z = 1.089058f;
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+ .thenReturn(displayPrimaries);
+
+ setUpTintController();
+ assertWithMessage("Setup with valid SurfaceControl failed")
+ .that(mDisplayWhiteBalanceTintController.mSetUp)
+ .isTrue();
+
+ final int cct = 6500;
+ mDisplayWhiteBalanceTintController.setTargetCct(cct);
+ final float[] matrixDwb = mDisplayWhiteBalanceTintController.computeMatrixForCct(cct);
+ mDisplayWhiteBalanceTintController.setAppliedCct(cct);
+
+ assertWithMessage("Failed to set temperature")
+ .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
+ .isEqualTo(cct);
+ final float[] expectedMatrixDwb = {
+ 0.971848f, -0.001421f, 0.000491f, 0.0f,
+ 0.028193f, 0.945798f, 0.003207f, 0.0f,
+ -0.000042f, -0.000989f, 0.988659f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f
+ };
+ assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
+ 1e-6f /* tolerance */);
+ }
+
+ private void setUpTintController() {
+ mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController(
+ mDisplayManagerInternal);
+ mDisplayWhiteBalanceTintController.setUp(mMockedContext, true);
+ mDisplayWhiteBalanceTintController.setActivated(true);
+ }
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
new file mode 100644
index 0000000..880501f
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2022 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.state;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.DisplayPowerProximityStateController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayStateControllerTest {
+ private static final boolean DISPLAY_ENABLED = true;
+ private static final boolean DISPLAY_IN_TRANSITION = true;
+
+ private DisplayStateController mDisplayStateController;
+
+ @Mock
+ private DisplayPowerProximityStateController mDisplayPowerProximityStateController;
+
+ @Before
+ public void before() {
+ MockitoAnnotations.initMocks(this);
+ mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
+ }
+
+ @Test
+ public void updateProximityStateEvaluatesStateOffPolicyAsExpected() {
+ when(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()).thenReturn(
+ false);
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+
+ displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+ int state = mDisplayStateController.updateDisplayState(displayPowerRequest, DISPLAY_ENABLED,
+ !DISPLAY_IN_TRANSITION);
+ assertEquals(Display.STATE_OFF, state);
+ verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
+ Display.STATE_OFF);
+ assertEquals(true, mDisplayStateController.shouldPerformScreenOffTransition());
+ }
+
+ @Test
+ public void updateProximityStateEvaluatesDozePolicyAsExpected() {
+ when(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()).thenReturn(
+ false);
+ validDisplayState(DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE,
+ Display.STATE_DOZE, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
+ }
+
+ @Test
+ public void updateProximityStateEvaluatesDimPolicyAsExpected() {
+ when(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()).thenReturn(
+ false);
+ validDisplayState(DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM,
+ Display.STATE_ON, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
+ }
+
+ @Test
+ public void updateProximityStateEvaluatesDimBrightAsExpected() {
+ when(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()).thenReturn(
+ false);
+ validDisplayState(DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT,
+ Display.STATE_ON, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
+ }
+
+ @Test
+ public void updateProximityStateWorksAsExpectedWhenDisplayDisabled() {
+ when(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()).thenReturn(
+ false);
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+
+ displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ int state = mDisplayStateController.updateDisplayState(displayPowerRequest,
+ !DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
+ assertEquals(Display.STATE_OFF, state);
+ verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
+ Display.STATE_ON);
+ assertEquals(false, mDisplayStateController.shouldPerformScreenOffTransition());
+ }
+
+ @Test
+ public void updateProximityStateWorksAsExpectedWhenTransitionPhase() {
+ when(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()).thenReturn(
+ false);
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+
+ displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ int state = mDisplayStateController.updateDisplayState(displayPowerRequest, DISPLAY_ENABLED,
+ DISPLAY_IN_TRANSITION);
+ assertEquals(Display.STATE_OFF, state);
+ verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
+ Display.STATE_ON);
+ assertEquals(false, mDisplayStateController.shouldPerformScreenOffTransition());
+ }
+
+ @Test
+ public void updateProximityStateWorksAsExpectedWhenScreenOffBecauseOfProximity() {
+ when(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()).thenReturn(
+ true);
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+
+ displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ int state = mDisplayStateController.updateDisplayState(displayPowerRequest, DISPLAY_ENABLED,
+ !DISPLAY_IN_TRANSITION);
+ assertEquals(Display.STATE_OFF, state);
+ verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
+ Display.STATE_ON);
+ assertEquals(false, mDisplayStateController.shouldPerformScreenOffTransition());
+ }
+
+ private void validDisplayState(int policy, int displayState, boolean isEnabled,
+ boolean isInTransition) {
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+ displayPowerRequest.policy = policy;
+ int state = mDisplayStateController.updateDisplayState(displayPowerRequest, isEnabled,
+ isInTransition);
+ assertEquals(displayState, state);
+ verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
+ displayState);
+ assertEquals(false, mDisplayStateController.shouldPerformScreenOffTransition());
+ }
+}