diff options
4 files changed, 531 insertions, 0 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 6c6635d..7258e16 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3752,6 +3752,97 @@ public final class Settings { "accessibility_captioning_font_scale"; /** + * Setting that specifies whether the quick setting tile for display + * color inversion is enabled. + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_INVERSION_QUICK_SETTING_ENABLED = + "accessibility_display_inversion_quick_setting_enabled"; + + /** + * Setting that specifies whether display color inversion is enabled. + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_INVERSION_ENABLED = + "accessibility_display_inversion_enabled"; + + /** + * Integer property that specifies the type of color inversion to + * perform. Valid values are defined in AccessibilityManager. + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_INVERSION = + "accessibility_display_inversion"; + + /** + * Setting that specifies whether the quick setting tile for display + * color space adjustment is enabled. + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_DALTONIZER_QUICK_SETTING_ENABLED = + "accessibility_display_daltonizer_quick_setting_enabled"; + + /** + * Setting that specifies whether display color space adjustment is + * enabled. + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED = + "accessibility_display_daltonizer_enabled"; + + /** + * Integer property that specifies the type of color space adjustment to + * perform. Valid values are defined in AccessibilityManager. + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_DALTONIZER = + "accessibility_display_daltonizer"; + + /** + * Setting that specifies whether the quick setting tile for display + * contrast enhancement is enabled. + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_CONTRAST_QUICK_SETTING_ENABLED = + "accessibility_display_contrast_quick_setting_enabled"; + + /** + * Setting that specifies whether display contrast enhancement is + * enabled. + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_CONTRAST_ENABLED = + "accessibility_display_contrast_enabled"; + + /** + * Floating point property that specifies display contrast adjustment. + * Valid range is [0, ...] where 0 is gray, 1 is normal, and higher + * values indicate enhanced contrast. + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_CONTRAST = + "accessibility_display_contrast"; + + /** + * Floating point property that specifies display brightness adjustment. + * Valid range is [-1, 1] where -1 is black, 0 is default, and 1 is + * white. + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_BRIGHTNESS = + "accessibility_display_brightness"; + + /** * The timout for considering a press to be a long press in milliseconds. * @hide */ diff --git a/core/java/android/util/MatrixUtils.java b/core/java/android/util/MatrixUtils.java new file mode 100644 index 0000000..c443d6c --- /dev/null +++ b/core/java/android/util/MatrixUtils.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +import java.util.Arrays; + +/** + * A class that contains utility methods related to matrices. + * + * @hide + */ +public class MatrixUtils { + private MatrixUtils() { + // This class is non-instantiable. + } + + /** + * Sets a matrix to the identity matrix. + * + * @param out output matrix of size n*m + * @param n number of rows in the output matrix + * @param m number of columns in the output matrix + */ + public static void setIdentityM(float[] out, int n, int m) { + final int size = n * m; + if (out.length != size) { + throw new IllegalArgumentException("Invalid dimensions for output matrix"); + } + + Arrays.fill(out, 0, size, 0); + for (int i = Math.min(m,n) - 1; i >= 0; i--) { + out[i * (m + 1)] = 1; + } + } + + /** + * Add two matrices. May be used in-place by specifying the same value for + * the out matrix and one of the input matrices. + * + * @param ab output matrix of size n*m + * @param a left-hand matrix of size n*m + * @param n number of rows in the left-hand matrix + * @param m number of columns in the left-hand matrix + * @param b right-hand matrix of size n*m + */ + public static void addMM(float[] ab, float[] a, int n, int m, float[] b) { + final int size = n * m; + if (a.length != size) { + throw new IllegalArgumentException("Invalid dimensions for matrix a"); + } else if (b.length != size) { + throw new IllegalArgumentException("Invalid dimensions for matrix b"); + } else if (ab.length != size) { + throw new IllegalArgumentException("Invalid dimensions for matrix ab"); + } + + for (int i = 0; i < size; i++) { + ab[i] = a[i] + b[i]; + } + } + + /** + * Multiply two matrices. + * + * @param ab output matrix of size n*p + * @param a left-hand matrix of size n*m + * @param n number of rows in the left-hand matrix + * @param m number of columns in the left-hand matrix + * @param b right-hand matrix of size m*p + * @param p number of columns in the right-hand matrix + */ + public static void multiplyMM(float[] ab, float[] a, int n, int m, float[] b, int p) { + if (a.length != n * m) { + throw new IllegalArgumentException("Invalid dimensions for matrix a"); + } else if (b.length != m * p) { + throw new IllegalArgumentException("Invalid dimensions for matrix b"); + } else if (ab.length != n * p) { + throw new IllegalArgumentException("Invalid dimensions for matrix ab"); + } + + for (int i = 0; i < n; i++) { + for (int j = 0; j < p; j++) { + float sum = 0; + for (int k = 0; k < m; k++) { + sum += a[i * m + k] * b[k * p + j]; + } + ab[i * p + j] = sum; + } + } + } + + /** + * Multiply a matrix by a scalar value. May be used in-place by specifying + * the same value for the input and output matrices. + * + * @param out output matrix + * @param in input matrix + * @param scalar scalar value + */ + public static void multiplyMS(float[] out, float[] in, float scalar) { + final int n = out.length; + for (int i = 0; i < n; i++) { + out[i] = in[i] * scalar; + } + } +} diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 04ce7e2..020b92c 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -75,6 +75,42 @@ public final class AccessibilityManager { /** @hide */ public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002; + /** @hide */ + public static final int INVERSION_DISABLED = -1; + + /** @hide */ + public static final int INVERSION_STANDARD = 0; + + /** @hide */ + public static final int INVERSION_HUE_ONLY = 1; + + /** @hide */ + public static final int INVERSION_VALUE_ONLY = 2; + + /** @hide */ + public static final int DALTONIZER_DISABLED = -1; + + /** @hide */ + public static final int DALTONIZER_SIMULATE_MONOCHROMACY = 0; + + /** @hide */ + public static final int DALTONIZER_SIMULATE_PROTANOMALY = 1; + + /** @hide */ + public static final int DALTONIZER_SIMULATE_DEUTERANOMALY = 2; + + /** @hide */ + public static final int DALTONIZER_SIMULATE_TRITANOMALY = 3; + + /** @hide */ + public static final int DALTONIZER_CORRECT_PROTANOMALY = 11; + + /** @hide */ + public static final int DALTONIZER_CORRECT_DEUTERANOMALY = 12; + + /** @hide */ + public static final int DALTONIZER_CORRECT_TRITANOMALY = 13; + static final Object sInstanceSync = new Object(); private static AccessibilityManager sInstance; diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index 83e69d6..9203be7 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -51,6 +51,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Parcel; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; @@ -61,6 +62,7 @@ import android.os.UserManager; import android.provider.Settings; import android.text.TextUtils; import android.text.TextUtils.SimpleStringSplitter; +import android.util.MatrixUtils; import android.util.Pools.Pool; import android.util.Pools.SimplePool; import android.util.Slog; @@ -139,6 +141,56 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private static final int MAX_POOL_SIZE = 10; + /** Matrix used for converting color to grayscale. */ + private static final float[] GRAYSCALE_MATRIX = new float[] { + .2126f, .2126f, .2126f, + .7152f, .7152f, .7152f, + .0722f, .0722f, .0722f + }; + + /** Matrix used for standard display inversion. */ + private static final float[] INVERSION_MATRIX_STANDARD = new float[] { + -1, 0, 0, + 0, -1, 0, + 0, 0, -1 + }; + + /** Offset used for standard display inversion. */ + private static final float INVERSION_OFFSET_STANDARD = 1; + + /** Matrix and offset used for hue-only display inversion. */ + private static final float[] INVERSION_MATRIX_HUE_ONLY = new float[] { + 0, .5f, .5f, + .5f, 0, .5f, + .5f, .5f, 0 + }; + + /** Offset used for hue-only display inversion. */ + private static final float INVERSION_OFFSET_HUE_ONLY = 0; + + /** Matrix and offset used for value-only display inversion. */ + private static final float[] INVERSION_MATRIX_VALUE_ONLY = new float[] { + 0, -.5f, -.5f, + -.5f, 0, -.5f, + -.5f, -.5f, 0 + }; + + /** Offset used for value-only display inversion. */ + private static final float INVERSION_OFFSET_VALUE_ONLY = 1; + + /** Default contrast for display contrast enhancement. */ + private static final float DEFAULT_DISPLAY_CONTRAST = 2; + + /** Default brightness for display contrast enhancement. */ + private static final float DEFAULT_DISPLAY_BRIGHTNESS = 0; + + /** Default inversion mode for display color inversion. */ + private static final int DEFAULT_DISPLAY_INVERSION = AccessibilityManager.INVERSION_STANDARD; + + /** Default inversion mode for display color correction. */ + private static final int DEFAULT_DISPLAY_DALTONIZER = + AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY; + private static int sIdCounter = 0; private static int sNextWindowId; @@ -1297,6 +1349,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { updateFilterKeyEventsLocked(userState); updateTouchExplorationLocked(userState); updateEnhancedWebAccessibilityLocked(userState); + updateDisplayColorAdjustmentSettingsLocked(userState); scheduleUpdateInputFilter(userState); scheduleUpdateClientsIfNeededLocked(userState); } @@ -1355,6 +1408,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { somthingChanged |= readTouchExplorationEnabledSettingLocked(userState); somthingChanged |= readEnhancedWebAccessibilityEnabledChangedLocked(userState); somthingChanged |= readDisplayMagnificationEnabledSettingLocked(userState); + somthingChanged |= readDisplayColorAdjustmentSettingsLocked(userState); return somthingChanged; } @@ -1403,6 +1457,136 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return false; } + private boolean readDisplayColorAdjustmentSettingsLocked(UserState userState) { + final ContentResolver cr = mContext.getContentResolver(); + final int userId = userState.mUserId; + + boolean hasColorTransform = Settings.Secure.getIntForUser( + cr, Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0, userId) == 1; + + if (!hasColorTransform) { + hasColorTransform |= Settings.Secure.getIntForUser( + cr, Settings.Secure.ACCESSIBILITY_DISPLAY_CONTRAST_ENABLED, 0, userId) == 1; + } + + if (!hasColorTransform) { + hasColorTransform |= Settings.Secure.getIntForUser( + cr, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0, userId) == 1; + } + + if (userState.mHasDisplayColorAdjustment != hasColorTransform) { + userState.mHasDisplayColorAdjustment = hasColorTransform; + return true; + } + + // If adjustment is enabled, always assume there was a transform change. + return hasColorTransform; + } + + private void updateDisplayColorAdjustmentSettingsLocked(UserState userState) { + final ContentResolver cr = mContext.getContentResolver(); + final int userId = userState.mUserId; + final float[] offsetVector = new float[3]; + float[] colorOffset = new float[3]; + float[] outputOffset = new float[3]; + float[] colorMatrix = new float[9]; + float[] outputMatrix = new float[9]; + boolean hasColorTransform = false; + + MatrixUtils.setIdentityM(colorMatrix, 3, 3); + + final boolean inversionEnabled = Settings.Secure.getIntForUser( + cr, Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0, userId) == 1; + if (inversionEnabled) { + final int inversionMode = Settings.Secure.getIntForUser(cr, + Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION, DEFAULT_DISPLAY_INVERSION, + userId); + final float[] inversionMatrix; + final float inversionOffset; + switch (inversionMode) { + case AccessibilityManager.INVERSION_HUE_ONLY: + inversionMatrix = INVERSION_MATRIX_HUE_ONLY; + inversionOffset = INVERSION_OFFSET_HUE_ONLY; + break; + case AccessibilityManager.INVERSION_VALUE_ONLY: + inversionMatrix = INVERSION_MATRIX_VALUE_ONLY; + inversionOffset = INVERSION_OFFSET_VALUE_ONLY; + break; + default: + inversionMatrix = INVERSION_MATRIX_STANDARD; + inversionOffset = INVERSION_OFFSET_STANDARD; + } + + Arrays.fill(offsetVector, inversionOffset); + + MatrixUtils.multiplyMM(outputMatrix, colorMatrix, 3, 3, inversionMatrix, 3); + MatrixUtils.multiplyMM(outputOffset, colorOffset, 1, 3, inversionMatrix, 3); + MatrixUtils.addMM(colorOffset, outputOffset, 1, 3, offsetVector); + + final float[] temp = colorMatrix; + colorMatrix = outputMatrix; + outputMatrix = temp; + + hasColorTransform = true; + } + + final boolean contrastEnabled = Settings.Secure.getIntForUser( + cr, Settings.Secure.ACCESSIBILITY_DISPLAY_CONTRAST_ENABLED, 0, userId) == 1; + if (contrastEnabled) { + final float contrast = Settings.Secure.getFloatForUser(cr, + Settings.Secure.ACCESSIBILITY_DISPLAY_CONTRAST, DEFAULT_DISPLAY_CONTRAST, + userId); + final float brightness = Settings.Secure.getFloatForUser(cr, + Settings.Secure.ACCESSIBILITY_DISPLAY_BRIGHTNESS, DEFAULT_DISPLAY_BRIGHTNESS, + userId); + final float offset = brightness * contrast - 0.5f * contrast + 0.5f; + Arrays.fill(offsetVector, offset); + + MatrixUtils.multiplyMS(outputMatrix, colorMatrix, contrast); + MatrixUtils.multiplyMS(outputOffset, colorOffset, contrast); + MatrixUtils.addMM(colorOffset, outputOffset, 1, 3, offsetVector); + + final float[] temp = colorMatrix; + colorMatrix = outputMatrix; + outputMatrix = temp; + + hasColorTransform = true; + } + + final boolean daltonizerEnabled = Settings.Secure.getIntForUser( + cr, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0, userId) != 0; + if (daltonizerEnabled) { + final int daltonizerMode = Settings.Secure.getIntForUser(cr, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, DEFAULT_DISPLAY_DALTONIZER, + userId); + // Monochromacy isn't supported by the native Daltonizer. + if (daltonizerMode == AccessibilityManager.DALTONIZER_SIMULATE_MONOCHROMACY) { + MatrixUtils.multiplyMM(outputMatrix, colorMatrix, 3, 3, GRAYSCALE_MATRIX, 3); + MatrixUtils.multiplyMM(outputOffset, colorOffset, 1, 3, GRAYSCALE_MATRIX, 3); + + final float[] temp = colorMatrix; + colorMatrix = outputMatrix; + outputMatrix = temp; + + final float[] tempVec = colorOffset; + colorOffset = outputOffset; + outputOffset = temp; + + nativeSetDaltonizerMode(AccessibilityManager.DALTONIZER_DISABLED); + } else { + nativeSetDaltonizerMode(daltonizerMode); + } + } else { + nativeSetDaltonizerMode(AccessibilityManager.DALTONIZER_DISABLED); + } + + if (hasColorTransform) { + nativeSetColorTransform(colorMatrix, colorOffset); + } else { + nativeSetColorTransform(null, null); + } + } + private void updateTouchExplorationLocked(UserState userState) { boolean enabled = false; final int serviceCount = userState.mBoundServices.size(); @@ -1524,6 +1708,59 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + /** + * Sets the surface flinger's Daltonization mode. This adjusts the color + * space to correct for or simulate various types of color blindness. + * + * @param mode new Daltonization mode + */ + private static void nativeSetDaltonizerMode(int mode) { + try { + final IBinder flinger = ServiceManager.getService("SurfaceFlinger"); + if (flinger != null) { + final Parcel data = Parcel.obtain(); + data.writeInterfaceToken("android.ui.ISurfaceComposer"); + data.writeInt(mode); + flinger.transact(1014, data, null, 0); + data.recycle(); + } + } catch (RemoteException ex) { + Slog.e(LOG_TAG, "Failed to set Daltonizer mode", ex); + } + } + + /** + * Sets the surface flinger's color transformation matrix and offset. If + * either value is null, color transformations are disabled. + * + * @param matrix new color transformation matrix, or null to disable + * @param offset new color transformation offset, or null to disable + */ + private static void nativeSetColorTransform(float[] matrix, float[] offset) { + try { + final IBinder flinger = ServiceManager.getService("SurfaceFlinger"); + if (flinger != null) { + final Parcel data = Parcel.obtain(); + data.writeInterfaceToken("android.ui.ISurfaceComposer"); + if (matrix != null && offset != null) { + data.writeInt(1); + for (int i = 0; i < 9; i++) { + data.writeFloat(matrix[i]); + } + for (int i = 0; i < 3; i++) { + data.writeFloat(offset[i]); + } + } else { + data.writeInt(0); + } + flinger.transact(1015, data, null, 0); + data.recycle(); + } + } catch (RemoteException ex) { + Slog.e(LOG_TAG, "Failed to set color transform", ex); + } + } + private class AccessibilityConnectionWrapper implements DeathRecipient { private final int mWindowId; private final int mUserId; @@ -2947,6 +3184,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public boolean mIsEnhancedWebAccessibilityEnabled; public boolean mIsDisplayMagnificationEnabled; public boolean mIsFilterKeyEventsEnabled; + public boolean mHasDisplayColorAdjustment; private Service mUiAutomationService; private IAccessibilityServiceClient mUiAutomationServiceClient; @@ -3038,6 +3276,23 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private final Uri mEnhancedWebAccessibilityUri = Settings.Secure .getUriFor(Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION); + private final Uri mDisplayContrastEnabledUri = Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_DISPLAY_CONTRAST_ENABLED); + private final Uri mDisplayContrastUri = Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_DISPLAY_CONTRAST); + private final Uri mDisplayBrightnessUri = Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_DISPLAY_BRIGHTNESS); + + private final Uri mDisplayInversionEnabledUri = Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED); + private final Uri mDisplayInversionUri = Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION); + + private final Uri mDisplayDaltonizerEnabledUri = Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED); + private final Uri mDisplayDaltonizerUri = Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER); + public AccessibilityContentObserver(Handler handler) { super(handler); } @@ -3056,6 +3311,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver(mEnhancedWebAccessibilityUri, false, this, UserHandle.USER_ALL); + contentResolver.registerContentObserver( + mDisplayContrastEnabledUri, false, this, UserHandle.USER_ALL); + contentResolver.registerContentObserver( + mDisplayContrastUri, false, this, UserHandle.USER_ALL); + contentResolver.registerContentObserver( + mDisplayBrightnessUri, false, this, UserHandle.USER_ALL); + contentResolver.registerContentObserver( + mDisplayInversionEnabledUri, false, this, UserHandle.USER_ALL); + contentResolver.registerContentObserver( + mDisplayInversionUri, false, this, UserHandle.USER_ALL); + contentResolver.registerContentObserver( + mDisplayDaltonizerEnabledUri, false, this, UserHandle.USER_ALL); + contentResolver.registerContentObserver( + mDisplayDaltonizerUri, false, this, UserHandle.USER_ALL); } @Override @@ -3120,6 +3389,22 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } } + } else if (mDisplayContrastEnabledUri.equals(uri) + || mDisplayInversionEnabledUri.equals(uri) + || mDisplayDaltonizerEnabledUri.equals(uri) + || mDisplayContrastUri.equals(uri) + || mDisplayBrightnessUri.equals(uri) + || mDisplayInversionUri.equals(uri) + || mDisplayDaltonizerUri.equals(uri)) { + synchronized (mLock) { + // We will update when the automation service dies. + UserState userState = getCurrentUserStateLocked(); + if (userState.mUiAutomationService == null) { + if (readDisplayColorAdjustmentSettingsLocked(userState)) { + updateDisplayColorAdjustmentSettingsLocked(userState); + } + } + } } } } |