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