blob: a94f049f7ef82785a86c54c3a4f7be108847883e [file] [log] [blame]
Justin Klaassen22eb1992016-07-11 20:52:23 -07001/*
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
17package com.android.server.display;
18
Christine Franks8ad71492017-10-24 19:04:22 -070019import android.app.ActivityManager;
Justin Klaassen22eb1992016-07-11 20:52:23 -070020import android.opengl.Matrix;
21import android.os.IBinder;
22import android.os.Parcel;
23import android.os.RemoteException;
24import android.os.ServiceManager;
Christine Franks8ad71492017-10-24 19:04:22 -070025import android.os.SystemProperties;
26import android.util.Log;
Justin Klaassen22eb1992016-07-11 20:52:23 -070027import android.util.Slog;
28import android.util.SparseArray;
Justin Klaassen639214e2016-07-14 21:00:06 -070029import com.android.internal.annotations.GuardedBy;
Christine Franks5397f032017-11-01 18:35:16 -070030import com.android.internal.app.ColorDisplayController;
Justin Klaassen639214e2016-07-14 21:00:06 -070031import java.util.Arrays;
32
Justin Klaassen22eb1992016-07-11 20:52:23 -070033/**
34 * Manager for applying color transformations to the display.
35 */
36public class DisplayTransformManager {
37
38 private static final String TAG = "DisplayTransformManager";
39
Christine Franks8ad71492017-10-24 19:04:22 -070040 private static final String SURFACE_FLINGER = "SurfaceFlinger";
41
Justin Klaassen22eb1992016-07-11 20:52:23 -070042 /**
43 * Color transform level used by Night display to tint the display red.
44 */
45 public static final int LEVEL_COLOR_MATRIX_NIGHT_DISPLAY = 100;
46 /**
Bryan Mawhinney462e29d2018-03-22 15:52:41 +000047 * Color transform level used to adjust the color saturation of the display.
48 */
49 public static final int LEVEL_COLOR_MATRIX_SATURATION = 150;
50 /**
Justin Klaassen22eb1992016-07-11 20:52:23 -070051 * Color transform level used by A11y services to make the display monochromatic.
52 */
53 public static final int LEVEL_COLOR_MATRIX_GRAYSCALE = 200;
54 /**
55 * Color transform level used by A11y services to invert the display colors.
56 */
57 public static final int LEVEL_COLOR_MATRIX_INVERT_COLOR = 300;
58
Romain Guy26a2b972017-04-17 09:39:51 -070059 private static final int SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX = 1015;
60 private static final int SURFACE_FLINGER_TRANSACTION_DALTONIZER = 1014;
Romain Guy26a2b972017-04-17 09:39:51 -070061
Christine Franks8ad71492017-10-24 19:04:22 -070062 private static final String PERSISTENT_PROPERTY_SATURATION = "persist.sys.sf.color_saturation";
63 private static final String PERSISTENT_PROPERTY_NATIVE_MODE = "persist.sys.sf.native_mode";
64
65 private static final int SURFACE_FLINGER_TRANSACTION_SATURATION = 1022;
66 private static final int SURFACE_FLINGER_TRANSACTION_NATIVE_MODE = 1023;
67
68 private static final float COLOR_SATURATION_NATURAL = 1.0f;
69 private static final float COLOR_SATURATION_BOOSTED = 1.1f;
70
Justin Klaassen639214e2016-07-14 21:00:06 -070071 /**
72 * Map of level -> color transformation matrix.
73 */
74 @GuardedBy("mColorMatrix")
Justin Klaassen22eb1992016-07-11 20:52:23 -070075 private final SparseArray<float[]> mColorMatrix = new SparseArray<>(3);
Justin Klaassen639214e2016-07-14 21:00:06 -070076 /**
77 * Temporary matrix used internally by {@link #computeColorMatrixLocked()}.
78 */
79 @GuardedBy("mColorMatrix")
80 private final float[][] mTempColorMatrix = new float[2][16];
Justin Klaassen22eb1992016-07-11 20:52:23 -070081
Justin Klaassen639214e2016-07-14 21:00:06 -070082 /**
83 * Lock used for synchronize access to {@link #mDaltonizerMode}.
84 */
85 private final Object mDaltonizerModeLock = new Object();
86 @GuardedBy("mDaltonizerModeLock")
Justin Klaassen22eb1992016-07-11 20:52:23 -070087 private int mDaltonizerMode = -1;
88
89 /* package */ DisplayTransformManager() {
90 }
91
92 /**
Justin Klaassen639214e2016-07-14 21:00:06 -070093 * Returns a copy of the color transform matrix set for a given level.
Justin Klaassen22eb1992016-07-11 20:52:23 -070094 */
95 public float[] getColorMatrix(int key) {
96 synchronized (mColorMatrix) {
Justin Klaassen639214e2016-07-14 21:00:06 -070097 final float[] value = mColorMatrix.get(key);
98 return value == null ? null : Arrays.copyOf(value, value.length);
Justin Klaassen22eb1992016-07-11 20:52:23 -070099 }
100 }
101
102 /**
103 * Sets and applies a current color transform matrix for a given level.
104 * <p>
105 * Note: all color transforms are first composed to a single matrix in ascending order based
106 * on level before being applied to the display.
107 *
Justin Klaassen639214e2016-07-14 21:00:06 -0700108 * @param level the level used to identify and compose the color transform (low -> high)
Justin Klaassen22eb1992016-07-11 20:52:23 -0700109 * @param value the 4x4 color transform matrix (in column-major order), or {@code null} to
110 * remove the color transform matrix associated with the provided level
111 */
Justin Klaassen639214e2016-07-14 21:00:06 -0700112 public void setColorMatrix(int level, float[] value) {
Justin Klaassen22eb1992016-07-11 20:52:23 -0700113 if (value != null && value.length != 16) {
114 throw new IllegalArgumentException("Expected length: 16 (4x4 matrix)"
115 + ", actual length: " + value.length);
116 }
117
118 synchronized (mColorMatrix) {
Justin Klaassen639214e2016-07-14 21:00:06 -0700119 final float[] oldValue = mColorMatrix.get(level);
120 if (!Arrays.equals(oldValue, value)) {
121 if (value == null) {
122 mColorMatrix.remove(level);
123 } else if (oldValue == null) {
124 mColorMatrix.put(level, Arrays.copyOf(value, value.length));
125 } else {
126 System.arraycopy(value, 0, oldValue, 0, value.length);
127 }
Justin Klaassen22eb1992016-07-11 20:52:23 -0700128
Justin Klaassen639214e2016-07-14 21:00:06 -0700129 // Update the current color transform.
130 applyColorMatrix(computeColorMatrixLocked());
131 }
Justin Klaassen22eb1992016-07-11 20:52:23 -0700132 }
133 }
134
135 /**
136 * Returns the composition of all current color matrices, or {@code null} if there are none.
137 */
Justin Klaassen639214e2016-07-14 21:00:06 -0700138 @GuardedBy("mColorMatrix")
139 private float[] computeColorMatrixLocked() {
140 final int count = mColorMatrix.size();
141 if (count == 0) {
142 return null;
Justin Klaassen22eb1992016-07-11 20:52:23 -0700143 }
Justin Klaassen639214e2016-07-14 21:00:06 -0700144
145 final float[][] result = mTempColorMatrix;
146 Matrix.setIdentityM(result[0], 0);
147 for (int i = 0; i < count; i++) {
148 float[] rhs = mColorMatrix.valueAt(i);
149 Matrix.multiplyMM(result[(i + 1) % 2], 0, result[i % 2], 0, rhs, 0);
150 }
151 return result[count % 2];
Justin Klaassen22eb1992016-07-11 20:52:23 -0700152 }
153
154 /**
155 * Returns the current Daltonization mode.
156 */
157 public int getDaltonizerMode() {
Justin Klaassen639214e2016-07-14 21:00:06 -0700158 synchronized (mDaltonizerModeLock) {
159 return mDaltonizerMode;
160 }
Justin Klaassen22eb1992016-07-11 20:52:23 -0700161 }
162
163 /**
164 * Sets the current Daltonization mode. This adjusts the color space to correct for or simulate
165 * various types of color blindness.
166 *
167 * @param mode the new Daltonization mode, or -1 to disable
168 */
169 public void setDaltonizerMode(int mode) {
Justin Klaassen639214e2016-07-14 21:00:06 -0700170 synchronized (mDaltonizerModeLock) {
171 if (mDaltonizerMode != mode) {
172 mDaltonizerMode = mode;
173 applyDaltonizerMode(mode);
174 }
Justin Klaassen22eb1992016-07-11 20:52:23 -0700175 }
176 }
177
178 /**
179 * Propagates the provided color transformation matrix to the SurfaceFlinger.
180 */
181 private static void applyColorMatrix(float[] m) {
Christine Franks8ad71492017-10-24 19:04:22 -0700182 final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
Justin Klaassen22eb1992016-07-11 20:52:23 -0700183 if (flinger != null) {
184 final Parcel data = Parcel.obtain();
185 data.writeInterfaceToken("android.ui.ISurfaceComposer");
186 if (m != null) {
187 data.writeInt(1);
188 for (int i = 0; i < 16; i++) {
189 data.writeFloat(m[i]);
190 }
191 } else {
192 data.writeInt(0);
193 }
194 try {
Romain Guy26a2b972017-04-17 09:39:51 -0700195 flinger.transact(SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX, data, null, 0);
Justin Klaassen22eb1992016-07-11 20:52:23 -0700196 } catch (RemoteException ex) {
197 Slog.e(TAG, "Failed to set color transform", ex);
198 } finally {
199 data.recycle();
200 }
201 }
202 }
203
204 /**
205 * Propagates the provided Daltonization mode to the SurfaceFlinger.
206 */
207 private static void applyDaltonizerMode(int mode) {
Christine Franks8ad71492017-10-24 19:04:22 -0700208 final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
Justin Klaassen22eb1992016-07-11 20:52:23 -0700209 if (flinger != null) {
210 final Parcel data = Parcel.obtain();
211 data.writeInterfaceToken("android.ui.ISurfaceComposer");
212 data.writeInt(mode);
213 try {
Romain Guy26a2b972017-04-17 09:39:51 -0700214 flinger.transact(SURFACE_FLINGER_TRANSACTION_DALTONIZER, data, null, 0);
Justin Klaassen22eb1992016-07-11 20:52:23 -0700215 } catch (RemoteException ex) {
216 Slog.e(TAG, "Failed to set Daltonizer mode", ex);
217 } finally {
218 data.recycle();
219 }
220 }
221 }
Christine Franks8ad71492017-10-24 19:04:22 -0700222
223 public static boolean isNativeModeEnabled() {
224 return SystemProperties.getBoolean(PERSISTENT_PROPERTY_NATIVE_MODE, false);
225 }
226
Christine Franks218e6562017-11-27 10:20:14 -0800227 public boolean setColorMode(int colorMode, float[] nightDisplayMatrix) {
Christine Franks5397f032017-11-01 18:35:16 -0700228 if (colorMode == ColorDisplayController.COLOR_MODE_NATURAL) {
Christine Franks8ad71492017-10-24 19:04:22 -0700229 applySaturation(COLOR_SATURATION_NATURAL);
230 setNativeMode(false);
Christine Franks5397f032017-11-01 18:35:16 -0700231 } else if (colorMode == ColorDisplayController.COLOR_MODE_BOOSTED) {
Christine Franks8ad71492017-10-24 19:04:22 -0700232 applySaturation(COLOR_SATURATION_BOOSTED);
233 setNativeMode(false);
Christine Franks5397f032017-11-01 18:35:16 -0700234 } else if (colorMode == ColorDisplayController.COLOR_MODE_SATURATED) {
Christine Franks8ad71492017-10-24 19:04:22 -0700235 applySaturation(COLOR_SATURATION_NATURAL);
236 setNativeMode(true);
237 }
Christine Franks218e6562017-11-27 10:20:14 -0800238 setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, nightDisplayMatrix);
Christine Franks8ad71492017-10-24 19:04:22 -0700239
240 updateConfiguration();
241
242 return true;
243 }
244
245 /**
246 * Propagates the provided saturation to the SurfaceFlinger.
247 */
248 private void applySaturation(float saturation) {
249 SystemProperties.set(PERSISTENT_PROPERTY_SATURATION, Float.toString(saturation));
250 final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
251 if (flinger != null) {
252 final Parcel data = Parcel.obtain();
253 data.writeInterfaceToken("android.ui.ISurfaceComposer");
254 data.writeFloat(saturation);
255 try {
256 flinger.transact(SURFACE_FLINGER_TRANSACTION_SATURATION, data, null, 0);
257 } catch (RemoteException ex) {
258 Log.e(TAG, "Failed to set saturation", ex);
259 } finally {
260 data.recycle();
261 }
262 }
263 }
264
265 /**
266 * Toggles native mode on/off in SurfaceFlinger.
267 */
268 private void setNativeMode(boolean enabled) {
269 SystemProperties.set(PERSISTENT_PROPERTY_NATIVE_MODE, enabled ? "1" : "0");
270 final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
271 if (flinger != null) {
272 final Parcel data = Parcel.obtain();
273 data.writeInterfaceToken("android.ui.ISurfaceComposer");
274 data.writeInt(enabled ? 1 : 0);
275 try {
276 flinger.transact(SURFACE_FLINGER_TRANSACTION_NATIVE_MODE, data, null, 0);
277 } catch (RemoteException ex) {
278 Log.e(TAG, "Failed to set native mode", ex);
279 } finally {
280 data.recycle();
281 }
282 }
283 }
284
285 private void updateConfiguration() {
286 try {
287 ActivityManager.getService().updateConfiguration(null);
288 } catch (RemoteException e) {
289 Log.e(TAG, "Could not update configuration", e);
290 }
291 }
Justin Klaassen22eb1992016-07-11 20:52:23 -0700292}