blob: 6902b1a5cd046a2276d9b0b9a38d72150ba52b03 [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
19import android.opengl.Matrix;
20import android.os.IBinder;
21import android.os.Parcel;
22import android.os.RemoteException;
23import android.os.ServiceManager;
24import android.util.Slog;
25import android.util.SparseArray;
26
Justin Klaassen639214e2016-07-14 21:00:06 -070027import com.android.internal.annotations.GuardedBy;
28
29import java.util.Arrays;
30
Justin Klaassen22eb1992016-07-11 20:52:23 -070031/**
32 * Manager for applying color transformations to the display.
33 */
34public class DisplayTransformManager {
35
36 private static final String TAG = "DisplayTransformManager";
37
38 /**
39 * Color transform level used by Night display to tint the display red.
40 */
41 public static final int LEVEL_COLOR_MATRIX_NIGHT_DISPLAY = 100;
42 /**
43 * Color transform level used by A11y services to make the display monochromatic.
44 */
45 public static final int LEVEL_COLOR_MATRIX_GRAYSCALE = 200;
46 /**
47 * Color transform level used by A11y services to invert the display colors.
48 */
49 public static final int LEVEL_COLOR_MATRIX_INVERT_COLOR = 300;
50
Justin Klaassen639214e2016-07-14 21:00:06 -070051 /**
52 * Map of level -> color transformation matrix.
53 */
54 @GuardedBy("mColorMatrix")
Justin Klaassen22eb1992016-07-11 20:52:23 -070055 private final SparseArray<float[]> mColorMatrix = new SparseArray<>(3);
Justin Klaassen639214e2016-07-14 21:00:06 -070056 /**
57 * Temporary matrix used internally by {@link #computeColorMatrixLocked()}.
58 */
59 @GuardedBy("mColorMatrix")
60 private final float[][] mTempColorMatrix = new float[2][16];
Justin Klaassen22eb1992016-07-11 20:52:23 -070061
Justin Klaassen639214e2016-07-14 21:00:06 -070062 /**
63 * Lock used for synchronize access to {@link #mDaltonizerMode}.
64 */
65 private final Object mDaltonizerModeLock = new Object();
66 @GuardedBy("mDaltonizerModeLock")
Justin Klaassen22eb1992016-07-11 20:52:23 -070067 private int mDaltonizerMode = -1;
68
69 /* package */ DisplayTransformManager() {
70 }
71
72 /**
Justin Klaassen639214e2016-07-14 21:00:06 -070073 * Returns a copy of the color transform matrix set for a given level.
Justin Klaassen22eb1992016-07-11 20:52:23 -070074 */
75 public float[] getColorMatrix(int key) {
76 synchronized (mColorMatrix) {
Justin Klaassen639214e2016-07-14 21:00:06 -070077 final float[] value = mColorMatrix.get(key);
78 return value == null ? null : Arrays.copyOf(value, value.length);
Justin Klaassen22eb1992016-07-11 20:52:23 -070079 }
80 }
81
82 /**
83 * Sets and applies a current color transform matrix for a given level.
84 * <p>
85 * Note: all color transforms are first composed to a single matrix in ascending order based
86 * on level before being applied to the display.
87 *
Justin Klaassen639214e2016-07-14 21:00:06 -070088 * @param level the level used to identify and compose the color transform (low -> high)
Justin Klaassen22eb1992016-07-11 20:52:23 -070089 * @param value the 4x4 color transform matrix (in column-major order), or {@code null} to
90 * remove the color transform matrix associated with the provided level
91 */
Justin Klaassen639214e2016-07-14 21:00:06 -070092 public void setColorMatrix(int level, float[] value) {
Justin Klaassen22eb1992016-07-11 20:52:23 -070093 if (value != null && value.length != 16) {
94 throw new IllegalArgumentException("Expected length: 16 (4x4 matrix)"
95 + ", actual length: " + value.length);
96 }
97
98 synchronized (mColorMatrix) {
Justin Klaassen639214e2016-07-14 21:00:06 -070099 final float[] oldValue = mColorMatrix.get(level);
100 if (!Arrays.equals(oldValue, value)) {
101 if (value == null) {
102 mColorMatrix.remove(level);
103 } else if (oldValue == null) {
104 mColorMatrix.put(level, Arrays.copyOf(value, value.length));
105 } else {
106 System.arraycopy(value, 0, oldValue, 0, value.length);
107 }
Justin Klaassen22eb1992016-07-11 20:52:23 -0700108
Justin Klaassen639214e2016-07-14 21:00:06 -0700109 // Update the current color transform.
110 applyColorMatrix(computeColorMatrixLocked());
111 }
Justin Klaassen22eb1992016-07-11 20:52:23 -0700112 }
113 }
114
115 /**
116 * Returns the composition of all current color matrices, or {@code null} if there are none.
117 */
Justin Klaassen639214e2016-07-14 21:00:06 -0700118 @GuardedBy("mColorMatrix")
119 private float[] computeColorMatrixLocked() {
120 final int count = mColorMatrix.size();
121 if (count == 0) {
122 return null;
Justin Klaassen22eb1992016-07-11 20:52:23 -0700123 }
Justin Klaassen639214e2016-07-14 21:00:06 -0700124
125 final float[][] result = mTempColorMatrix;
126 Matrix.setIdentityM(result[0], 0);
127 for (int i = 0; i < count; i++) {
128 float[] rhs = mColorMatrix.valueAt(i);
129 Matrix.multiplyMM(result[(i + 1) % 2], 0, result[i % 2], 0, rhs, 0);
130 }
131 return result[count % 2];
Justin Klaassen22eb1992016-07-11 20:52:23 -0700132 }
133
134 /**
135 * Returns the current Daltonization mode.
136 */
137 public int getDaltonizerMode() {
Justin Klaassen639214e2016-07-14 21:00:06 -0700138 synchronized (mDaltonizerModeLock) {
139 return mDaltonizerMode;
140 }
Justin Klaassen22eb1992016-07-11 20:52:23 -0700141 }
142
143 /**
144 * Sets the current Daltonization mode. This adjusts the color space to correct for or simulate
145 * various types of color blindness.
146 *
147 * @param mode the new Daltonization mode, or -1 to disable
148 */
149 public void setDaltonizerMode(int mode) {
Justin Klaassen639214e2016-07-14 21:00:06 -0700150 synchronized (mDaltonizerModeLock) {
151 if (mDaltonizerMode != mode) {
152 mDaltonizerMode = mode;
153 applyDaltonizerMode(mode);
154 }
Justin Klaassen22eb1992016-07-11 20:52:23 -0700155 }
156 }
157
158 /**
159 * Propagates the provided color transformation matrix to the SurfaceFlinger.
160 */
161 private static void applyColorMatrix(float[] m) {
162 final IBinder flinger = ServiceManager.getService("SurfaceFlinger");
163 if (flinger != null) {
164 final Parcel data = Parcel.obtain();
165 data.writeInterfaceToken("android.ui.ISurfaceComposer");
166 if (m != null) {
167 data.writeInt(1);
168 for (int i = 0; i < 16; i++) {
169 data.writeFloat(m[i]);
170 }
171 } else {
172 data.writeInt(0);
173 }
174 try {
175 flinger.transact(1015, data, null, 0);
176 } catch (RemoteException ex) {
177 Slog.e(TAG, "Failed to set color transform", ex);
178 } finally {
179 data.recycle();
180 }
181 }
182 }
183
184 /**
185 * Propagates the provided Daltonization mode to the SurfaceFlinger.
186 */
187 private static void applyDaltonizerMode(int mode) {
188 final IBinder flinger = ServiceManager.getService("SurfaceFlinger");
189 if (flinger != null) {
190 final Parcel data = Parcel.obtain();
191 data.writeInterfaceToken("android.ui.ISurfaceComposer");
192 data.writeInt(mode);
193 try {
194 flinger.transact(1014, data, null, 0);
195 } catch (RemoteException ex) {
196 Slog.e(TAG, "Failed to set Daltonizer mode", ex);
197 } finally {
198 data.recycle();
199 }
200 }
201 }
202}