Added system-wide minimum brightness curve.
The minimum brightness curve guarantess that any brightness curve
that dips below it is rejected by the system.
This prevent auto-brightness from setting the screen so dark as to
prevent the user from resetting or disabling it, and maps lux to
the absolute minimum nits that are still readable in that ambient
brightness.
Test: atest BrightnessConfigurationTest.
Fixes: 77176207
Change-Id: Ibd1e83e9b147f3849d6c907f828cbe5950c8367f
diff --git a/api/system-current.txt b/api/system-current.txt
index c85e2d3..ac9ddcd 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1252,6 +1252,7 @@
method public android.hardware.display.BrightnessConfiguration getBrightnessConfiguration();
method public java.util.List<android.hardware.display.BrightnessChangeEvent> getBrightnessEvents();
method public android.hardware.display.BrightnessConfiguration getDefaultBrightnessConfiguration();
+ method public android.util.Pair<float[], float[]> getMinimumBrightnessCurve();
method public android.graphics.Point getStableDisplaySize();
method public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration);
method public void setSaturationLevel(float);
diff --git a/core/java/android/hardware/display/BrightnessConfiguration.java b/core/java/android/hardware/display/BrightnessConfiguration.java
index 67e97bf..6d9ba77 100644
--- a/core/java/android/hardware/display/BrightnessConfiguration.java
+++ b/core/java/android/hardware/display/BrightnessConfiguration.java
@@ -86,7 +86,9 @@
sb.append("(").append(mLux[i]).append(", ").append(mNits[i]).append(")");
}
sb.append("], '");
- sb.append(mDescription);
+ if (mDescription != null) {
+ sb.append(mDescription);
+ }
sb.append("'}");
return sb.toString();
}
@@ -96,7 +98,9 @@
int result = 1;
result = result * 31 + Arrays.hashCode(mLux);
result = result * 31 + Arrays.hashCode(mNits);
- result = result * 31 + mDescription.hashCode();
+ if (mDescription != null) {
+ result = result * 31 + mDescription.hashCode();
+ }
return result;
}
diff --git a/core/java/android/hardware/display/Curve.aidl b/core/java/android/hardware/display/Curve.aidl
new file mode 100644
index 0000000..796493e
--- /dev/null
+++ b/core/java/android/hardware/display/Curve.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2018 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 android.hardware.display;
+
+parcelable Curve;
diff --git a/core/java/android/hardware/display/Curve.java b/core/java/android/hardware/display/Curve.java
new file mode 100644
index 0000000..ac28fdd
--- /dev/null
+++ b/core/java/android/hardware/display/Curve.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2018 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 android.hardware.display;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public final class Curve implements Parcelable {
+ private final float[] mX;
+ private final float[] mY;
+
+ public Curve(float[] x, float[] y) {
+ mX = x;
+ mY = y;
+ }
+
+ public float[] getX() {
+ return mX;
+ }
+
+ public float[] getY() {
+ return mY;
+ }
+
+ public static final Creator<Curve> CREATOR = new Creator<Curve>() {
+ public Curve createFromParcel(Parcel in) {
+ float[] x = in.createFloatArray();
+ float[] y = in.createFloatArray();
+ return new Curve(x, y);
+ }
+
+ public Curve[] newArray(int size) {
+ return new Curve[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeFloatArray(mX);
+ out.writeFloatArray(mY);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index efb9517..b182fa2 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -28,6 +28,7 @@
import android.graphics.Point;
import android.media.projection.MediaProjection;
import android.os.Handler;
+import android.util.Pair;
import android.util.SparseArray;
import android.view.Display;
import android.view.Surface;
@@ -748,6 +749,22 @@
}
/**
+ * Returns the minimum brightness curve, which guarantess that any brightness curve that dips
+ * below it is rejected by the system.
+ * This prevent auto-brightness from setting the screen so dark as to prevent the user from
+ * resetting or disabling it, and maps lux to the absolute minimum nits that are still readable
+ * in that ambient brightness.
+ *
+ * @return The minimum brightness curve (as lux values and their corresponding nits values).
+ *
+ * @hide
+ */
+ @SystemApi
+ public Pair<float[], float[]> getMinimumBrightnessCurve() {
+ return mGlobal.getMinimumBrightnessCurve();
+ }
+
+ /**
* Listens for changes in available display devices.
*/
public interface DisplayListener {
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 2d0ef2f..d968a3e 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -31,6 +31,7 @@
import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayAdjustments;
@@ -563,6 +564,24 @@
}
/**
+ * Returns the minimum brightness curve, which guarantess that any brightness curve that dips
+ * below it is rejected by the system.
+ * This prevent auto-brightness from setting the screen so dark as to prevent the user from
+ * resetting or disabling it, and maps lux to the absolute minimum nits that are still readable
+ * in that ambient brightness.
+ *
+ * @return The minimum brightness curve (as lux values and their corresponding nits values).
+ */
+ public Pair<float[], float[]> getMinimumBrightnessCurve() {
+ try {
+ Curve curve = mDm.getMinimumBrightnessCurve();
+ return Pair.create(curve.getX(), curve.getY());
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Retrieves ambient brightness stats.
*/
public List<AmbientBrightnessDayStats> getAmbientBrightnessStats() {
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index b77de748..b575997 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -19,6 +19,7 @@
import android.content.pm.ParceledListSlice;
import android.graphics.Point;
import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.Curve;
import android.hardware.display.IDisplayManagerCallback;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.WifiDisplay;
@@ -112,4 +113,7 @@
// Temporarily sets the auto brightness adjustment factor.
void setTemporaryAutoBrightnessAdjustment(float adjustment);
+
+ // Get the minimum brightness curve.
+ Curve getMinimumBrightnessCurve();
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3cd80f2..28a6cee 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1286,6 +1286,32 @@
in darkness (although they may not be visible in a bright room). -->
<integer name="config_screenBrightnessDark">1</integer>
+ <!-- Array of lux values to define the minimum brightness curve, which guarantees that any
+ brightness curve that dips below it is rejected by the system.
+ This prevents auto-brightness from setting the screen so dark as to prevent the user from
+ resetting or disabling it.
+
+ The values must be non-negative and strictly increasing, and correspond to the values in
+ the config_minimumBrightnessCurveNits array. -->
+ <array name="config_minimumBrightnessCurveLux">
+ <item>0.0</item>
+ <item>2000.0</item>
+ <item>4000.0</item>
+ </array>
+
+ <!-- Array of nits values to define the minimum brightness curve, which guarantees that any
+ brightness curve that dips below it is rejected by the system.
+ This should map lux to the absolute minimum nits that are still readable in that ambient
+ brightness.
+
+ The values must be non-negative and non-decreasing, and correspond to the values in the
+ config_minimumBrightnessCurveLux array. -->
+ <array name="config_minimumBrightnessCurveNits">
+ <item>0.0</item>
+ <item>50.0</item>
+ <item>90.0</item>
+ </array>
+
<!-- Array of light sensor lux values to define our levels for auto backlight brightness support.
The N entries of this array define N + 1 control points as follows:
(1-based arrays)
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ac5c3ab..361188e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1808,6 +1808,8 @@
<java-symbol type="array" name="config_dynamicHysteresisBrightLevels" />
<java-symbol type="array" name="config_dynamicHysteresisDarkLevels" />
<java-symbol type="array" name="config_dynamicHysteresisLuxLevels" />
+ <java-symbol type="array" name="config_minimumBrightnessCurveLux" />
+ <java-symbol type="array" name="config_minimumBrightnessCurveNits" />
<java-symbol type="array" name="config_protectedNetworks" />
<java-symbol type="array" name="config_statusBarIcons" />
<java-symbol type="array" name="config_tether_bluetooth_regexs" />
diff --git a/core/tests/coretests/src/android/hardware/display/BrightnessConfigurationTest.java b/core/tests/coretests/src/android/hardware/display/BrightnessConfigurationTest.java
index bad0d25..5ef30a8 100644
--- a/core/tests/coretests/src/android/hardware/display/BrightnessConfigurationTest.java
+++ b/core/tests/coretests/src/android/hardware/display/BrightnessConfigurationTest.java
@@ -41,7 +41,7 @@
};
private static final float[] NITS_LEVELS = {
- 0.5f,
+ 1f,
90f,
100f,
};
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 93e0bd5..0f531a8 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -36,11 +36,13 @@
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Point;
import android.hardware.SensorManager;
import android.hardware.display.AmbientBrightnessDayStats;
import android.hardware.display.BrightnessChangeEvent;
import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.Curve;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayViewport;
@@ -72,8 +74,10 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.IntArray;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.Spline;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.Surface;
@@ -276,6 +280,11 @@
private final Injector mInjector;
+ // The minimum brightness curve, which guarantess that any brightness curve that dips below it
+ // is rejected by the system.
+ private final Curve mMinimumBrightnessCurve;
+ private final Spline mMinimumBrightnessSpline;
+
public DisplayManagerService(Context context) {
this(context, new Injector());
}
@@ -289,8 +298,15 @@
mUiHandler = UiThread.getHandler();
mDisplayAdapterListener = new DisplayAdapterListener();
mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
+ Resources resources = mContext.getResources();
mDefaultDisplayDefaultColorMode = mContext.getResources().getInteger(
com.android.internal.R.integer.config_defaultDisplayDefaultColorMode);
+ float[] lux = getFloatArray(resources.obtainTypedArray(
+ com.android.internal.R.array.config_minimumBrightnessCurveLux));
+ float[] nits = getFloatArray(resources.obtainTypedArray(
+ com.android.internal.R.array.config_minimumBrightnessCurveNits));
+ mMinimumBrightnessCurve = new Curve(lux, nits);
+ mMinimumBrightnessSpline = Spline.createSpline(lux, nits);
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mGlobalDisplayBrightness = pm.getDefaultScreenBrightnessSetting();
@@ -1032,9 +1048,15 @@
}
}
+ @VisibleForTesting
+ Curve getMinimumBrightnessCurveInternal() {
+ return mMinimumBrightnessCurve;
+ }
+
private void setBrightnessConfigurationForUserInternal(
@Nullable BrightnessConfiguration c, @UserIdInt int userId,
@Nullable String packageName) {
+ validateBrightnessConfiguration(c);
final int userSerial = getUserManager().getUserSerialNumber(userId);
synchronized (mSyncRoot) {
try {
@@ -1049,6 +1071,28 @@
}
}
+ @VisibleForTesting
+ void validateBrightnessConfiguration(BrightnessConfiguration config) {
+ if (config == null) {
+ return;
+ }
+ if (isBrightnessConfigurationTooDark(config)) {
+ throw new IllegalArgumentException("brightness curve is too dark");
+ }
+ }
+
+ private boolean isBrightnessConfigurationTooDark(BrightnessConfiguration config) {
+ Pair<float[], float[]> curve = config.getCurve();
+ float[] lux = curve.first;
+ float[] nits = curve.second;
+ for (int i = 0; i < lux.length; i++) {
+ if (nits[i] < mMinimumBrightnessSpline.interpolate(lux[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void loadBrightnessConfiguration() {
synchronized (mSyncRoot) {
final int userSerial = getUserManager().getUserSerialNumber(mCurrentUserId);
@@ -1369,6 +1413,16 @@
}
}
+ private static float[] getFloatArray(TypedArray array) {
+ int length = array.length();
+ float[] floatArray = new float[length];
+ for (int i = 0; i < length; i++) {
+ floatArray[i] = array.getFloat(i, Float.NaN);
+ }
+ array.recycle();
+ return floatArray;
+ }
+
/**
* This is the object that everything in the display manager locks on.
* We make it an inner class within the {@link DisplayManagerService} to so that it is
@@ -1999,6 +2053,16 @@
}
}
+ @Override // Binder call
+ public Curve getMinimumBrightnessCurve() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return getMinimumBrightnessCurveInternal();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
void setBrightness(int brightness) {
Settings.System.putIntForUser(mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS, brightness, UserHandle.USER_CURRENT);
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 2f2afd7..ceee60c 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -17,12 +17,15 @@
package com.android.server.display;
import android.content.Context;
+import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.Curve;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayViewport;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.input.InputManagerInternal;
import android.os.Handler;
import android.os.IBinder;
+import android.os.UserHandle;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.SurfaceControl;
@@ -226,6 +229,62 @@
+ " virtual display adapter");
}
+ /**
+ * Tests that an exception is raised for too dark a brightness configuration.
+ */
+ public void testTooDarkBrightnessConfigurationThrowException() {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mShortMockedInjector);
+ Curve minimumBrightnessCurve = displayManager.getMinimumBrightnessCurveInternal();
+ float[] lux = minimumBrightnessCurve.getX();
+ float[] minimumNits = minimumBrightnessCurve.getY();
+ float[] nits = new float[minimumNits.length];
+ // For every control point, assert that making it slightly lower than the minimum throws an
+ // exception.
+ for (int i = 0; i < nits.length; i++) {
+ for (int j = 0; j < nits.length; j++) {
+ nits[j] = minimumNits[j];
+ if (j == i) {
+ nits[j] -= 0.1f;
+ }
+ if (nits[j] < 0) {
+ nits[j] = 0;
+ }
+ }
+ BrightnessConfiguration config =
+ new BrightnessConfiguration.Builder(lux, nits).build();
+ Exception thrown = null;
+ try {
+ displayManager.validateBrightnessConfiguration(config);
+ } catch (IllegalArgumentException e) {
+ thrown = e;
+ }
+ assertNotNull("Building too dark a brightness configuration must throw an exception");
+ }
+ }
+
+ /**
+ * Tests that no exception is raised for not too dark a brightness configuration.
+ */
+ public void testBrightEnoughBrightnessConfigurationDoesNotThrowException() {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mShortMockedInjector);
+ Curve minimumBrightnessCurve = displayManager.getMinimumBrightnessCurveInternal();
+ float[] lux = minimumBrightnessCurve.getX();
+ float[] nits = minimumBrightnessCurve.getY();
+ BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits).build();
+ displayManager.validateBrightnessConfiguration(config);
+ }
+
+ /**
+ * Tests that null brightness configurations are alright.
+ */
+ public void testNullBrightnessConfiguration() {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mShortMockedInjector);
+ displayManager.validateBrightnessConfiguration(null);
+ }
+
private void registerDefaultDisplays(DisplayManagerService displayManager) {
Handler handler = displayManager.getDisplayHandler();
// Would prefer to call displayManager.onStart() directly here but it performs binderService