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 17 package com.android.server.display; 18 19 import android.app.ActivityManager; 20 import android.opengl.Matrix; 21 import android.os.IBinder; 22 import android.os.Parcel; 23 import android.os.RemoteException; 24 import android.os.ServiceManager; 25 import android.os.SystemProperties; 26 import android.util.Log; 27 import android.util.Slog; 28 import android.util.SparseArray; 29 import com.android.internal.annotations.GuardedBy; 30 import com.android.internal.app.ColorDisplayController; 31 import java.util.Arrays; 32 33 /** 34 * Manager for applying color transformations to the display. 35 */ 36 public class DisplayTransformManager { 37 38 private static final String TAG = "DisplayTransformManager"; 39 40 private static final String SURFACE_FLINGER = "SurfaceFlinger"; 41 42 /** 43 * Color transform level used by Night display to tint the display red. 44 */ 45 public static final int LEVEL_COLOR_MATRIX_NIGHT_DISPLAY = 100; 46 /** 47 * Color transform level used to adjust the color saturation of the display. 48 */ 49 public static final int LEVEL_COLOR_MATRIX_SATURATION = 150; 50 /** 51 * Color transform level used by A11y services to make the display monochromatic. 52 */ 53 public static final int LEVEL_COLOR_MATRIX_GRAYSCALE = 200; 54 /** 55 * Color transform level used by A11y services to invert the display colors. 56 */ 57 public static final int LEVEL_COLOR_MATRIX_INVERT_COLOR = 300; 58 59 private static final int SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX = 1015; 60 private static final int SURFACE_FLINGER_TRANSACTION_DALTONIZER = 1014; 61 62 private static final String PERSISTENT_PROPERTY_SATURATION = "persist.sys.sf.color_saturation"; 63 private static final String PERSISTENT_PROPERTY_DISPLAY_COLOR = "persist.sys.sf.native_mode"; 64 65 /** 66 * SurfaceFlinger global saturation factor. 67 */ 68 private static final int SURFACE_FLINGER_TRANSACTION_SATURATION = 1022; 69 /** 70 * SurfaceFlinger display color (managed, unmanaged, etc.). 71 */ 72 private static final int SURFACE_FLINGER_TRANSACTION_DISPLAY_COLOR = 1023; 73 74 private static final float COLOR_SATURATION_NATURAL = 1.0f; 75 private static final float COLOR_SATURATION_BOOSTED = 1.1f; 76 77 private static final int DISPLAY_COLOR_MANAGED = 0; 78 private static final int DISPLAY_COLOR_UNMANAGED = 1; 79 private static final int DISPLAY_COLOR_ENHANCED = 2; 80 81 /** 82 * Map of level -> color transformation matrix. 83 */ 84 @GuardedBy("mColorMatrix") 85 private final SparseArray<float[]> mColorMatrix = new SparseArray<>(3); 86 /** 87 * Temporary matrix used internally by {@link #computeColorMatrixLocked()}. 88 */ 89 @GuardedBy("mColorMatrix") 90 private final float[][] mTempColorMatrix = new float[2][16]; 91 92 /** 93 * Lock used for synchronize access to {@link #mDaltonizerMode}. 94 */ 95 private final Object mDaltonizerModeLock = new Object(); 96 @GuardedBy("mDaltonizerModeLock") 97 private int mDaltonizerMode = -1; 98 99 /* package */ DisplayTransformManager() { 100 } 101 102 /** 103 * Returns a copy of the color transform matrix set for a given level. 104 */ 105 public float[] getColorMatrix(int key) { 106 synchronized (mColorMatrix) { 107 final float[] value = mColorMatrix.get(key); 108 return value == null ? null : Arrays.copyOf(value, value.length); 109 } 110 } 111 112 /** 113 * Sets and applies a current color transform matrix for a given level. 114 * <p> 115 * Note: all color transforms are first composed to a single matrix in ascending order based 116 * on level before being applied to the display. 117 * 118 * @param level the level used to identify and compose the color transform (low -> high) 119 * @param value the 4x4 color transform matrix (in column-major order), or {@code null} to 120 * remove the color transform matrix associated with the provided level 121 */ 122 public void setColorMatrix(int level, float[] value) { 123 if (value != null && value.length != 16) { 124 throw new IllegalArgumentException("Expected length: 16 (4x4 matrix)" 125 + ", actual length: " + value.length); 126 } 127 128 synchronized (mColorMatrix) { 129 final float[] oldValue = mColorMatrix.get(level); 130 if (!Arrays.equals(oldValue, value)) { 131 if (value == null) { 132 mColorMatrix.remove(level); 133 } else if (oldValue == null) { 134 mColorMatrix.put(level, Arrays.copyOf(value, value.length)); 135 } else { 136 System.arraycopy(value, 0, oldValue, 0, value.length); 137 } 138 139 // Update the current color transform. 140 applyColorMatrix(computeColorMatrixLocked()); 141 } 142 } 143 } 144 145 /** 146 * Returns the composition of all current color matrices, or {@code null} if there are none. 147 */ 148 @GuardedBy("mColorMatrix") 149 private float[] computeColorMatrixLocked() { 150 final int count = mColorMatrix.size(); 151 if (count == 0) { 152 return null; 153 } 154 155 final float[][] result = mTempColorMatrix; 156 Matrix.setIdentityM(result[0], 0); 157 for (int i = 0; i < count; i++) { 158 float[] rhs = mColorMatrix.valueAt(i); 159 Matrix.multiplyMM(result[(i + 1) % 2], 0, result[i % 2], 0, rhs, 0); 160 } 161 return result[count % 2]; 162 } 163 164 /** 165 * Returns the current Daltonization mode. 166 */ 167 public int getDaltonizerMode() { 168 synchronized (mDaltonizerModeLock) { 169 return mDaltonizerMode; 170 } 171 } 172 173 /** 174 * Sets the current Daltonization mode. This adjusts the color space to correct for or simulate 175 * various types of color blindness. 176 * 177 * @param mode the new Daltonization mode, or -1 to disable 178 */ 179 public void setDaltonizerMode(int mode) { 180 synchronized (mDaltonizerModeLock) { 181 if (mDaltonizerMode != mode) { 182 mDaltonizerMode = mode; 183 applyDaltonizerMode(mode); 184 } 185 } 186 } 187 188 /** 189 * Propagates the provided color transformation matrix to the SurfaceFlinger. 190 */ 191 private static void applyColorMatrix(float[] m) { 192 final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER); 193 if (flinger != null) { 194 final Parcel data = Parcel.obtain(); 195 data.writeInterfaceToken("android.ui.ISurfaceComposer"); 196 if (m != null) { 197 data.writeInt(1); 198 for (int i = 0; i < 16; i++) { 199 data.writeFloat(m[i]); 200 } 201 } else { 202 data.writeInt(0); 203 } 204 try { 205 flinger.transact(SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX, data, null, 0); 206 } catch (RemoteException ex) { 207 Slog.e(TAG, "Failed to set color transform", ex); 208 } finally { 209 data.recycle(); 210 } 211 } 212 } 213 214 /** 215 * Propagates the provided Daltonization mode to the SurfaceFlinger. 216 */ 217 private static void applyDaltonizerMode(int mode) { 218 final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER); 219 if (flinger != null) { 220 final Parcel data = Parcel.obtain(); 221 data.writeInterfaceToken("android.ui.ISurfaceComposer"); 222 data.writeInt(mode); 223 try { 224 flinger.transact(SURFACE_FLINGER_TRANSACTION_DALTONIZER, data, null, 0); 225 } catch (RemoteException ex) { 226 Slog.e(TAG, "Failed to set Daltonizer mode", ex); 227 } finally { 228 data.recycle(); 229 } 230 } 231 } 232 233 /** 234 * Return true when the color matrix works in linear space. 235 */ 236 public static boolean needsLinearColorMatrix() { 237 return SystemProperties.getInt(PERSISTENT_PROPERTY_DISPLAY_COLOR, 238 DISPLAY_COLOR_UNMANAGED) != DISPLAY_COLOR_UNMANAGED; 239 } 240 241 /** 242 * Return true when the specified colorMode requires the color matrix to 243 * work in linear space. 244 */ 245 public static boolean needsLinearColorMatrix(int colorMode) { 246 return colorMode != ColorDisplayController.COLOR_MODE_SATURATED; 247 } 248 249 public boolean setColorMode(int colorMode, float[] nightDisplayMatrix) { 250 if (colorMode == ColorDisplayController.COLOR_MODE_NATURAL) { 251 applySaturation(COLOR_SATURATION_NATURAL); 252 setDisplayColor(DISPLAY_COLOR_MANAGED); 253 } else if (colorMode == ColorDisplayController.COLOR_MODE_BOOSTED) { 254 applySaturation(COLOR_SATURATION_BOOSTED); 255 setDisplayColor(DISPLAY_COLOR_MANAGED); 256 } else if (colorMode == ColorDisplayController.COLOR_MODE_SATURATED) { 257 applySaturation(COLOR_SATURATION_NATURAL); 258 setDisplayColor(DISPLAY_COLOR_UNMANAGED); 259 } else if (colorMode == ColorDisplayController.COLOR_MODE_AUTOMATIC) { 260 applySaturation(COLOR_SATURATION_NATURAL); 261 setDisplayColor(DISPLAY_COLOR_ENHANCED); 262 } 263 setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, nightDisplayMatrix); 264 265 updateConfiguration(); 266 267 return true; 268 } 269 270 /** 271 * Propagates the provided saturation to the SurfaceFlinger. 272 */ 273 private void applySaturation(float saturation) { 274 SystemProperties.set(PERSISTENT_PROPERTY_SATURATION, Float.toString(saturation)); 275 final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER); 276 if (flinger != null) { 277 final Parcel data = Parcel.obtain(); 278 data.writeInterfaceToken("android.ui.ISurfaceComposer"); 279 data.writeFloat(saturation); 280 try { 281 flinger.transact(SURFACE_FLINGER_TRANSACTION_SATURATION, data, null, 0); 282 } catch (RemoteException ex) { 283 Log.e(TAG, "Failed to set saturation", ex); 284 } finally { 285 data.recycle(); 286 } 287 } 288 } 289 290 /** 291 * Toggles native mode on/off in SurfaceFlinger. 292 */ 293 private void setDisplayColor(int color) { 294 SystemProperties.set(PERSISTENT_PROPERTY_DISPLAY_COLOR, Integer.toString(color)); 295 final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER); 296 if (flinger != null) { 297 final Parcel data = Parcel.obtain(); 298 data.writeInterfaceToken("android.ui.ISurfaceComposer"); 299 data.writeInt(color); 300 try { 301 flinger.transact(SURFACE_FLINGER_TRANSACTION_DISPLAY_COLOR, data, null, 0); 302 } catch (RemoteException ex) { 303 Log.e(TAG, "Failed to set display color", ex); 304 } finally { 305 data.recycle(); 306 } 307 } 308 } 309 310 private void updateConfiguration() { 311 try { 312 ActivityManager.getService().updateConfiguration(null); 313 } catch (RemoteException e) { 314 Log.e(TAG, "Could not update configuration", e); 315 } 316 } 317 } 318