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 
17 package com.android.server.accessibility;
18 
19 import android.content.ContentResolver;
20 import android.content.Context;
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.provider.Settings;
27 import android.util.Slog;
28 import android.view.accessibility.AccessibilityManager;
29 
30 /**
31  * Utility methods for performing accessibility display adjustments.
32  */
33 class 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     /**
45      * Matrix and offset used for luminance inversion. Represents a transform
46      * from RGB to YIQ color space, rotation around the Y axis by 180 degrees,
47      * transform back to RGB color space, and subtraction from 1. The last row
48      * represents a non-multiplied addition, see surfaceflinger's ProgramCache
49      * for full implementation details.
50      */
51     private static final float[] INVERSION_MATRIX_VALUE_ONLY = new float[] {
52         0.402f, -0.598f, -0.599f, 0,
53        -1.174f, -0.174f, -1.175f, 0,
54        -0.228f, -0.228f,  0.772f, 0,
55              1,       1,       1, 1
56     };
57 
58     /** Default inversion mode for display color correction. */
59     private static final int DEFAULT_DISPLAY_DALTONIZER =
60             AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY;
61 
62     /**
63      * Returns whether the specified user with has any display color
64      * adjustments.
65      */
hasAdjustments(Context context, int userId)66     public static boolean hasAdjustments(Context context, int userId) {
67         final ContentResolver cr = context.getContentResolver();
68 
69         if (Settings.Secure.getIntForUser(cr,
70                 Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0, userId) != 0) {
71             return true;
72         }
73 
74         if (Settings.Secure.getIntForUser(cr,
75                 Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0, userId) != 0) {
76             return true;
77         }
78 
79         return false;
80     }
81 
82     /**
83      * Applies the specified user's display color adjustments.
84      */
applyAdjustments(Context context, int userId)85     public static void applyAdjustments(Context context, int userId) {
86         final ContentResolver cr = context.getContentResolver();
87         float[] colorMatrix = null;
88 
89         if (Settings.Secure.getIntForUser(cr,
90                 Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0, userId) != 0) {
91             colorMatrix = multiply(colorMatrix, INVERSION_MATRIX_VALUE_ONLY);
92         }
93 
94         if (Settings.Secure.getIntForUser(cr,
95                 Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0, userId) != 0) {
96             final int daltonizerMode = Settings.Secure.getIntForUser(cr,
97                     Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, DEFAULT_DISPLAY_DALTONIZER,
98                     userId);
99             // Monochromacy isn't supported by the native Daltonizer.
100             if (daltonizerMode == AccessibilityManager.DALTONIZER_SIMULATE_MONOCHROMACY) {
101                 colorMatrix = multiply(colorMatrix, GRAYSCALE_MATRIX);
102                 setDaltonizerMode(AccessibilityManager.DALTONIZER_DISABLED);
103             } else {
104                 setDaltonizerMode(daltonizerMode);
105             }
106         } else {
107             setDaltonizerMode(AccessibilityManager.DALTONIZER_DISABLED);
108         }
109 
110         String matrix = Settings.Secure.getStringForUser(cr,
111                 Settings.Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX, userId);
112         if (matrix != null) {
113             final float[] userMatrix = get4x4Matrix(matrix);
114             if (userMatrix != null) {
115                 colorMatrix = multiply(colorMatrix, userMatrix);
116             }
117         }
118 
119         setColorTransform(colorMatrix);
120     }
121 
get4x4Matrix(String matrix)122     private static float[] get4x4Matrix(String matrix) {
123         String[] strValues = matrix.split(",");
124         if (strValues.length != 16) {
125             return null;
126         }
127         float[] values = new float[strValues.length];
128         try {
129             for (int i = 0; i < values.length; i++) {
130                 values[i] = Float.parseFloat(strValues[i]);
131             }
132         } catch (java.lang.NumberFormatException ex) {
133             return null;
134         }
135         return values;
136     }
137 
multiply(float[] matrix, float[] other)138     private static float[] multiply(float[] matrix, float[] other) {
139         if (matrix == null) {
140             return other;
141         }
142         float[] result = new float[16];
143         Matrix.multiplyMM(result, 0, matrix, 0, other, 0);
144         return result;
145     }
146 
147     /**
148      * Sets the surface flinger's Daltonization mode. This adjusts the color
149      * space to correct for or simulate various types of color blindness.
150      *
151      * @param mode new Daltonization mode
152      */
setDaltonizerMode(int mode)153     private static void setDaltonizerMode(int mode) {
154         try {
155             final IBinder flinger = ServiceManager.getService("SurfaceFlinger");
156             if (flinger != null) {
157                 final Parcel data = Parcel.obtain();
158                 data.writeInterfaceToken("android.ui.ISurfaceComposer");
159                 data.writeInt(mode);
160                 flinger.transact(1014, data, null, 0);
161                 data.recycle();
162             }
163         } catch (RemoteException ex) {
164             Slog.e(LOG_TAG, "Failed to set Daltonizer mode", ex);
165         }
166     }
167 
168     /**
169      * Sets the surface flinger's color transformation as a 4x4 matrix. If the
170      * matrix is null, color transformations are disabled.
171      *
172      * @param m the float array that holds the transformation matrix, or null to
173      *            disable transformation
174      */
setColorTransform(float[] m)175     private static void setColorTransform(float[] m) {
176         try {
177             final IBinder flinger = ServiceManager.getService("SurfaceFlinger");
178             if (flinger != null) {
179                 final Parcel data = Parcel.obtain();
180                 data.writeInterfaceToken("android.ui.ISurfaceComposer");
181                 if (m != null) {
182                     data.writeInt(1);
183                     for (int i = 0; i < 16; i++) {
184                         data.writeFloat(m[i]);
185                     }
186                 } else {
187                     data.writeInt(0);
188                 }
189                 flinger.transact(1015, data, null, 0);
190                 data.recycle();
191             }
192         } catch (RemoteException ex) {
193             Slog.e(LOG_TAG, "Failed to set color transform", ex);
194         }
195     }
196 
197 }
198