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 reduce bright colors. 63 */ 64 public static final int LEVEL_COLOR_MATRIX_REDUCE_BRIGHT_COLORS = 250; 65 /** 66 * Color transform level used by A11y services to invert the display colors. 67 */ 68 public static final int LEVEL_COLOR_MATRIX_INVERT_COLOR = 300; 69 70 private static final int SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX = 1015; 71 private static final int SURFACE_FLINGER_TRANSACTION_DALTONIZER = 1014; 72 /** 73 * SurfaceFlinger global saturation factor. 74 */ 75 private static final int SURFACE_FLINGER_TRANSACTION_SATURATION = 1022; 76 /** 77 * SurfaceFlinger display color (managed, unmanaged, etc.). 78 */ 79 private static final int SURFACE_FLINGER_TRANSACTION_DISPLAY_COLOR = 1023; 80 private static final int SURFACE_FLINGER_TRANSACTION_QUERY_COLOR_MANAGED = 1030; 81 82 @VisibleForTesting 83 static final String PERSISTENT_PROPERTY_SATURATION = "persist.sys.sf.color_saturation"; 84 @VisibleForTesting 85 static final String PERSISTENT_PROPERTY_COMPOSITION_COLOR_MODE = "persist.sys.sf.color_mode"; 86 @VisibleForTesting 87 static final String PERSISTENT_PROPERTY_DISPLAY_COLOR = "persist.sys.sf.native_mode"; 88 89 private static final float COLOR_SATURATION_NATURAL = 1.0f; 90 private static final float COLOR_SATURATION_BOOSTED = 1.1f; 91 92 /** 93 * Display color modes defined by DisplayColorSetting in 94 * frameworks/native/services/surfaceflinger/SurfaceFlinger.h. 95 */ 96 private static final int DISPLAY_COLOR_MANAGED = 0; 97 private static final int DISPLAY_COLOR_UNMANAGED = 1; 98 private static final int DISPLAY_COLOR_ENHANCED = 2; 99 100 /** 101 * Map of level -> color transformation matrix. 102 */ 103 @GuardedBy("mColorMatrix") 104 private final SparseArray<float[]> mColorMatrix = new SparseArray<>(6); 105 /** 106 * Temporary matrix used internally by {@link #computeColorMatrixLocked()}. 107 */ 108 @GuardedBy("mColorMatrix") 109 private final float[][] mTempColorMatrix = new float[2][16]; 110 111 /** 112 * Lock used for synchronize access to {@link #mDaltonizerMode}. 113 */ 114 private final Object mDaltonizerModeLock = new Object(); 115 @GuardedBy("mDaltonizerModeLock") 116 private int mDaltonizerMode = -1; 117 118 private static final IBinder sFlinger = ServiceManager.getService(SURFACE_FLINGER); 119 DisplayTransformManager()120 /* package */ DisplayTransformManager() { 121 } 122 123 /** 124 * Returns a copy of the color transform matrix set for a given level. 125 */ getColorMatrix(int key)126 public float[] getColorMatrix(int key) { 127 synchronized (mColorMatrix) { 128 final float[] value = mColorMatrix.get(key); 129 return value == null ? null : Arrays.copyOf(value, value.length); 130 } 131 } 132 133 /** 134 * Sets and applies a current color transform matrix for a given level. 135 * <p> 136 * Note: all color transforms are first composed to a single matrix in ascending order based on 137 * level before being applied to the display. 138 * 139 * @param level the level used to identify and compose the color transform (low -> high) 140 * @param value the 4x4 color transform matrix (in column-major order), or {@code null} to 141 * remove the color transform matrix associated with the provided level 142 */ setColorMatrix(int level, float[] value)143 public void setColorMatrix(int level, float[] value) { 144 if (value != null && value.length != 16) { 145 throw new IllegalArgumentException("Expected length: 16 (4x4 matrix)" 146 + ", actual length: " + value.length); 147 } 148 149 synchronized (mColorMatrix) { 150 final float[] oldValue = mColorMatrix.get(level); 151 if (!Arrays.equals(oldValue, value)) { 152 if (value == null) { 153 mColorMatrix.remove(level); 154 } else if (oldValue == null) { 155 mColorMatrix.put(level, Arrays.copyOf(value, value.length)); 156 } else { 157 System.arraycopy(value, 0, oldValue, 0, value.length); 158 } 159 160 // Update the current color transform. 161 applyColorMatrix(computeColorMatrixLocked()); 162 } 163 } 164 } 165 166 /** 167 * Sets the current Daltonization mode. This adjusts the color space to correct for or simulate 168 * various types of color blindness. 169 * 170 * @param mode the new Daltonization mode, or -1 to disable 171 */ setDaltonizerMode(int mode)172 public void setDaltonizerMode(int mode) { 173 synchronized (mDaltonizerModeLock) { 174 if (mDaltonizerMode != mode) { 175 mDaltonizerMode = mode; 176 applyDaltonizerMode(mode); 177 } 178 } 179 } 180 181 /** 182 * Returns the composition of all current color matrices, or {@code null} if there are none. 183 */ 184 @GuardedBy("mColorMatrix") computeColorMatrixLocked()185 private float[] computeColorMatrixLocked() { 186 final int count = mColorMatrix.size(); 187 if (count == 0) { 188 return null; 189 } 190 191 final float[][] result = mTempColorMatrix; 192 Matrix.setIdentityM(result[0], 0); 193 for (int i = 0; i < count; i++) { 194 float[] rhs = mColorMatrix.valueAt(i); 195 Matrix.multiplyMM(result[(i + 1) % 2], 0, result[i % 2], 0, rhs, 0); 196 } 197 return result[count % 2]; 198 } 199 200 /** 201 * Propagates the provided color transformation matrix to the SurfaceFlinger. 202 */ applyColorMatrix(float[] m)203 private static void applyColorMatrix(float[] m) { 204 final Parcel data = Parcel.obtain(); 205 data.writeInterfaceToken("android.ui.ISurfaceComposer"); 206 if (m != null) { 207 data.writeInt(1); 208 for (int i = 0; i < 16; i++) { 209 data.writeFloat(m[i]); 210 } 211 } else { 212 data.writeInt(0); 213 } 214 try { 215 sFlinger.transact(SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX, data, null, 0); 216 } catch (RemoteException ex) { 217 Slog.e(TAG, "Failed to set color transform", ex); 218 } finally { 219 data.recycle(); 220 } 221 } 222 223 /** 224 * Propagates the provided Daltonization mode to the SurfaceFlinger. 225 */ applyDaltonizerMode(int mode)226 private static void applyDaltonizerMode(int mode) { 227 final Parcel data = Parcel.obtain(); 228 data.writeInterfaceToken("android.ui.ISurfaceComposer"); 229 data.writeInt(mode); 230 try { 231 sFlinger.transact(SURFACE_FLINGER_TRANSACTION_DALTONIZER, data, null, 0); 232 } catch (RemoteException ex) { 233 Slog.e(TAG, "Failed to set Daltonizer mode", ex); 234 } finally { 235 data.recycle(); 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 Parcel data = Parcel.obtain(); 290 final Parcel reply = Parcel.obtain(); 291 data.writeInterfaceToken("android.ui.ISurfaceComposer"); 292 try { 293 sFlinger.transact(SURFACE_FLINGER_TRANSACTION_QUERY_COLOR_MANAGED, data, reply, 0); 294 return reply.readBoolean(); 295 } catch (RemoteException ex) { 296 Slog.e(TAG, "Failed to query wide color support", ex); 297 } finally { 298 data.recycle(); 299 reply.recycle(); 300 } 301 return false; 302 } 303 304 /** 305 * Propagates the provided saturation to the SurfaceFlinger. 306 */ applySaturation(float saturation)307 private void applySaturation(float saturation) { 308 SystemProperties.set(PERSISTENT_PROPERTY_SATURATION, Float.toString(saturation)); 309 final Parcel data = Parcel.obtain(); 310 data.writeInterfaceToken("android.ui.ISurfaceComposer"); 311 data.writeFloat(saturation); 312 try { 313 sFlinger.transact(SURFACE_FLINGER_TRANSACTION_SATURATION, data, null, 0); 314 } catch (RemoteException ex) { 315 Slog.e(TAG, "Failed to set saturation", ex); 316 } finally { 317 data.recycle(); 318 } 319 } 320 321 /** 322 * Toggles native mode on/off in SurfaceFlinger. 323 */ setDisplayColor(int color, int compositionColorMode)324 private void setDisplayColor(int color, int compositionColorMode) { 325 SystemProperties.set(PERSISTENT_PROPERTY_DISPLAY_COLOR, Integer.toString(color)); 326 if (compositionColorMode != Display.COLOR_MODE_INVALID) { 327 SystemProperties.set(PERSISTENT_PROPERTY_COMPOSITION_COLOR_MODE, 328 Integer.toString(compositionColorMode)); 329 } 330 331 final Parcel data = Parcel.obtain(); 332 data.writeInterfaceToken("android.ui.ISurfaceComposer"); 333 data.writeInt(color); 334 if (compositionColorMode != Display.COLOR_MODE_INVALID) { 335 data.writeInt(compositionColorMode); 336 } 337 try { 338 sFlinger.transact(SURFACE_FLINGER_TRANSACTION_DISPLAY_COLOR, data, null, 0); 339 } catch (RemoteException ex) { 340 Slog.e(TAG, "Failed to set display color", ex); 341 } finally { 342 data.recycle(); 343 } 344 } 345 updateConfiguration()346 private void updateConfiguration() { 347 try { 348 ActivityTaskManager.getService().updateConfiguration(null); 349 } catch (RemoteException e) { 350 Slog.e(TAG, "Could not update configuration", e); 351 } 352 } 353 } 354