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