blob: 436ebff0ad86e3acc607d35ed88eb113a675c5d7 [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;
Michael Wrightd8460232018-01-16 18:04:59 +000033import java.util.Arrays;
Michael Wrighteef0e132017-11-21 17:57:52 +000034
35/**
36 * A utility to map from an ambient brightness to a display's "backlight" brightness based on the
37 * available display information and brightness configuration.
38 *
39 * Note that without a mapping from the nits to a display backlight level, any
40 * {@link BrightnessConfiguration}s that are set are just ignored.
41 */
42public abstract class BrightnessMappingStrategy {
43 private static final String TAG = "BrightnessMappingStrategy";
44 private static final boolean DEBUG = false;
45
Michael Wrightd8460232018-01-16 18:04:59 +000046 private static final float LUX_GRAD_SMOOTHING = 0.25f;
47 private static final float MAX_GRAD = 1.0f;
48
Michael Wrighteef0e132017-11-21 17:57:52 +000049 @Nullable
Michael Wright144aac92017-12-21 18:37:41 +000050 public static BrightnessMappingStrategy create(Resources resources) {
51 float[] luxLevels = getLuxLevels(resources.getIntArray(
52 com.android.internal.R.array.config_autoBrightnessLevels));
53 int[] brightnessLevelsBacklight = resources.getIntArray(
54 com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
55 float[] brightnessLevelsNits = getFloatArray(resources.obtainTypedArray(
56 com.android.internal.R.array.config_autoBrightnessDisplayValuesNits));
57
58 float[] nitsRange = getFloatArray(resources.obtainTypedArray(
59 com.android.internal.R.array.config_screenBrightnessNits));
60 int[] backlightRange = resources.getIntArray(
61 com.android.internal.R.array.config_screenBrightnessBacklight);
62
Michael Wrighteef0e132017-11-21 17:57:52 +000063 if (isValidMapping(nitsRange, backlightRange)
64 && isValidMapping(luxLevels, brightnessLevelsNits)) {
Michael Wright144aac92017-12-21 18:37:41 +000065 int minimumBacklight = resources.getInteger(
66 com.android.internal.R.integer.config_screenBrightnessSettingMinimum);
67 int maximumBacklight = resources.getInteger(
68 com.android.internal.R.integer.config_screenBrightnessSettingMaximum);
69 if (backlightRange[0] > minimumBacklight
70 || backlightRange[backlightRange.length - 1] < maximumBacklight) {
71 Slog.w(TAG, "Screen brightness mapping does not cover whole range of available"
72 + " backlight values, autobrightness functionality may be impaired.");
73 }
Michael Wrighteef0e132017-11-21 17:57:52 +000074 BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder();
75 builder.setCurve(luxLevels, brightnessLevelsNits);
76 return new PhysicalMappingStrategy(builder.build(), nitsRange, backlightRange);
77 } else if (isValidMapping(luxLevels, brightnessLevelsBacklight)) {
78 return new SimpleMappingStrategy(luxLevels, brightnessLevelsBacklight);
79 } else {
80 return null;
81 }
82 }
83
Michael Wright144aac92017-12-21 18:37:41 +000084 private static float[] getLuxLevels(int[] lux) {
85 // The first control point is implicit and always at 0 lux.
86 float[] levels = new float[lux.length + 1];
87 for (int i = 0; i < lux.length; i++) {
88 levels[i + 1] = (float) lux[i];
89 }
90 return levels;
91 }
92
93 private static float[] getFloatArray(TypedArray array) {
94 final int N = array.length();
95 float[] vals = new float[N];
96 for (int i = 0; i < N; i++) {
97 vals[i] = array.getFloat(i, -1.0f);
98 }
99 array.recycle();
100 return vals;
101 }
102
Michael Wrighteef0e132017-11-21 17:57:52 +0000103 private static boolean isValidMapping(float[] x, float[] y) {
104 if (x == null || y == null || x.length == 0 || y.length == 0) {
105 return false;
106 }
107 if (x.length != y.length) {
108 return false;
109 }
110 final int N = x.length;
111 float prevX = x[0];
112 float prevY = y[0];
113 if (prevX < 0 || prevY < 0 || Float.isNaN(prevX) || Float.isNaN(prevY)) {
114 return false;
115 }
116 for (int i = 1; i < N; i++) {
117 if (prevX >= x[i] || prevY > y[i]) {
118 return false;
119 }
120 if (Float.isNaN(x[i]) || Float.isNaN(y[i])) {
121 return false;
122 }
123 prevX = x[i];
124 prevY = y[i];
125 }
126 return true;
127 }
128
129 private static boolean isValidMapping(float[] x, int[] y) {
130 if (x == null || y == null || x.length == 0 || y.length == 0) {
131 return false;
132 }
133 if (x.length != y.length) {
134 return false;
135 }
136 final int N = x.length;
137 float prevX = x[0];
138 int prevY = y[0];
139 if (prevX < 0 || prevY < 0 || Float.isNaN(prevX)) {
140 return false;
141 }
142 for (int i = 1; i < N; i++) {
143 if (prevX >= x[i] || prevY > y[i]) {
144 return false;
145 }
146 if (Float.isNaN(x[i])) {
147 return false;
148 }
149 prevX = x[i];
150 prevY = y[i];
151 }
152 return true;
153 }
154
155 /**
156 * Sets the {@link BrightnessConfiguration}.
157 *
158 * @param config The new configuration. If {@code null} is passed, the default configuration is
159 * used.
160 * @return Whether the brightness configuration has changed.
161 */
162 public abstract boolean setBrightnessConfiguration(@Nullable BrightnessConfiguration config);
163
164 /**
165 * Returns the desired brightness of the display based on the current ambient lux.
166 *
167 * The returned brightness will be in the range [0, 1.0], where 1.0 is the display at max
168 * brightness and 0 is the display at minimum brightness.
169 *
170 * @param lux The current ambient brightness in lux.
Michael Wright144aac92017-12-21 18:37:41 +0000171 * @return The desired brightness of the display normalized to the range [0, 1.0].
Michael Wrighteef0e132017-11-21 17:57:52 +0000172 */
173 public abstract float getBrightness(float lux);
174
Michael Wright144aac92017-12-21 18:37:41 +0000175 /**
Michael Wrightd8460232018-01-16 18:04:59 +0000176 * Converts the provided backlight value to nits if possible.
Michael Wright144aac92017-12-21 18:37:41 +0000177 *
178 * Returns -1.0f if there's no available mapping for the backlight to nits.
179 */
Michael Wrightd8460232018-01-16 18:04:59 +0000180 public abstract float convertToNits(int backlight);
181
182 /**
183 * Adds a user interaction data point to the brightness mapping.
184 *
185 * Currently, we only keep track of one of these at a time to constrain what can happen to the
186 * curve.
187 */
188 public abstract void addUserDataPoint(float lux, float brightness);
189
190 /**
191 * Removes any short term adjustments made to the curve from user interactions.
192 *
193 * Note that this does *not* reset the mapping to its initial state, any brightness
194 * configurations that have been applied will continue to be in effect. This solely removes the
195 * effects of user interactions on the model.
196 */
197 public abstract void clearUserDataPoints();
Michael Wright144aac92017-12-21 18:37:41 +0000198
Michael Wrighteef0e132017-11-21 17:57:52 +0000199 public abstract void dump(PrintWriter pw);
200
201 private static float normalizeAbsoluteBrightness(int brightness) {
202 brightness = MathUtils.constrain(brightness,
203 PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON);
204 return (float) brightness / PowerManager.BRIGHTNESS_ON;
205 }
206
Michael Wrightd8460232018-01-16 18:04:59 +0000207 private static Spline createSpline(float[] x, float[] y) {
208 Spline spline = Spline.createSpline(x, y);
209 if (DEBUG) {
210 Slog.d(TAG, "Spline: " + spline);
211 for (float v = 1f; v < x[x.length - 1] * 1.25f; v *= 1.25f) {
212 Slog.d(TAG, String.format(" %7.1f: %7.1f", v, spline.interpolate(v)));
213 }
214 }
215 return spline;
216 }
217
218 private static Pair<float[], float[]> insertControlPoint(
219 float[] luxLevels, float[] brightnessLevels, float lux, float brightness) {
220 if (DEBUG) {
221 Slog.d(TAG, "Inserting new control point at (" + lux + ", " + brightness + ")");
222 }
223 final int idx = findInsertionPoint(luxLevels, lux);
224 final float[] newLuxLevels;
225 final float[] newBrightnessLevels;
226 if (idx == luxLevels.length) {
227 newLuxLevels = Arrays.copyOf(luxLevels, luxLevels.length + 1);
228 newBrightnessLevels = Arrays.copyOf(brightnessLevels, brightnessLevels.length + 1);
229 newLuxLevels[idx] = lux;
230 newBrightnessLevels[idx] = brightness;
231 } else if (luxLevels[idx] == lux) {
232 newLuxLevels = Arrays.copyOf(luxLevels, luxLevels.length);
233 newBrightnessLevels = Arrays.copyOf(brightnessLevels, brightnessLevels.length);
234 newBrightnessLevels[idx] = brightness;
235 } else {
236 newLuxLevels = Arrays.copyOf(luxLevels, luxLevels.length + 1);
237 System.arraycopy(newLuxLevels, idx, newLuxLevels, idx+1, luxLevels.length - idx);
238 newLuxLevels[idx] = lux;
239 newBrightnessLevels = Arrays.copyOf(brightnessLevels, brightnessLevels.length + 1);
240 System.arraycopy(newBrightnessLevels, idx, newBrightnessLevels, idx+1,
241 brightnessLevels.length - idx);
242 newBrightnessLevels[idx] = brightness;
243 }
244 smoothCurve(newLuxLevels, newBrightnessLevels, idx);
245 return Pair.create(newLuxLevels, newBrightnessLevels);
246 }
247
248 /**
249 * Returns the index of the first value that's less than or equal to {@code val}.
250 *
251 * This assumes that {@code arr} is sorted. If all values in {@code arr} are greater
252 * than val, then it will return the length of arr as the insertion point.
253 */
254 private static int findInsertionPoint(float[] arr, float val) {
255 for (int i = 0; i < arr.length; i++) {
256 if (val <= arr[i]) {
257 return i;
258 }
259 }
260 return arr.length;
261 }
262
263 private static void smoothCurve(float[] lux, float[] brightness, int idx) {
264 if (DEBUG) {
265 Slog.d(TAG, "smoothCurve(lux=" + Arrays.toString(lux)
266 + ", brightness=" + Arrays.toString(brightness)
267 + ", idx=" + idx + ")");
268 }
269 float prevLux = lux[idx];
270 float prevBrightness = brightness[idx];
271 // Smooth curve for data points above the newly introduced point
272 for (int i = idx+1; i < lux.length; i++) {
273 float currLux = lux[i];
274 float currBrightness = brightness[i];
275 float maxBrightness = prevBrightness * permissibleRatio(currLux, prevLux);
276 float newBrightness = MathUtils.constrain(
277 currBrightness, prevBrightness, maxBrightness);
278 if (newBrightness == currBrightness) {
279 break;
280 }
281 prevLux = currLux;
282 prevBrightness = newBrightness;
283 brightness[i] = newBrightness;
284 }
285
286 // Smooth curve for data points below the newly introduced point
287 prevLux = lux[idx];
288 prevBrightness = brightness[idx];
289 for (int i = idx-1; i >= 0; i--) {
290 float currLux = lux[i];
291 float currBrightness = brightness[i];
292 float minBrightness = prevBrightness * permissibleRatio(currLux, prevLux);
293 float newBrightness = MathUtils.constrain(
294 currBrightness, minBrightness, prevBrightness);
295 if (newBrightness == currBrightness) {
296 break;
297 }
298 prevLux = currLux;
299 prevBrightness = newBrightness;
300 brightness[i] = newBrightness;
301 }
302 if (DEBUG) {
303 Slog.d(TAG, "Smoothed Curve: lux=" + Arrays.toString(lux)
304 + ", brightness=" + Arrays.toString(brightness));
305 }
306 }
307
308 private static float permissibleRatio(float currLux, float prevLux) {
309 return MathUtils.exp(MAX_GRAD
310 * (MathUtils.log(currLux + LUX_GRAD_SMOOTHING)
311 - MathUtils.log(prevLux + LUX_GRAD_SMOOTHING)));
312 }
Michael Wrighteef0e132017-11-21 17:57:52 +0000313
314 /**
315 * A {@link BrightnessMappingStrategy} that maps from ambient room brightness directly to the
316 * backlight of the display.
317 *
318 * Since we don't have information about the display's physical brightness, any brightness
319 * configurations that are set are just ignored.
320 */
321 private static class SimpleMappingStrategy extends BrightnessMappingStrategy {
Michael Wrightd8460232018-01-16 18:04:59 +0000322 // Lux control points
323 private final float[] mLux;
324 // Brightness control points normalized to [0, 1]
325 private final float[] mBrightness;
326
327 private Spline mSpline;
328 private float mUserLux;
329 private float mUserBrightness;
Michael Wrighteef0e132017-11-21 17:57:52 +0000330
331 public SimpleMappingStrategy(float[] lux, int[] brightness) {
332 Preconditions.checkArgument(lux.length != 0 && brightness.length != 0,
333 "Lux and brightness arrays must not be empty!");
334 Preconditions.checkArgument(lux.length == brightness.length,
335 "Lux and brightness arrays must be the same length!");
336 Preconditions.checkArrayElementsInRange(lux, 0, Float.MAX_VALUE, "lux");
337 Preconditions.checkArrayElementsInRange(brightness,
338 0, Integer.MAX_VALUE, "brightness");
339
340 final int N = brightness.length;
Michael Wrightd8460232018-01-16 18:04:59 +0000341 mLux = new float[N];
342 mBrightness = new float[N];
Michael Wrighteef0e132017-11-21 17:57:52 +0000343 for (int i = 0; i < N; i++) {
Michael Wrightd8460232018-01-16 18:04:59 +0000344 mLux[i] = lux[i];
345 mBrightness[i] = normalizeAbsoluteBrightness(brightness[i]);
Michael Wrighteef0e132017-11-21 17:57:52 +0000346 }
347
Michael Wrightd8460232018-01-16 18:04:59 +0000348 mSpline = createSpline(mLux, mBrightness);
349 mUserLux = -1;
350 mUserBrightness = -1;
Michael Wrighteef0e132017-11-21 17:57:52 +0000351 }
352
353 @Override
354 public boolean setBrightnessConfiguration(@Nullable BrightnessConfiguration config) {
Michael Wrighteef0e132017-11-21 17:57:52 +0000355 return false;
356 }
357
358 @Override
359 public float getBrightness(float lux) {
360 return mSpline.interpolate(lux);
361 }
362
363 @Override
Michael Wrightd8460232018-01-16 18:04:59 +0000364 public float convertToNits(int backlight) {
Michael Wright144aac92017-12-21 18:37:41 +0000365 return -1.0f;
366 }
367
368 @Override
Michael Wrightd8460232018-01-16 18:04:59 +0000369 public void addUserDataPoint(float lux, float brightness) {
370 if (DEBUG){
371 Slog.d(TAG, "addUserDataPoint(lux=" + lux + ", brightness=" + brightness + ")");
372 }
373 Pair<float[], float[]> curve = insertControlPoint(mLux, mBrightness, lux, brightness);
374 mSpline = createSpline(curve.first, curve.second);
375 mUserLux = lux;
376 mUserBrightness = brightness;
377 }
378
379 @Override
380 public void clearUserDataPoints() {
381 if (mUserLux != -1) {
382 mSpline = createSpline(mLux, mBrightness);
383 mUserLux = -1;
384 mUserBrightness = -1;
385 }
386 }
387
388 @Override
Michael Wrighteef0e132017-11-21 17:57:52 +0000389 public void dump(PrintWriter pw) {
390 pw.println("SimpleMappingStrategy");
391 pw.println(" mSpline=" + mSpline);
Michael Wrightd8460232018-01-16 18:04:59 +0000392 pw.println(" mUserLux=" + mUserLux);
393 pw.println(" mUserBrightness=" + mUserBrightness);
Michael Wrighteef0e132017-11-21 17:57:52 +0000394 }
395 }
396
397 /** A {@link BrightnessMappingStrategy} that maps from ambient room brightness to the physical
398 * range of the display, rather than to the range of the backlight control (typically 0-255).
399 *
400 * By mapping through the physical brightness, the curve becomes portable across devices and
401 * gives us more resolution in the resulting mapping.
402 */
403 @VisibleForTesting
404 static class PhysicalMappingStrategy extends BrightnessMappingStrategy {
405 // The current brightness configuration.
406 private BrightnessConfiguration mConfig;
407
408 // A spline mapping from the current ambient light in lux to the desired display brightness
409 // in nits.
410 private Spline mBrightnessSpline;
411
412 // A spline mapping from nits to the corresponding backlight value, normalized to the range
413 // [0, 1.0].
Michael Wright144aac92017-12-21 18:37:41 +0000414 private final Spline mNitsToBacklightSpline;
415
Michael Wrighteef0e132017-11-21 17:57:52 +0000416 // The default brightness configuration.
417 private final BrightnessConfiguration mDefaultConfig;
418
Michael Wrightd8460232018-01-16 18:04:59 +0000419 // A spline mapping from the device's backlight value, normalized to the range [0, 1.0], to
420 // a brightness in nits.
421 private Spline mBacklightToNitsSpline;
422
423 private float mUserLux;
424 private float mUserBrightness;
425
Michael Wrighteef0e132017-11-21 17:57:52 +0000426 public PhysicalMappingStrategy(BrightnessConfiguration config,
427 float[] nits, int[] backlight) {
428 Preconditions.checkArgument(nits.length != 0 && backlight.length != 0,
429 "Nits and backlight arrays must not be empty!");
430 Preconditions.checkArgument(nits.length == backlight.length,
431 "Nits and backlight arrays must be the same length!");
432 Preconditions.checkNotNull(config);
433 Preconditions.checkArrayElementsInRange(nits, 0, Float.MAX_VALUE, "nits");
434 Preconditions.checkArrayElementsInRange(backlight,
435 PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON, "backlight");
436
Michael Wrightd8460232018-01-16 18:04:59 +0000437 mUserLux = -1;
438 mUserBrightness = -1;
439
Michael Wrighteef0e132017-11-21 17:57:52 +0000440 // Setup the backlight spline
441 final int N = nits.length;
Michael Wright144aac92017-12-21 18:37:41 +0000442 float[] normalizedBacklight = new float[N];
Michael Wrighteef0e132017-11-21 17:57:52 +0000443 for (int i = 0; i < N; i++) {
Michael Wright144aac92017-12-21 18:37:41 +0000444 normalizedBacklight[i] = normalizeAbsoluteBrightness(backlight[i]);
Michael Wrighteef0e132017-11-21 17:57:52 +0000445 }
446
Michael Wrightd8460232018-01-16 18:04:59 +0000447 mNitsToBacklightSpline = createSpline(nits, normalizedBacklight);
448 mBacklightToNitsSpline = createSpline(normalizedBacklight, nits);
Michael Wrighteef0e132017-11-21 17:57:52 +0000449
450 mDefaultConfig = config;
451 setBrightnessConfiguration(config);
452 }
453
454 @Override
455 public boolean setBrightnessConfiguration(@Nullable BrightnessConfiguration config) {
456 if (config == null) {
457 config = mDefaultConfig;
458 }
459 if (config.equals(mConfig)) {
Michael Wrighteef0e132017-11-21 17:57:52 +0000460 return false;
461 }
462
463 Pair<float[], float[]> curve = config.getCurve();
Michael Wrightd8460232018-01-16 18:04:59 +0000464 mBrightnessSpline = createSpline(curve.first /*lux*/, curve.second /*nits*/);
Michael Wrighteef0e132017-11-21 17:57:52 +0000465 mConfig = config;
466 return true;
467 }
468
469 @Override
470 public float getBrightness(float lux) {
Michael Wrightd8460232018-01-16 18:04:59 +0000471 float nits = mBrightnessSpline.interpolate(lux);
472 float backlight = mNitsToBacklightSpline.interpolate(nits);
473 return backlight;
Michael Wright144aac92017-12-21 18:37:41 +0000474 }
475
476 @Override
Michael Wrightd8460232018-01-16 18:04:59 +0000477 public float convertToNits(int backlight) {
Michael Wright144aac92017-12-21 18:37:41 +0000478 return mBacklightToNitsSpline.interpolate(normalizeAbsoluteBrightness(backlight));
Michael Wrighteef0e132017-11-21 17:57:52 +0000479 }
480
481 @Override
Michael Wrightd8460232018-01-16 18:04:59 +0000482 public void addUserDataPoint(float lux, float backlight) {
483 if (DEBUG){
484 Slog.d(TAG, "addUserDataPoint(lux=" + lux + ", backlight=" + backlight + ")");
485 }
486 float brightness = mBacklightToNitsSpline.interpolate(backlight);
487 Pair<float[], float[]> defaultCurve = mConfig.getCurve();
488 Pair<float[], float[]> newCurve =
489 insertControlPoint(defaultCurve.first, defaultCurve.second, lux, brightness);
490 mBrightnessSpline = createSpline(newCurve.first, newCurve.second);
491 mUserLux = lux;
492 mUserBrightness = brightness;
493 }
494
495 @Override
496 public void clearUserDataPoints() {
497 if (mUserLux != -1) {
498 Pair<float[], float[]> defaultCurve = mConfig.getCurve();
499 mBrightnessSpline = createSpline(defaultCurve.first, defaultCurve.second);
500 mUserLux = -1;
501 mUserBrightness = -1;
502 }
503 }
504
505 @Override
Michael Wrighteef0e132017-11-21 17:57:52 +0000506 public void dump(PrintWriter pw) {
507 pw.println("PhysicalMappingStrategy");
508 pw.println(" mConfig=" + mConfig);
509 pw.println(" mBrightnessSpline=" + mBrightnessSpline);
Michael Wright144aac92017-12-21 18:37:41 +0000510 pw.println(" mNitsToBacklightSpline=" + mNitsToBacklightSpline);
Michael Wrightd8460232018-01-16 18:04:59 +0000511 pw.println(" mUserLux=" + mUserLux);
512 pw.println(" mUserBrightness=" + mUserBrightness);
Michael Wrighteef0e132017-11-21 17:57:52 +0000513 }
514 }
515}