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