DisplayAdjustmentUtils.java revision 19c662b3df3b35756a92282bb6cc767e6407cb8a
1/*
2 * Copyright (C) 2013 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.accessibility;
18
19import android.content.ContentResolver;
20import android.content.Context;
21import android.opengl.Matrix;
22import android.os.IBinder;
23import android.os.Parcel;
24import android.os.RemoteException;
25import android.os.ServiceManager;
26import android.provider.Settings;
27import android.util.Slog;
28import android.view.accessibility.AccessibilityManager;
29
30/**
31 * Utility methods for performing accessibility display adjustments.
32 */
33class DisplayAdjustmentUtils {
34    private static final String LOG_TAG = DisplayAdjustmentUtils.class.getSimpleName();
35
36    /** Matrix and offset used for converting color to gray-scale. */
37    private static final float[] GRAYSCALE_MATRIX = new float[] {
38        .2126f, .2126f, .2126f, 0,
39        .7152f, .7152f, .7152f, 0,
40        .0722f, .0722f, .0722f, 0,
41             0,      0,      0, 1
42    };
43
44    /** Matrix and offset used for standard display inversion. */
45    private static final float[] INVERSION_MATRIX_STANDARD = new float[] {
46        -1,  0,  0, 0,
47         0, -1,  0, 0,
48         0,  0, -1, 0,
49         1,  1,  1, 1
50    };
51
52    /** Matrix and offset used for hue-only display inversion. */
53    private static final float[] INVERSION_MATRIX_HUE_ONLY = new float[] {
54          0, .5f, .5f, 0,
55        .5f,   0, .5f, 0,
56        .5f, .5f,   0, 0,
57          0,   0,   0, 1
58    };
59
60    /** Matrix and offset used for value-only display inversion. */
61    private static final float[] INVERSION_MATRIX_VALUE_ONLY = new float[] {
62           0, -.5f, -.5f, 0,
63        -.5f,    0, -.5f, 0,
64        -.5f, -.5f,    0, 0,
65           1,    1,    1, 1
66    };
67
68    /** Default contrast for display contrast enhancement. */
69    private static final float DEFAULT_DISPLAY_CONTRAST = 2;
70
71    /** Default brightness for display contrast enhancement. */
72    private static final float DEFAULT_DISPLAY_BRIGHTNESS = 0;
73
74    /** Default inversion mode for display color inversion. */
75    private static final int DEFAULT_DISPLAY_INVERSION = AccessibilityManager.INVERSION_STANDARD;
76
77    /** Default inversion mode for display color correction. */
78    private static final int DEFAULT_DISPLAY_DALTONIZER =
79            AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY;
80
81    /**
82     * Returns whether the specified user with has any display color
83     * adjustments.
84     */
85    public static boolean hasAdjustments(Context context, int userId) {
86        final ContentResolver cr = context.getContentResolver();
87
88        boolean hasColorTransform = Settings.Secure.getIntForUser(
89                cr, Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0, userId) == 1;
90
91        if (!hasColorTransform) {
92            hasColorTransform |= Settings.Secure.getIntForUser(
93                cr, Settings.Secure.ACCESSIBILITY_DISPLAY_CONTRAST_ENABLED, 0, userId) == 1;
94        }
95
96        if (!hasColorTransform) {
97            hasColorTransform |= Settings.Secure.getIntForUser(
98                cr, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0, userId) == 1;
99        }
100
101        return hasColorTransform;
102    }
103
104    /**
105     * Applies the specified user's display color adjustments.
106     */
107    public static void applyAdjustments(Context context, int userId) {
108        final ContentResolver cr = context.getContentResolver();
109        float[] colorMatrix = new float[16];
110        float[] outputMatrix = new float[16];
111        boolean hasColorTransform = false;
112
113        Matrix.setIdentityM(colorMatrix, 0);
114
115        final boolean inversionEnabled = Settings.Secure.getIntForUser(
116                cr, Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0, userId) == 1;
117        if (inversionEnabled) {
118            final int inversionMode = Settings.Secure.getIntForUser(cr,
119                    Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION, DEFAULT_DISPLAY_INVERSION,
120                    userId);
121            final float[] inversionMatrix;
122            switch (inversionMode) {
123                case AccessibilityManager.INVERSION_HUE_ONLY:
124                    inversionMatrix = INVERSION_MATRIX_HUE_ONLY;
125                    break;
126                case AccessibilityManager.INVERSION_VALUE_ONLY:
127                    inversionMatrix = INVERSION_MATRIX_VALUE_ONLY;
128                    break;
129                default:
130                    inversionMatrix = INVERSION_MATRIX_STANDARD;
131            }
132
133            Matrix.multiplyMM(outputMatrix, 0, colorMatrix, 0, inversionMatrix, 0);
134
135            colorMatrix = outputMatrix;
136            outputMatrix = colorMatrix;
137
138            hasColorTransform = true;
139        }
140
141        final boolean contrastEnabled = Settings.Secure.getIntForUser(
142                cr, Settings.Secure.ACCESSIBILITY_DISPLAY_CONTRAST_ENABLED, 0, userId) == 1;
143        if (contrastEnabled) {
144            final float contrast = Settings.Secure.getFloatForUser(cr,
145                    Settings.Secure.ACCESSIBILITY_DISPLAY_CONTRAST, DEFAULT_DISPLAY_CONTRAST,
146                    userId);
147            final float brightness = Settings.Secure.getFloatForUser(cr,
148                    Settings.Secure.ACCESSIBILITY_DISPLAY_BRIGHTNESS, DEFAULT_DISPLAY_BRIGHTNESS,
149                    userId);
150            final float off = brightness * contrast - 0.5f * contrast + 0.5f;
151            final float[] contrastMatrix = {
152                    contrast, 0, 0, 0,
153                    0, contrast, 0, 0,
154                    0, 0, contrast, 0,
155                    off, off, off, 1
156            };
157
158            Matrix.multiplyMM(outputMatrix, 0, colorMatrix, 0, contrastMatrix, 0);
159
160            colorMatrix = outputMatrix;
161            outputMatrix = colorMatrix;
162
163            hasColorTransform = true;
164        }
165
166        final boolean daltonizerEnabled = Settings.Secure.getIntForUser(
167                cr, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0, userId) != 0;
168        if (daltonizerEnabled) {
169            final int daltonizerMode = Settings.Secure.getIntForUser(cr,
170                    Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, DEFAULT_DISPLAY_DALTONIZER,
171                    userId);
172            // Monochromacy isn't supported by the native Daltonizer.
173            if (daltonizerMode == AccessibilityManager.DALTONIZER_SIMULATE_MONOCHROMACY) {
174                Matrix.multiplyMM(outputMatrix, 0, colorMatrix, 0, GRAYSCALE_MATRIX, 0);
175
176                final float[] temp = colorMatrix;
177                colorMatrix = outputMatrix;
178                outputMatrix = temp;
179
180                hasColorTransform = true;
181                nativeSetDaltonizerMode(AccessibilityManager.DALTONIZER_DISABLED);
182            } else {
183                nativeSetDaltonizerMode(daltonizerMode);
184            }
185        } else {
186            nativeSetDaltonizerMode(AccessibilityManager.DALTONIZER_DISABLED);
187        }
188
189        if (hasColorTransform) {
190            nativeSetColorTransform(colorMatrix);
191        } else {
192            nativeSetColorTransform(null);
193        }
194    }
195
196    /**
197     * Sets the surface flinger's Daltonization mode. This adjusts the color
198     * space to correct for or simulate various types of color blindness.
199     *
200     * @param mode new Daltonization mode
201     */
202    private static void nativeSetDaltonizerMode(int mode) {
203        try {
204            final IBinder flinger = ServiceManager.getService("SurfaceFlinger");
205            if (flinger != null) {
206                final Parcel data = Parcel.obtain();
207                data.writeInterfaceToken("android.ui.ISurfaceComposer");
208                data.writeInt(mode);
209                flinger.transact(1014, data, null, 0);
210                data.recycle();
211            }
212        } catch (RemoteException ex) {
213            Slog.e(LOG_TAG, "Failed to set Daltonizer mode", ex);
214        }
215    }
216
217    /**
218     * Sets the surface flinger's color transformation as a 4x4 matrix. If the
219     * matrix is null, color transformations are disabled.
220     *
221     * @param m the float array that holds the transformation matrix, or null to
222     *            disable transformation
223     */
224    private static void nativeSetColorTransform(float[] m) {
225        try {
226            final IBinder flinger = ServiceManager.getService("SurfaceFlinger");
227            if (flinger != null) {
228                final Parcel data = Parcel.obtain();
229                data.writeInterfaceToken("android.ui.ISurfaceComposer");
230                if (m != null) {
231                    data.writeInt(1);
232                    for (int i = 0; i < 16; i++) {
233                        data.writeFloat(m[i]);
234                    }
235                } else {
236                    data.writeInt(0);
237                }
238                flinger.transact(1015, data, null, 0);
239                data.recycle();
240            }
241        } catch (RemoteException ex) {
242            Slog.e(LOG_TAG, "Failed to set color transform", ex);
243        }
244    }
245
246}
247