blob: ac0e1b5c35502210e5e5d4e1f03bcc587497ab63 [file] [log] [blame]
Michael Wrighteef0e132017-11-21 17:57:52 +00001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.display;
18
19import android.annotation.Nullable;
Michael Wright144aac92017-12-21 18:37:41 +000020import android.content.res.Resources;
21import android.content.res.TypedArray;
Michael Wrighteef0e132017-11-21 17:57:52 +000022import android.hardware.display.BrightnessConfiguration;
23import android.os.PowerManager;
24import android.util.MathUtils;
25import android.util.Pair;
26import android.util.Slog;
27import android.util.Spline;
28
29import com.android.internal.util.Preconditions;
30import com.android.internal.annotations.VisibleForTesting;
31
32import java.io.PrintWriter;
33
34/**
35 * A utility to map from an ambient brightness to a display's "backlight" brightness based on the
36 * available display information and brightness configuration.
37 *
38 * Note that without a mapping from the nits to a display backlight level, any
39 * {@link BrightnessConfiguration}s that are set are just ignored.
40 */
41public abstract class BrightnessMappingStrategy {
42 private static final String TAG = "BrightnessMappingStrategy";
43 private static final boolean DEBUG = false;
44
45 @Nullable
Michael Wright144aac92017-12-21 18:37:41 +000046 public static BrightnessMappingStrategy create(Resources resources) {
47 float[] luxLevels = getLuxLevels(resources.getIntArray(
48 com.android.internal.R.array.config_autoBrightnessLevels));
49 int[] brightnessLevelsBacklight = resources.getIntArray(
50 com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
51 float[] brightnessLevelsNits = getFloatArray(resources.obtainTypedArray(
52 com.android.internal.R.array.config_autoBrightnessDisplayValuesNits));
53
54 float[] nitsRange = getFloatArray(resources.obtainTypedArray(
55 com.android.internal.R.array.config_screenBrightnessNits));
56 int[] backlightRange = resources.getIntArray(
57 com.android.internal.R.array.config_screenBrightnessBacklight);
58
Michael Wrighteef0e132017-11-21 17:57:52 +000059 if (isValidMapping(nitsRange, backlightRange)
60 && isValidMapping(luxLevels, brightnessLevelsNits)) {
Michael Wright144aac92017-12-21 18:37:41 +000061 int minimumBacklight = resources.getInteger(
62 com.android.internal.R.integer.config_screenBrightnessSettingMinimum);
63 int maximumBacklight = resources.getInteger(
64 com.android.internal.R.integer.config_screenBrightnessSettingMaximum);
65 if (backlightRange[0] > minimumBacklight
66 || backlightRange[backlightRange.length - 1] < maximumBacklight) {
67 Slog.w(TAG, "Screen brightness mapping does not cover whole range of available"
68 + " backlight values, autobrightness functionality may be impaired.");
69 }
Michael Wrighteef0e132017-11-21 17:57:52 +000070 BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder();
71 builder.setCurve(luxLevels, brightnessLevelsNits);
72 return new PhysicalMappingStrategy(builder.build(), nitsRange, backlightRange);
73 } else if (isValidMapping(luxLevels, brightnessLevelsBacklight)) {
74 return new SimpleMappingStrategy(luxLevels, brightnessLevelsBacklight);
75 } else {
76 return null;
77 }
78 }
79
Michael Wright144aac92017-12-21 18:37:41 +000080 private static float[] getLuxLevels(int[] lux) {
81 // The first control point is implicit and always at 0 lux.
82 float[] levels = new float[lux.length + 1];
83 for (int i = 0; i < lux.length; i++) {
84 levels[i + 1] = (float) lux[i];
85 }
86 return levels;
87 }
88
89 private static float[] getFloatArray(TypedArray array) {
90 final int N = array.length();
91 float[] vals = new float[N];
92 for (int i = 0; i < N; i++) {
93 vals[i] = array.getFloat(i, -1.0f);
94 }
95 array.recycle();
96 return vals;
97 }
98
Michael Wrighteef0e132017-11-21 17:57:52 +000099 private static boolean isValidMapping(float[] x, float[] y) {
100 if (x == null || y == null || x.length == 0 || y.length == 0) {
101 return false;
102 }
103 if (x.length != y.length) {
104 return false;
105 }
106 final int N = x.length;
107 float prevX = x[0];
108 float prevY = y[0];
109 if (prevX < 0 || prevY < 0 || Float.isNaN(prevX) || Float.isNaN(prevY)) {
110 return false;
111 }
112 for (int i = 1; i < N; i++) {
113 if (prevX >= x[i] || prevY > y[i]) {
114 return false;
115 }
116 if (Float.isNaN(x[i]) || Float.isNaN(y[i])) {
117 return false;
118 }
119 prevX = x[i];
120 prevY = y[i];
121 }
122 return true;
123 }
124
125 private static boolean isValidMapping(float[] x, int[] y) {
126 if (x == null || y == null || x.length == 0 || y.length == 0) {
127 return false;
128 }
129 if (x.length != y.length) {
130 return false;
131 }
132 final int N = x.length;
133 float prevX = x[0];
134 int prevY = y[0];
135 if (prevX < 0 || prevY < 0 || Float.isNaN(prevX)) {
136 return false;
137 }
138 for (int i = 1; i < N; i++) {
139 if (prevX >= x[i] || prevY > y[i]) {
140 return false;
141 }
142 if (Float.isNaN(x[i])) {
143 return false;
144 }
145 prevX = x[i];
146 prevY = y[i];
147 }
148 return true;
149 }
150
151 /**
152 * Sets the {@link BrightnessConfiguration}.
153 *
154 * @param config The new configuration. If {@code null} is passed, the default configuration is
155 * used.
156 * @return Whether the brightness configuration has changed.
157 */
158 public abstract boolean setBrightnessConfiguration(@Nullable BrightnessConfiguration config);
159
160 /**
161 * Returns the desired brightness of the display based on the current ambient lux.
162 *
163 * The returned brightness will be in the range [0, 1.0], where 1.0 is the display at max
164 * brightness and 0 is the display at minimum brightness.
165 *
166 * @param lux The current ambient brightness in lux.
Michael Wright144aac92017-12-21 18:37:41 +0000167 * @return The desired brightness of the display normalized to the range [0, 1.0].
Michael Wrighteef0e132017-11-21 17:57:52 +0000168 */
169 public abstract float getBrightness(float lux);
170
Michael Wright144aac92017-12-21 18:37:41 +0000171 /**
172 * Gets the display's brightness in nits for the given backlight value.
173 *
174 * Returns -1.0f if there's no available mapping for the backlight to nits.
175 */
176 public abstract float getNits(int backlight);
177
Michael Wrighteef0e132017-11-21 17:57:52 +0000178 public abstract void dump(PrintWriter pw);
179
180 private static float normalizeAbsoluteBrightness(int brightness) {
181 brightness = MathUtils.constrain(brightness,
182 PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON);
183 return (float) brightness / PowerManager.BRIGHTNESS_ON;
184 }
185
186
187 /**
188 * A {@link BrightnessMappingStrategy} that maps from ambient room brightness directly to the
189 * backlight of the display.
190 *
191 * Since we don't have information about the display's physical brightness, any brightness
192 * configurations that are set are just ignored.
193 */
194 private static class SimpleMappingStrategy extends BrightnessMappingStrategy {
195 private final Spline mSpline;
196
197 public SimpleMappingStrategy(float[] lux, int[] brightness) {
198 Preconditions.checkArgument(lux.length != 0 && brightness.length != 0,
199 "Lux and brightness arrays must not be empty!");
200 Preconditions.checkArgument(lux.length == brightness.length,
201 "Lux and brightness arrays must be the same length!");
202 Preconditions.checkArrayElementsInRange(lux, 0, Float.MAX_VALUE, "lux");
203 Preconditions.checkArrayElementsInRange(brightness,
204 0, Integer.MAX_VALUE, "brightness");
205
206 final int N = brightness.length;
207 float[] x = new float[N];
208 float[] y = new float[N];
209 for (int i = 0; i < N; i++) {
210 x[i] = lux[i];
211 y[i] = normalizeAbsoluteBrightness(brightness[i]);
212 }
213
214 mSpline = Spline.createSpline(x, y);
215 if (DEBUG) {
216 Slog.d(TAG, "Auto-brightness spline: " + mSpline);
217 for (float v = 1f; v < lux[lux.length - 1] * 1.25f; v *= 1.25f) {
218 Slog.d(TAG, String.format(" %7.1f: %7.1f", v, mSpline.interpolate(v)));
219 }
220 }
221 }
222
223 @Override
224 public boolean setBrightnessConfiguration(@Nullable BrightnessConfiguration config) {
Michael Wrighteef0e132017-11-21 17:57:52 +0000225 return false;
226 }
227
228 @Override
229 public float getBrightness(float lux) {
230 return mSpline.interpolate(lux);
231 }
232
233 @Override
Michael Wright144aac92017-12-21 18:37:41 +0000234 public float getNits(int backlight) {
235 return -1.0f;
236 }
237
238 @Override
Michael Wrighteef0e132017-11-21 17:57:52 +0000239 public void dump(PrintWriter pw) {
240 pw.println("SimpleMappingStrategy");
241 pw.println(" mSpline=" + mSpline);
242 }
243 }
244
245 /** A {@link BrightnessMappingStrategy} that maps from ambient room brightness to the physical
246 * range of the display, rather than to the range of the backlight control (typically 0-255).
247 *
248 * By mapping through the physical brightness, the curve becomes portable across devices and
249 * gives us more resolution in the resulting mapping.
250 */
251 @VisibleForTesting
252 static class PhysicalMappingStrategy extends BrightnessMappingStrategy {
253 // The current brightness configuration.
254 private BrightnessConfiguration mConfig;
255
256 // A spline mapping from the current ambient light in lux to the desired display brightness
257 // in nits.
258 private Spline mBrightnessSpline;
259
260 // A spline mapping from nits to the corresponding backlight value, normalized to the range
261 // [0, 1.0].
Michael Wright144aac92017-12-21 18:37:41 +0000262 private final Spline mNitsToBacklightSpline;
263
264 // A spline mapping from the device's backlight value, normalized to the range [0, 1.0], to
265 // a brightness in nits.
266 private final Spline mBacklightToNitsSpline;
Michael Wrighteef0e132017-11-21 17:57:52 +0000267
268 // The default brightness configuration.
269 private final BrightnessConfiguration mDefaultConfig;
270
271 public PhysicalMappingStrategy(BrightnessConfiguration config,
272 float[] nits, int[] backlight) {
273 Preconditions.checkArgument(nits.length != 0 && backlight.length != 0,
274 "Nits and backlight arrays must not be empty!");
275 Preconditions.checkArgument(nits.length == backlight.length,
276 "Nits and backlight arrays must be the same length!");
277 Preconditions.checkNotNull(config);
278 Preconditions.checkArrayElementsInRange(nits, 0, Float.MAX_VALUE, "nits");
279 Preconditions.checkArrayElementsInRange(backlight,
280 PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON, "backlight");
281
282 // Setup the backlight spline
283 final int N = nits.length;
Michael Wright144aac92017-12-21 18:37:41 +0000284 float[] normalizedBacklight = new float[N];
Michael Wrighteef0e132017-11-21 17:57:52 +0000285 for (int i = 0; i < N; i++) {
Michael Wright144aac92017-12-21 18:37:41 +0000286 normalizedBacklight[i] = normalizeAbsoluteBrightness(backlight[i]);
Michael Wrighteef0e132017-11-21 17:57:52 +0000287 }
288
Michael Wright144aac92017-12-21 18:37:41 +0000289 mNitsToBacklightSpline = Spline.createSpline(nits, normalizedBacklight);
290 mBacklightToNitsSpline = Spline.createSpline(normalizedBacklight, nits);
Michael Wrighteef0e132017-11-21 17:57:52 +0000291 if (DEBUG) {
Michael Wright144aac92017-12-21 18:37:41 +0000292 Slog.d(TAG, "Backlight spline: " + mNitsToBacklightSpline);
Michael Wrighteef0e132017-11-21 17:57:52 +0000293 for (float v = 1f; v < nits[nits.length - 1] * 1.25f; v *= 1.25f) {
294 Slog.d(TAG, String.format(
Michael Wright144aac92017-12-21 18:37:41 +0000295 " %7.1f: %7.1f", v, mNitsToBacklightSpline.interpolate(v)));
Michael Wrighteef0e132017-11-21 17:57:52 +0000296 }
297 }
298
299 mDefaultConfig = config;
300 setBrightnessConfiguration(config);
301 }
302
303 @Override
304 public boolean setBrightnessConfiguration(@Nullable BrightnessConfiguration config) {
305 if (config == null) {
306 config = mDefaultConfig;
307 }
308 if (config.equals(mConfig)) {
309 if (DEBUG) {
310 Slog.d(TAG, "Tried to set an identical brightness config, ignoring");
311 }
312 return false;
313 }
314
315 Pair<float[], float[]> curve = config.getCurve();
316 mBrightnessSpline = Spline.createSpline(curve.first /*lux*/, curve.second /*nits*/);
317 if (DEBUG) {
318 Slog.d(TAG, "Brightness spline: " + mBrightnessSpline);
319 final float[] lux = curve.first;
320 for (float v = 1f; v < lux[lux.length - 1] * 1.25f; v *= 1.25f) {
321 Slog.d(TAG, String.format(
322 " %7.1f: %7.1f", v, mBrightnessSpline.interpolate(v)));
323 }
324 }
325 mConfig = config;
326 return true;
327 }
328
329 @Override
330 public float getBrightness(float lux) {
Michael Wright144aac92017-12-21 18:37:41 +0000331 return mNitsToBacklightSpline.interpolate(mBrightnessSpline.interpolate(lux));
332 }
333
334 @Override
335 public float getNits(int backlight) {
336 return mBacklightToNitsSpline.interpolate(normalizeAbsoluteBrightness(backlight));
Michael Wrighteef0e132017-11-21 17:57:52 +0000337 }
338
339 @Override
340 public void dump(PrintWriter pw) {
341 pw.println("PhysicalMappingStrategy");
342 pw.println(" mConfig=" + mConfig);
343 pw.println(" mBrightnessSpline=" + mBrightnessSpline);
Michael Wright144aac92017-12-21 18:37:41 +0000344 pw.println(" mNitsToBacklightSpline=" + mNitsToBacklightSpline);
Michael Wrighteef0e132017-11-21 17:57:52 +0000345 }
346 }
347}