DisplayTransformManager.java revision 1e0e7176bdbae9fd1f47351e6ed100c303535cab
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.app.ActivityManager; 20import android.opengl.Matrix; 21import android.os.IBinder; 22import android.os.Parcel; 23import android.os.RemoteException; 24import android.os.ServiceManager; 25import android.os.SystemProperties; 26import android.util.Log; 27import android.util.Slog; 28import android.util.SparseArray; 29import com.android.internal.annotations.GuardedBy; 30import com.android.internal.app.ColorDisplayController; 31import java.util.Arrays; 32 33/** 34 * Manager for applying color transformations to the display. 35 */ 36public 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 colors are stretched from the working color space to the 235 * native color space. 236 */ 237 public static boolean isNativeModeEnabled() { 238 return SystemProperties.getInt(PERSISTENT_PROPERTY_DISPLAY_COLOR, 239 DISPLAY_COLOR_MANAGED) != DISPLAY_COLOR_MANAGED; 240 } 241 242 /** 243 * Return true when the specified colorMode stretches colors from the 244 * working color space to the native color space. 245 */ 246 public static boolean isColorModeNative(int colorMode) { 247 return !(colorMode == ColorDisplayController.COLOR_MODE_NATURAL || 248 colorMode == ColorDisplayController.COLOR_MODE_BOOSTED); 249 } 250 251 public boolean setColorMode(int colorMode, float[] nightDisplayMatrix) { 252 if (colorMode == ColorDisplayController.COLOR_MODE_NATURAL) { 253 applySaturation(COLOR_SATURATION_NATURAL); 254 setDisplayColor(DISPLAY_COLOR_MANAGED); 255 } else if (colorMode == ColorDisplayController.COLOR_MODE_BOOSTED) { 256 applySaturation(COLOR_SATURATION_BOOSTED); 257 setDisplayColor(DISPLAY_COLOR_MANAGED); 258 } else if (colorMode == ColorDisplayController.COLOR_MODE_SATURATED) { 259 applySaturation(COLOR_SATURATION_NATURAL); 260 setDisplayColor(DISPLAY_COLOR_UNMANAGED); 261 } else if (colorMode == ColorDisplayController.COLOR_MODE_AUTOMATIC) { 262 applySaturation(COLOR_SATURATION_NATURAL); 263 setDisplayColor(DISPLAY_COLOR_ENHANCED); 264 } 265 setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, nightDisplayMatrix); 266 267 updateConfiguration(); 268 269 return true; 270 } 271 272 /** 273 * Propagates the provided saturation to the SurfaceFlinger. 274 */ 275 private void applySaturation(float saturation) { 276 SystemProperties.set(PERSISTENT_PROPERTY_SATURATION, Float.toString(saturation)); 277 final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER); 278 if (flinger != null) { 279 final Parcel data = Parcel.obtain(); 280 data.writeInterfaceToken("android.ui.ISurfaceComposer"); 281 data.writeFloat(saturation); 282 try { 283 flinger.transact(SURFACE_FLINGER_TRANSACTION_SATURATION, data, null, 0); 284 } catch (RemoteException ex) { 285 Log.e(TAG, "Failed to set saturation", ex); 286 } finally { 287 data.recycle(); 288 } 289 } 290 } 291 292 /** 293 * Toggles native mode on/off in SurfaceFlinger. 294 */ 295 private void setDisplayColor(int color) { 296 SystemProperties.set(PERSISTENT_PROPERTY_DISPLAY_COLOR, Integer.toString(color)); 297 final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER); 298 if (flinger != null) { 299 final Parcel data = Parcel.obtain(); 300 data.writeInterfaceToken("android.ui.ISurfaceComposer"); 301 data.writeInt(color); 302 try { 303 flinger.transact(SURFACE_FLINGER_TRANSACTION_DISPLAY_COLOR, data, null, 0); 304 } catch (RemoteException ex) { 305 Log.e(TAG, "Failed to set display color", ex); 306 } finally { 307 data.recycle(); 308 } 309 } 310 } 311 312 private void updateConfiguration() { 313 try { 314 ActivityManager.getService().updateConfiguration(null); 315 } catch (RemoteException e) { 316 Log.e(TAG, "Could not update configuration", e); 317 } 318 } 319} 320