DisplayTransformManager.java revision 26a2b97dbe48ee45e9ae70110714048f2f360f97
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
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.MathUtils;
25import android.util.Slog;
26import android.util.SparseArray;
27
28import com.android.internal.annotations.GuardedBy;
29
30import java.util.Arrays;
31
32/**
33 * Manager for applying color transformations to the display.
34 */
35public class DisplayTransformManager {
36
37    private static final String TAG = "DisplayTransformManager";
38
39    /**
40     * Color transform level used by Night display to tint the display red.
41     */
42    public static final int LEVEL_COLOR_MATRIX_NIGHT_DISPLAY = 100;
43    /**
44     * Color transform level used by A11y services to make the display monochromatic.
45     */
46    public static final int LEVEL_COLOR_MATRIX_GRAYSCALE = 200;
47    /**
48     * Color transform level used by A11y services to invert the display colors.
49     */
50    public static final int LEVEL_COLOR_MATRIX_INVERT_COLOR = 300;
51
52    private static final int SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX = 1015;
53    private static final int SURFACE_FLINGER_TRANSACTION_DALTONIZER = 1014;
54    private static final int SURFACE_FLINGER_TRANSACTION_SATURATION = 1022;
55
56    /**
57     * Map of level -> color transformation matrix.
58     */
59    @GuardedBy("mColorMatrix")
60    private final SparseArray<float[]> mColorMatrix = new SparseArray<>(3);
61    /**
62     * Temporary matrix used internally by {@link #computeColorMatrixLocked()}.
63     */
64    @GuardedBy("mColorMatrix")
65    private final float[][] mTempColorMatrix = new float[2][16];
66
67    /**
68     * Lock used for synchronize access to {@link #mDaltonizerMode}.
69     */
70    private final Object mDaltonizerModeLock = new Object();
71    @GuardedBy("mDaltonizerModeLock")
72    private int mDaltonizerMode = -1;
73
74    private final Object mSaturationLock = new Object();
75    @GuardedBy("mSaturationLock")
76    private float mSaturation = 1.0f;
77
78    /* package */ DisplayTransformManager() {
79    }
80
81    /**
82     * Returns a copy of the color transform matrix set for a given level.
83     */
84    public float[] getColorMatrix(int key) {
85        synchronized (mColorMatrix) {
86            final float[] value = mColorMatrix.get(key);
87            return value == null ? null : Arrays.copyOf(value, value.length);
88        }
89    }
90
91    /**
92     * Sets and applies a current color transform matrix for a given level.
93     * <p>
94     * Note: all color transforms are first composed to a single matrix in ascending order based
95     * on level before being applied to the display.
96     *
97     * @param level the level used to identify and compose the color transform (low -> high)
98     * @param value the 4x4 color transform matrix (in column-major order), or {@code null} to
99     *              remove the color transform matrix associated with the provided level
100     */
101    public void setColorMatrix(int level, float[] value) {
102        if (value != null && value.length != 16) {
103            throw new IllegalArgumentException("Expected length: 16 (4x4 matrix)"
104                    + ", actual length: " + value.length);
105        }
106
107        synchronized (mColorMatrix) {
108            final float[] oldValue = mColorMatrix.get(level);
109            if (!Arrays.equals(oldValue, value)) {
110                if (value == null) {
111                    mColorMatrix.remove(level);
112                } else if (oldValue == null) {
113                    mColorMatrix.put(level, Arrays.copyOf(value, value.length));
114                } else {
115                    System.arraycopy(value, 0, oldValue, 0, value.length);
116                }
117
118                // Update the current color transform.
119                applyColorMatrix(computeColorMatrixLocked());
120            }
121        }
122    }
123
124    /**
125     * Returns the composition of all current color matrices, or {@code null} if there are none.
126     */
127    @GuardedBy("mColorMatrix")
128    private float[] computeColorMatrixLocked() {
129        final int count = mColorMatrix.size();
130        if (count == 0) {
131            return null;
132        }
133
134        final float[][] result = mTempColorMatrix;
135        Matrix.setIdentityM(result[0], 0);
136        for (int i = 0; i < count; i++) {
137            float[] rhs = mColorMatrix.valueAt(i);
138            Matrix.multiplyMM(result[(i + 1) % 2], 0, result[i % 2], 0, rhs, 0);
139        }
140        return result[count % 2];
141    }
142
143    /**
144     * Returns the current Daltonization mode.
145     */
146    public int getDaltonizerMode() {
147        synchronized (mDaltonizerModeLock) {
148            return mDaltonizerMode;
149        }
150    }
151
152    /**
153     * Sets the current Daltonization mode. This adjusts the color space to correct for or simulate
154     * various types of color blindness.
155     *
156     * @param mode the new Daltonization mode, or -1 to disable
157     */
158    public void setDaltonizerMode(int mode) {
159        synchronized (mDaltonizerModeLock) {
160            if (mDaltonizerMode != mode) {
161                mDaltonizerMode = mode;
162                applyDaltonizerMode(mode);
163            }
164        }
165    }
166
167    /**
168     * Returns the current saturation.
169     */
170    public float getSaturation() {
171        synchronized (mSaturationLock) {
172            return mSaturation;
173        }
174    }
175
176    /**
177     * Sets the saturation level of the display. The default value is 1.0.
178     *
179     * @param saturation A value between 0 (0% saturation, grayscale) and 2 (100% extra saturation)
180     */
181    public void setSaturation(float saturation) {
182        synchronized (mSaturationLock) {
183            saturation = MathUtils.constrain(saturation, 0.0f, 2.0f);
184            if (mSaturation != saturation) {
185                mSaturation = saturation;
186                applySaturation(saturation);
187            }
188        }
189    }
190
191    /**
192     * Propagates the provided color transformation matrix to the SurfaceFlinger.
193     */
194    private static void applyColorMatrix(float[] m) {
195        final IBinder flinger = ServiceManager.getService("SurfaceFlinger");
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     */
220    private static void applyDaltonizerMode(int mode) {
221        final IBinder flinger = ServiceManager.getService("SurfaceFlinger");
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     * Propagates the provided saturation to the SurfaceFlinger.
238     */
239    private static void applySaturation(float saturation) {
240        final IBinder flinger = ServiceManager.getService("SurfaceFlinger");
241        if (flinger != null) {
242            final Parcel data = Parcel.obtain();
243            data.writeInterfaceToken("android.ui.ISurfaceComposer");
244            data.writeFloat(saturation);
245            try {
246                flinger.transact(SURFACE_FLINGER_TRANSACTION_SATURATION, data, null, 0);
247            } catch (RemoteException ex) {
248                Slog.e(TAG, "Failed to set saturation", ex);
249            } finally {
250                data.recycle();
251            }
252        }
253    }
254}
255