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 invert the display colors.
63      */
64     public static final int LEVEL_COLOR_MATRIX_INVERT_COLOR = 300;
65 
66     private static final int SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX = 1015;
67     private static final int SURFACE_FLINGER_TRANSACTION_DALTONIZER = 1014;
68     /**
69      * SurfaceFlinger global saturation factor.
70      */
71     private static final int SURFACE_FLINGER_TRANSACTION_SATURATION = 1022;
72     /**
73      * SurfaceFlinger display color (managed, unmanaged, etc.).
74      */
75     private static final int SURFACE_FLINGER_TRANSACTION_DISPLAY_COLOR = 1023;
76     private static final int SURFACE_FLINGER_TRANSACTION_QUERY_COLOR_MANAGED = 1030;
77 
78     @VisibleForTesting
79     static final String PERSISTENT_PROPERTY_SATURATION = "persist.sys.sf.color_saturation";
80     @VisibleForTesting
81     static final String PERSISTENT_PROPERTY_COMPOSITION_COLOR_MODE = "persist.sys.sf.color_mode";
82     @VisibleForTesting
83     static final String PERSISTENT_PROPERTY_DISPLAY_COLOR = "persist.sys.sf.native_mode";
84 
85     private static final float COLOR_SATURATION_NATURAL = 1.0f;
86     private static final float COLOR_SATURATION_BOOSTED = 1.1f;
87 
88     /**
89      * Display color modes defined by DisplayColorSetting in
90      * frameworks/native/services/surfaceflinger/SurfaceFlinger.h.
91      */
92     private static final int DISPLAY_COLOR_MANAGED = 0;
93     private static final int DISPLAY_COLOR_UNMANAGED = 1;
94     private static final int DISPLAY_COLOR_ENHANCED = 2;
95 
96     /**
97      * Map of level -> color transformation matrix.
98      */
99     @GuardedBy("mColorMatrix")
100     private final SparseArray<float[]> mColorMatrix = new SparseArray<>(5);
101     /**
102      * Temporary matrix used internally by {@link #computeColorMatrixLocked()}.
103      */
104     @GuardedBy("mColorMatrix")
105     private final float[][] mTempColorMatrix = new float[2][16];
106 
107     /**
108      * Lock used for synchronize access to {@link #mDaltonizerMode}.
109      */
110     private final Object mDaltonizerModeLock = new Object();
111     @GuardedBy("mDaltonizerModeLock")
112     private int mDaltonizerMode = -1;
113 
DisplayTransformManager()114     /* package */ DisplayTransformManager() {
115     }
116 
117     /**
118      * Returns a copy of the color transform matrix set for a given level.
119      */
getColorMatrix(int key)120     public float[] getColorMatrix(int key) {
121         synchronized (mColorMatrix) {
122             final float[] value = mColorMatrix.get(key);
123             return value == null ? null : Arrays.copyOf(value, value.length);
124         }
125     }
126 
127     /**
128      * Sets and applies a current color transform matrix for a given level.
129      * <p>
130      * Note: all color transforms are first composed to a single matrix in ascending order based on
131      * level before being applied to the display.
132      *
133      * @param level the level used to identify and compose the color transform (low -> high)
134      * @param value the 4x4 color transform matrix (in column-major order), or {@code null} to
135      * remove the color transform matrix associated with the provided level
136      */
setColorMatrix(int level, float[] value)137     public void setColorMatrix(int level, float[] value) {
138         if (value != null && value.length != 16) {
139             throw new IllegalArgumentException("Expected length: 16 (4x4 matrix)"
140                     + ", actual length: " + value.length);
141         }
142 
143         synchronized (mColorMatrix) {
144             final float[] oldValue = mColorMatrix.get(level);
145             if (!Arrays.equals(oldValue, value)) {
146                 if (value == null) {
147                     mColorMatrix.remove(level);
148                 } else if (oldValue == null) {
149                     mColorMatrix.put(level, Arrays.copyOf(value, value.length));
150                 } else {
151                     System.arraycopy(value, 0, oldValue, 0, value.length);
152                 }
153 
154                 // Update the current color transform.
155                 applyColorMatrix(computeColorMatrixLocked());
156             }
157         }
158     }
159 
160     /**
161      * Sets the current Daltonization mode. This adjusts the color space to correct for or simulate
162      * various types of color blindness.
163      *
164      * @param mode the new Daltonization mode, or -1 to disable
165      */
setDaltonizerMode(int mode)166     public void setDaltonizerMode(int mode) {
167         synchronized (mDaltonizerModeLock) {
168             if (mDaltonizerMode != mode) {
169                 mDaltonizerMode = mode;
170                 applyDaltonizerMode(mode);
171             }
172         }
173     }
174 
175     /**
176      * Returns the composition of all current color matrices, or {@code null} if there are none.
177      */
178     @GuardedBy("mColorMatrix")
computeColorMatrixLocked()179     private float[] computeColorMatrixLocked() {
180         final int count = mColorMatrix.size();
181         if (count == 0) {
182             return null;
183         }
184 
185         final float[][] result = mTempColorMatrix;
186         Matrix.setIdentityM(result[0], 0);
187         for (int i = 0; i < count; i++) {
188             float[] rhs = mColorMatrix.valueAt(i);
189             Matrix.multiplyMM(result[(i + 1) % 2], 0, result[i % 2], 0, rhs, 0);
190         }
191         return result[count % 2];
192     }
193 
194     /**
195      * Propagates the provided color transformation matrix to the SurfaceFlinger.
196      */
applyColorMatrix(float[] m)197     private static void applyColorMatrix(float[] m) {
198         final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
199         if (flinger != null) {
200             final Parcel data = Parcel.obtain();
201             data.writeInterfaceToken("android.ui.ISurfaceComposer");
202             if (m != null) {
203                 data.writeInt(1);
204                 for (int i = 0; i < 16; i++) {
205                     data.writeFloat(m[i]);
206                 }
207             } else {
208                 data.writeInt(0);
209             }
210             try {
211                 flinger.transact(SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX, data, null, 0);
212             } catch (RemoteException ex) {
213                 Slog.e(TAG, "Failed to set color transform", ex);
214             } finally {
215                 data.recycle();
216             }
217         }
218     }
219 
220     /**
221      * Propagates the provided Daltonization mode to the SurfaceFlinger.
222      */
applyDaltonizerMode(int mode)223     private static void applyDaltonizerMode(int mode) {
224         final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
225         if (flinger != null) {
226             final Parcel data = Parcel.obtain();
227             data.writeInterfaceToken("android.ui.ISurfaceComposer");
228             data.writeInt(mode);
229             try {
230                 flinger.transact(SURFACE_FLINGER_TRANSACTION_DALTONIZER, data, null, 0);
231             } catch (RemoteException ex) {
232                 Slog.e(TAG, "Failed to set Daltonizer mode", ex);
233             } finally {
234                 data.recycle();
235             }
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 IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
290         if (flinger != null) {
291             final Parcel data = Parcel.obtain();
292             final Parcel reply = Parcel.obtain();
293             data.writeInterfaceToken("android.ui.ISurfaceComposer");
294             try {
295                 flinger.transact(SURFACE_FLINGER_TRANSACTION_QUERY_COLOR_MANAGED, data, reply, 0);
296                 return reply.readBoolean();
297             } catch (RemoteException ex) {
298                 Slog.e(TAG, "Failed to query wide color support", ex);
299             } finally {
300                 data.recycle();
301                 reply.recycle();
302             }
303         }
304         return false;
305     }
306 
307     /**
308      * Propagates the provided saturation to the SurfaceFlinger.
309      */
applySaturation(float saturation)310     private void applySaturation(float saturation) {
311         SystemProperties.set(PERSISTENT_PROPERTY_SATURATION, Float.toString(saturation));
312         final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
313         if (flinger != null) {
314             final Parcel data = Parcel.obtain();
315             data.writeInterfaceToken("android.ui.ISurfaceComposer");
316             data.writeFloat(saturation);
317             try {
318                 flinger.transact(SURFACE_FLINGER_TRANSACTION_SATURATION, data, null, 0);
319             } catch (RemoteException ex) {
320                 Slog.e(TAG, "Failed to set saturation", ex);
321             } finally {
322                 data.recycle();
323             }
324         }
325     }
326 
327     /**
328      * Toggles native mode on/off in SurfaceFlinger.
329      */
setDisplayColor(int color, int compositionColorMode)330     private void setDisplayColor(int color, int compositionColorMode) {
331         SystemProperties.set(PERSISTENT_PROPERTY_DISPLAY_COLOR, Integer.toString(color));
332         if (compositionColorMode != Display.COLOR_MODE_INVALID) {
333             SystemProperties.set(PERSISTENT_PROPERTY_COMPOSITION_COLOR_MODE,
334                 Integer.toString(compositionColorMode));
335         }
336 
337         final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
338         if (flinger != null) {
339             final Parcel data = Parcel.obtain();
340             data.writeInterfaceToken("android.ui.ISurfaceComposer");
341             data.writeInt(color);
342             if (compositionColorMode != Display.COLOR_MODE_INVALID) {
343                 data.writeInt(compositionColorMode);
344             }
345             try {
346                 flinger.transact(SURFACE_FLINGER_TRANSACTION_DISPLAY_COLOR, data, null, 0);
347             } catch (RemoteException ex) {
348                 Slog.e(TAG, "Failed to set display color", ex);
349             } finally {
350                 data.recycle();
351             }
352         }
353     }
354 
updateConfiguration()355     private void updateConfiguration() {
356         try {
357             ActivityTaskManager.getService().updateConfiguration(null);
358         } catch (RemoteException e) {
359             Slog.e(TAG, "Could not update configuration", e);
360         }
361     }
362 }
363