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 value-only display inversion. */
45    private static final float[] INVERSION_MATRIX_VALUE_ONLY = new float[] {
46           0, -.5f, -.5f, 0,
47        -.5f,    0, -.5f, 0,
48        -.5f, -.5f,    0, 0,
49           1,    1,    1, 1
50    };
51
52    /** Default inversion mode for display color correction. */
53    private static final int DEFAULT_DISPLAY_DALTONIZER =
54            AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY;
55
56    /**
57     * Returns whether the specified user with has any display color
58     * adjustments.
59     */
60    public static boolean hasAdjustments(Context context, int userId) {
61        final ContentResolver cr = context.getContentResolver();
62
63        if (Settings.Secure.getIntForUser(cr,
64                Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0, userId) != 0) {
65            return true;
66        }
67
68        if (Settings.Secure.getIntForUser(cr,
69                Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0, userId) != 0) {
70            return true;
71        }
72
73        return false;
74    }
75
76    /**
77     * Applies the specified user's display color adjustments.
78     */
79    public static void applyAdjustments(Context context, int userId) {
80        final ContentResolver cr = context.getContentResolver();
81        float[] colorMatrix = null;
82
83        if (Settings.Secure.getIntForUser(cr,
84                Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0, userId) != 0) {
85            colorMatrix = multiply(colorMatrix, INVERSION_MATRIX_VALUE_ONLY);
86        }
87
88        if (Settings.Secure.getIntForUser(cr,
89                Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0, userId) != 0) {
90            final int daltonizerMode = Settings.Secure.getIntForUser(cr,
91                    Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, DEFAULT_DISPLAY_DALTONIZER,
92                    userId);
93            // Monochromacy isn't supported by the native Daltonizer.
94            if (daltonizerMode == AccessibilityManager.DALTONIZER_SIMULATE_MONOCHROMACY) {
95                colorMatrix = multiply(colorMatrix, GRAYSCALE_MATRIX);
96                setDaltonizerMode(AccessibilityManager.DALTONIZER_DISABLED);
97            } else {
98                setDaltonizerMode(daltonizerMode);
99            }
100        } else {
101            setDaltonizerMode(AccessibilityManager.DALTONIZER_DISABLED);
102        }
103
104        setColorTransform(colorMatrix);
105    }
106
107    private static float[] multiply(float[] matrix, float[] other) {
108        if (matrix == null) {
109            return other;
110        }
111        float[] result = new float[16];
112        Matrix.multiplyMM(result, 0, matrix, 0, other, 0);
113        return result;
114    }
115
116    /**
117     * Sets the surface flinger's Daltonization mode. This adjusts the color
118     * space to correct for or simulate various types of color blindness.
119     *
120     * @param mode new Daltonization mode
121     */
122    private static void setDaltonizerMode(int mode) {
123        try {
124            final IBinder flinger = ServiceManager.getService("SurfaceFlinger");
125            if (flinger != null) {
126                final Parcel data = Parcel.obtain();
127                data.writeInterfaceToken("android.ui.ISurfaceComposer");
128                data.writeInt(mode);
129                flinger.transact(1014, data, null, 0);
130                data.recycle();
131            }
132        } catch (RemoteException ex) {
133            Slog.e(LOG_TAG, "Failed to set Daltonizer mode", ex);
134        }
135    }
136
137    /**
138     * Sets the surface flinger's color transformation as a 4x4 matrix. If the
139     * matrix is null, color transformations are disabled.
140     *
141     * @param m the float array that holds the transformation matrix, or null to
142     *            disable transformation
143     */
144    private static void setColorTransform(float[] m) {
145        try {
146            final IBinder flinger = ServiceManager.getService("SurfaceFlinger");
147            if (flinger != null) {
148                final Parcel data = Parcel.obtain();
149                data.writeInterfaceToken("android.ui.ISurfaceComposer");
150                if (m != null) {
151                    data.writeInt(1);
152                    for (int i = 0; i < 16; i++) {
153                        data.writeFloat(m[i]);
154                    }
155                } else {
156                    data.writeInt(0);
157                }
158                flinger.transact(1015, data, null, 0);
159                data.recycle();
160            }
161        } catch (RemoteException ex) {
162            Slog.e(LOG_TAG, "Failed to set color transform", ex);
163        }
164    }
165
166}
167