DisplayTransformManager.java revision 26a2b97dbe48ee45e9ae70110714048f2f360f97
1/* 2 * Copyright (C) 2016 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.display; 18 19import android.opengl.Matrix; 20import android.os.IBinder; 21import android.os.Parcel; 22import android.os.RemoteException; 23import android.os.ServiceManager; 24import android.util.MathUtils; 25import android.util.Slog; 26import android.util.SparseArray; 27 28import com.android.internal.annotations.GuardedBy; 29 30import java.util.Arrays; 31 32/** 33 * Manager for applying color transformations to the display. 34 */ 35public class DisplayTransformManager { 36 37 private static final String TAG = "DisplayTransformManager"; 38 39 /** 40 * Color transform level used by Night display to tint the display red. 41 */ 42 public static final int LEVEL_COLOR_MATRIX_NIGHT_DISPLAY = 100; 43 /** 44 * Color transform level used by A11y services to make the display monochromatic. 45 */ 46 public static final int LEVEL_COLOR_MATRIX_GRAYSCALE = 200; 47 /** 48 * Color transform level used by A11y services to invert the display colors. 49 */ 50 public static final int LEVEL_COLOR_MATRIX_INVERT_COLOR = 300; 51 52 private static final int SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX = 1015; 53 private static final int SURFACE_FLINGER_TRANSACTION_DALTONIZER = 1014; 54 private static final int SURFACE_FLINGER_TRANSACTION_SATURATION = 1022; 55 56 /** 57 * Map of level -> color transformation matrix. 58 */ 59 @GuardedBy("mColorMatrix") 60 private final SparseArray<float[]> mColorMatrix = new SparseArray<>(3); 61 /** 62 * Temporary matrix used internally by {@link #computeColorMatrixLocked()}. 63 */ 64 @GuardedBy("mColorMatrix") 65 private final float[][] mTempColorMatrix = new float[2][16]; 66 67 /** 68 * Lock used for synchronize access to {@link #mDaltonizerMode}. 69 */ 70 private final Object mDaltonizerModeLock = new Object(); 71 @GuardedBy("mDaltonizerModeLock") 72 private int mDaltonizerMode = -1; 73 74 private final Object mSaturationLock = new Object(); 75 @GuardedBy("mSaturationLock") 76 private float mSaturation = 1.0f; 77 78 /* package */ DisplayTransformManager() { 79 } 80 81 /** 82 * Returns a copy of the color transform matrix set for a given level. 83 */ 84 public float[] getColorMatrix(int key) { 85 synchronized (mColorMatrix) { 86 final float[] value = mColorMatrix.get(key); 87 return value == null ? null : Arrays.copyOf(value, value.length); 88 } 89 } 90 91 /** 92 * Sets and applies a current color transform matrix for a given level. 93 * <p> 94 * Note: all color transforms are first composed to a single matrix in ascending order based 95 * on level before being applied to the display. 96 * 97 * @param level the level used to identify and compose the color transform (low -> high) 98 * @param value the 4x4 color transform matrix (in column-major order), or {@code null} to 99 * remove the color transform matrix associated with the provided level 100 */ 101 public void setColorMatrix(int level, float[] value) { 102 if (value != null && value.length != 16) { 103 throw new IllegalArgumentException("Expected length: 16 (4x4 matrix)" 104 + ", actual length: " + value.length); 105 } 106 107 synchronized (mColorMatrix) { 108 final float[] oldValue = mColorMatrix.get(level); 109 if (!Arrays.equals(oldValue, value)) { 110 if (value == null) { 111 mColorMatrix.remove(level); 112 } else if (oldValue == null) { 113 mColorMatrix.put(level, Arrays.copyOf(value, value.length)); 114 } else { 115 System.arraycopy(value, 0, oldValue, 0, value.length); 116 } 117 118 // Update the current color transform. 119 applyColorMatrix(computeColorMatrixLocked()); 120 } 121 } 122 } 123 124 /** 125 * Returns the composition of all current color matrices, or {@code null} if there are none. 126 */ 127 @GuardedBy("mColorMatrix") 128 private float[] computeColorMatrixLocked() { 129 final int count = mColorMatrix.size(); 130 if (count == 0) { 131 return null; 132 } 133 134 final float[][] result = mTempColorMatrix; 135 Matrix.setIdentityM(result[0], 0); 136 for (int i = 0; i < count; i++) { 137 float[] rhs = mColorMatrix.valueAt(i); 138 Matrix.multiplyMM(result[(i + 1) % 2], 0, result[i % 2], 0, rhs, 0); 139 } 140 return result[count % 2]; 141 } 142 143 /** 144 * Returns the current Daltonization mode. 145 */ 146 public int getDaltonizerMode() { 147 synchronized (mDaltonizerModeLock) { 148 return mDaltonizerMode; 149 } 150 } 151 152 /** 153 * Sets the current Daltonization mode. This adjusts the color space to correct for or simulate 154 * various types of color blindness. 155 * 156 * @param mode the new Daltonization mode, or -1 to disable 157 */ 158 public void setDaltonizerMode(int mode) { 159 synchronized (mDaltonizerModeLock) { 160 if (mDaltonizerMode != mode) { 161 mDaltonizerMode = mode; 162 applyDaltonizerMode(mode); 163 } 164 } 165 } 166 167 /** 168 * Returns the current saturation. 169 */ 170 public float getSaturation() { 171 synchronized (mSaturationLock) { 172 return mSaturation; 173 } 174 } 175 176 /** 177 * Sets the saturation level of the display. The default value is 1.0. 178 * 179 * @param saturation A value between 0 (0% saturation, grayscale) and 2 (100% extra saturation) 180 */ 181 public void setSaturation(float saturation) { 182 synchronized (mSaturationLock) { 183 saturation = MathUtils.constrain(saturation, 0.0f, 2.0f); 184 if (mSaturation != saturation) { 185 mSaturation = saturation; 186 applySaturation(saturation); 187 } 188 } 189 } 190 191 /** 192 * Propagates the provided color transformation matrix to the SurfaceFlinger. 193 */ 194 private static void applyColorMatrix(float[] m) { 195 final IBinder flinger = ServiceManager.getService("SurfaceFlinger"); 196 if (flinger != null) { 197 final Parcel data = Parcel.obtain(); 198 data.writeInterfaceToken("android.ui.ISurfaceComposer"); 199 if (m != null) { 200 data.writeInt(1); 201 for (int i = 0; i < 16; i++) { 202 data.writeFloat(m[i]); 203 } 204 } else { 205 data.writeInt(0); 206 } 207 try { 208 flinger.transact(SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX, data, null, 0); 209 } catch (RemoteException ex) { 210 Slog.e(TAG, "Failed to set color transform", ex); 211 } finally { 212 data.recycle(); 213 } 214 } 215 } 216 217 /** 218 * Propagates the provided Daltonization mode to the SurfaceFlinger. 219 */ 220 private static void applyDaltonizerMode(int mode) { 221 final IBinder flinger = ServiceManager.getService("SurfaceFlinger"); 222 if (flinger != null) { 223 final Parcel data = Parcel.obtain(); 224 data.writeInterfaceToken("android.ui.ISurfaceComposer"); 225 data.writeInt(mode); 226 try { 227 flinger.transact(SURFACE_FLINGER_TRANSACTION_DALTONIZER, data, null, 0); 228 } catch (RemoteException ex) { 229 Slog.e(TAG, "Failed to set Daltonizer mode", ex); 230 } finally { 231 data.recycle(); 232 } 233 } 234 } 235 236 /** 237 * Propagates the provided saturation to the SurfaceFlinger. 238 */ 239 private static void applySaturation(float saturation) { 240 final IBinder flinger = ServiceManager.getService("SurfaceFlinger"); 241 if (flinger != null) { 242 final Parcel data = Parcel.obtain(); 243 data.writeInterfaceToken("android.ui.ISurfaceComposer"); 244 data.writeFloat(saturation); 245 try { 246 flinger.transact(SURFACE_FLINGER_TRANSACTION_SATURATION, data, null, 0); 247 } catch (RemoteException ex) { 248 Slog.e(TAG, "Failed to set saturation", ex); 249 } finally { 250 data.recycle(); 251 } 252 } 253 } 254} 255