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