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