1 /*
2  * Copyright (C) 2016 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 
17 package com.android.server.display.color;
18 
19 import android.annotation.IntRange;
20 import android.app.ActivityTaskManager;
21 import android.hardware.display.ColorDisplayManager;
22 import android.opengl.Matrix;
23 import android.os.IBinder;
24 import android.os.Parcel;
25 import android.os.RemoteException;
26 import android.os.ServiceManager;
27 import android.os.SystemProperties;
28 import android.util.Slog;
29 import android.util.SparseArray;
30 import android.view.Display;
31 
32 import com.android.internal.annotations.GuardedBy;
33 import com.android.internal.annotations.VisibleForTesting;
34 
35 import java.util.Arrays;
36 
37 /**
38  * Manager for applying color transformations to the display.
39  */
40 public class DisplayTransformManager {
41 
42     private static final String TAG = "DisplayTransformManager";
43 
44     private static final String SURFACE_FLINGER = "SurfaceFlinger";
45 
46     /**
47      * Color transform level used by Night display to tint the display red.
48      */
49     public static final int LEVEL_COLOR_MATRIX_NIGHT_DISPLAY = 100;
50     /**
51      * Color transform level used by display white balance to adjust the display's white point.
52      */
53     public static final int LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE = 125;
54     /**
55      * Color transform level used to adjust the color saturation of the display.
56      */
57     public static final int LEVEL_COLOR_MATRIX_SATURATION = 150;
58     /**
59      * Color transform level used by A11y services to make the display monochromatic.
60      */
61     public static final int LEVEL_COLOR_MATRIX_GRAYSCALE = 200;
62     /**
63      * Color transform level used by A11y services to reduce bright colors.
64      */
65     public static final int LEVEL_COLOR_MATRIX_REDUCE_BRIGHT_COLORS = 250;
66     /**
67      * Color transform level used by A11y services to invert the display colors.
68      */
69     public static final int LEVEL_COLOR_MATRIX_INVERT_COLOR = 300;
70 
71     private static final int SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX = 1015;
72     private static final int SURFACE_FLINGER_TRANSACTION_DALTONIZER = 1014;
73     /**
74      * SurfaceFlinger global saturation factor.
75      */
76     private static final int SURFACE_FLINGER_TRANSACTION_SATURATION = 1022;
77     /**
78      * SurfaceFlinger display color (managed, unmanaged, etc.).
79      */
80     private static final int SURFACE_FLINGER_TRANSACTION_DISPLAY_COLOR = 1023;
81     private static final int SURFACE_FLINGER_TRANSACTION_QUERY_COLOR_MANAGED = 1030;
82 
83     @VisibleForTesting
84     static final String PERSISTENT_PROPERTY_SATURATION = "persist.sys.sf.color_saturation";
85     @VisibleForTesting
86     static final String PERSISTENT_PROPERTY_COMPOSITION_COLOR_MODE = "persist.sys.sf.color_mode";
87     @VisibleForTesting
88     static final String PERSISTENT_PROPERTY_DISPLAY_COLOR = "persist.sys.sf.native_mode";
89 
90     private static final float COLOR_SATURATION_NATURAL = 1.0f;
91     private static final float COLOR_SATURATION_BOOSTED = 1.1f;
92 
93     /**
94      * Display color modes defined by DisplayColorSetting in
95      * frameworks/native/services/surfaceflinger/SurfaceFlinger.h.
96      */
97     private static final int DISPLAY_COLOR_MANAGED = 0;
98     private static final int DISPLAY_COLOR_UNMANAGED = 1;
99     private static final int DISPLAY_COLOR_ENHANCED = 2;
100 
101     /**
102      * Map of level -> color transformation matrix.
103      */
104     @GuardedBy("mColorMatrix")
105     private final SparseArray<float[]> mColorMatrix = new SparseArray<>(6);
106     /**
107      * Temporary matrix used internally by {@link #computeColorMatrixLocked()}.
108      */
109     @GuardedBy("mColorMatrix")
110     private final float[][] mTempColorMatrix = new float[2][16];
111 
112     /**
113      * Lock used for synchronize access to {@link #mDaltonizerMode}.
114      */
115     @VisibleForTesting
116     final Object mDaltonizerModeLock = new Object();
117     @VisibleForTesting
118     @GuardedBy("mDaltonizerModeLock")
119     int mDaltonizerMode = -1;
120 
121     @VisibleForTesting
122     @GuardedBy("mDaltonizerModeLock")
123     int mDaltonizerLevel = -1;
124 
125     private static final IBinder sFlinger = ServiceManager.getService(SURFACE_FLINGER);
126 
DisplayTransformManager()127     /* package */ DisplayTransformManager() {
128     }
129 
130     /**
131      * Returns a copy of the color transform matrix set for a given level.
132      */
getColorMatrix(int key)133     public float[] getColorMatrix(int key) {
134         synchronized (mColorMatrix) {
135             final float[] value = mColorMatrix.get(key);
136             return value == null ? null : Arrays.copyOf(value, value.length);
137         }
138     }
139 
140     /**
141      * Sets and applies a current color transform matrix for a given level.
142      * <p>
143      * Note: all color transforms are first composed to a single matrix in ascending order based on
144      * level before being applied to the display.
145      *
146      * @param level the level used to identify and compose the color transform (low -> high)
147      * @param value the 4x4 color transform matrix (in column-major order), or {@code null} to
148      * remove the color transform matrix associated with the provided level
149      */
setColorMatrix(int level, float[] value)150     public void setColorMatrix(int level, float[] value) {
151         if (value != null && value.length != 16) {
152             throw new IllegalArgumentException("Expected length: 16 (4x4 matrix)"
153                     + ", actual length: " + value.length);
154         }
155 
156         synchronized (mColorMatrix) {
157             final float[] oldValue = mColorMatrix.get(level);
158             if (!Arrays.equals(oldValue, value)) {
159                 if (value == null) {
160                     mColorMatrix.remove(level);
161                 } else if (oldValue == null) {
162                     mColorMatrix.put(level, Arrays.copyOf(value, value.length));
163                 } else {
164                     System.arraycopy(value, 0, oldValue, 0, value.length);
165                 }
166 
167                 // Update the current color transform.
168                 applyColorMatrix(computeColorMatrixLocked());
169             }
170         }
171     }
172 
173     /**
174      * Sets the current Daltonization mode. This adjusts the color space to correct for or simulate
175      * various types of color blindness.
176      *
177      * @param mode the new Daltonization mode, or -1 to disable
178      * @param level the level of saturation for color correction [-1,10] inclusive. -1 for when
179      *              it is not set.
180      */
setDaltonizerMode(int mode, @IntRange(from = -1, to = 10) int level)181     public void setDaltonizerMode(int mode, @IntRange(from = -1, to = 10) int level) {
182         synchronized (mDaltonizerModeLock) {
183             if (mDaltonizerMode != mode || mDaltonizerLevel != level) {
184                 mDaltonizerMode = mode;
185                 mDaltonizerLevel = level;
186                 applyDaltonizerMode(mode, level);
187             }
188         }
189     }
190 
191     /**
192      * Returns the composition of all current color matrices, or {@code null} if there are none.
193      */
194     @GuardedBy("mColorMatrix")
computeColorMatrixLocked()195     private float[] computeColorMatrixLocked() {
196         final int count = mColorMatrix.size();
197         if (count == 0) {
198             return null;
199         }
200 
201         final float[][] result = mTempColorMatrix;
202         Matrix.setIdentityM(result[0], 0);
203         for (int i = 0; i < count; i++) {
204             float[] rhs = mColorMatrix.valueAt(i);
205             Matrix.multiplyMM(result[(i + 1) % 2], 0, result[i % 2], 0, rhs, 0);
206         }
207         return result[count % 2];
208     }
209 
210     /**
211      * Propagates the provided color transformation matrix to the SurfaceFlinger.
212      */
applyColorMatrix(float[] m)213     private static void applyColorMatrix(float[] m) {
214         final Parcel data = Parcel.obtain();
215         data.writeInterfaceToken("android.ui.ISurfaceComposer");
216         if (m != null) {
217             data.writeInt(1);
218             for (int i = 0; i < 16; i++) {
219                 data.writeFloat(m[i]);
220             }
221         } else {
222             data.writeInt(0);
223         }
224         try {
225             sFlinger.transact(SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX, data, null, 0);
226         } catch (RemoteException ex) {
227             Slog.e(TAG, "Failed to set color transform", ex);
228         } finally {
229             data.recycle();
230         }
231     }
232 
233     /**
234      * Propagates the provided Daltonization mode to the SurfaceFlinger.
235      */
applyDaltonizerMode(int mode, int level)236     private static void applyDaltonizerMode(int mode, int level) {
237         final Parcel data = Parcel.obtain();
238         data.writeInterfaceToken("android.ui.ISurfaceComposer");
239         data.writeInt(mode);
240         data.writeInt(level);
241         try {
242             sFlinger.transact(SURFACE_FLINGER_TRANSACTION_DALTONIZER, data, null, 0);
243         } catch (RemoteException ex) {
244             Slog.e(TAG, "Failed to set Daltonizer mode", ex);
245         } finally {
246             data.recycle();
247         }
248     }
249 
250     /**
251      * Return true when the color matrix works in linear space.
252      */
needsLinearColorMatrix()253     public boolean needsLinearColorMatrix() {
254         return SystemProperties.getInt(PERSISTENT_PROPERTY_DISPLAY_COLOR,
255                 DISPLAY_COLOR_UNMANAGED) != DISPLAY_COLOR_UNMANAGED;
256     }
257 
258     /**
259      * Return true when the specified colorMode requires the color matrix to work in linear space.
260      */
needsLinearColorMatrix(int colorMode)261     public boolean needsLinearColorMatrix(int colorMode) {
262         return colorMode != ColorDisplayManager.COLOR_MODE_SATURATED;
263     }
264 
265     /**
266      * Sets color mode and updates night display transform values.
267      */
setColorMode(int colorMode, float[] nightDisplayMatrix, int compositionColorMode)268     public boolean setColorMode(int colorMode, float[] nightDisplayMatrix,
269             int compositionColorMode) {
270         if (colorMode == ColorDisplayManager.COLOR_MODE_NATURAL) {
271             applySaturation(COLOR_SATURATION_NATURAL);
272             setDisplayColor(DISPLAY_COLOR_MANAGED, compositionColorMode);
273         } else if (colorMode == ColorDisplayManager.COLOR_MODE_BOOSTED) {
274             applySaturation(COLOR_SATURATION_BOOSTED);
275             setDisplayColor(DISPLAY_COLOR_MANAGED, compositionColorMode);
276         } else if (colorMode == ColorDisplayManager.COLOR_MODE_SATURATED) {
277             applySaturation(COLOR_SATURATION_NATURAL);
278             setDisplayColor(DISPLAY_COLOR_UNMANAGED, compositionColorMode);
279         } else if (colorMode == ColorDisplayManager.COLOR_MODE_AUTOMATIC) {
280             applySaturation(COLOR_SATURATION_NATURAL);
281             setDisplayColor(DISPLAY_COLOR_ENHANCED, compositionColorMode);
282         } else if (colorMode >= ColorDisplayManager.VENDOR_COLOR_MODE_RANGE_MIN
283                 && colorMode <= ColorDisplayManager.VENDOR_COLOR_MODE_RANGE_MAX) {
284             applySaturation(COLOR_SATURATION_NATURAL);
285             setDisplayColor(colorMode, compositionColorMode);
286         }
287 
288         setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, nightDisplayMatrix);
289 
290         updateConfiguration();
291 
292         return true;
293     }
294 
295     /**
296      * Returns whether the screen is color managed via SurfaceFlinger's {@link
297      * #SURFACE_FLINGER_TRANSACTION_QUERY_COLOR_MANAGED}.
298      */
isDeviceColorManaged()299     public boolean isDeviceColorManaged() {
300         final Parcel data = Parcel.obtain();
301         final Parcel reply = Parcel.obtain();
302         data.writeInterfaceToken("android.ui.ISurfaceComposer");
303         try {
304             sFlinger.transact(SURFACE_FLINGER_TRANSACTION_QUERY_COLOR_MANAGED, data, reply, 0);
305             return reply.readBoolean();
306         } catch (RemoteException ex) {
307             Slog.e(TAG, "Failed to query wide color support", ex);
308         } finally {
309             data.recycle();
310             reply.recycle();
311         }
312         return false;
313     }
314 
315     /**
316      * Propagates the provided saturation to the SurfaceFlinger.
317      */
applySaturation(float saturation)318     private void applySaturation(float saturation) {
319         SystemProperties.set(PERSISTENT_PROPERTY_SATURATION, Float.toString(saturation));
320         final Parcel data = Parcel.obtain();
321         data.writeInterfaceToken("android.ui.ISurfaceComposer");
322         data.writeFloat(saturation);
323         try {
324             sFlinger.transact(SURFACE_FLINGER_TRANSACTION_SATURATION, data, null, 0);
325         } catch (RemoteException ex) {
326             Slog.e(TAG, "Failed to set saturation", ex);
327         } finally {
328             data.recycle();
329         }
330     }
331 
332     /**
333      * Toggles native mode on/off in SurfaceFlinger.
334      */
setDisplayColor(int color, int compositionColorMode)335     private void setDisplayColor(int color, int compositionColorMode) {
336         SystemProperties.set(PERSISTENT_PROPERTY_DISPLAY_COLOR, Integer.toString(color));
337         if (compositionColorMode != Display.COLOR_MODE_INVALID) {
338             SystemProperties.set(PERSISTENT_PROPERTY_COMPOSITION_COLOR_MODE,
339                 Integer.toString(compositionColorMode));
340         }
341 
342         final Parcel data = Parcel.obtain();
343         data.writeInterfaceToken("android.ui.ISurfaceComposer");
344         data.writeInt(color);
345         if (compositionColorMode != Display.COLOR_MODE_INVALID) {
346             data.writeInt(compositionColorMode);
347         }
348         try {
349             sFlinger.transact(SURFACE_FLINGER_TRANSACTION_DISPLAY_COLOR, data, null, 0);
350         } catch (RemoteException ex) {
351             Slog.e(TAG, "Failed to set display color", ex);
352         } finally {
353             data.recycle();
354         }
355     }
356 
updateConfiguration()357     private void updateConfiguration() {
358         try {
359             ActivityTaskManager.getService().updateConfiguration(null);
360         } catch (RemoteException e) {
361             Slog.e(TAG, "Could not update configuration", e);
362         }
363     }
364 }
365