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