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; 18 19 import android.opengl.Matrix; 20 import android.os.IBinder; 21 import android.os.Parcel; 22 import android.os.RemoteException; 23 import android.os.ServiceManager; 24 import android.util.Slog; 25 import android.util.SparseArray; 26 27 import com.android.internal.annotations.GuardedBy; 28 29 import java.util.Arrays; 30 31 /** 32 * Manager for applying color transformations to the display. 33 */ 34 public class DisplayTransformManager { 35 36 private static final String TAG = "DisplayTransformManager"; 37 38 /** 39 * Color transform level used by Night display to tint the display red. 40 */ 41 public static final int LEVEL_COLOR_MATRIX_NIGHT_DISPLAY = 100; 42 /** 43 * Color transform level used by A11y services to make the display monochromatic. 44 */ 45 public static final int LEVEL_COLOR_MATRIX_GRAYSCALE = 200; 46 /** 47 * Color transform level used by A11y services to invert the display colors. 48 */ 49 public static final int LEVEL_COLOR_MATRIX_INVERT_COLOR = 300; 50 51 /** 52 * Map of level -> color transformation matrix. 53 */ 54 @GuardedBy("mColorMatrix") 55 private final SparseArray<float[]> mColorMatrix = new SparseArray<>(3); 56 /** 57 * Temporary matrix used internally by {@link #computeColorMatrixLocked()}. 58 */ 59 @GuardedBy("mColorMatrix") 60 private final float[][] mTempColorMatrix = new float[2][16]; 61 62 /** 63 * Lock used for synchronize access to {@link #mDaltonizerMode}. 64 */ 65 private final Object mDaltonizerModeLock = new Object(); 66 @GuardedBy("mDaltonizerModeLock") 67 private int mDaltonizerMode = -1; 68 69 /* package */ DisplayTransformManager() { 70 } 71 72 /** 73 * Returns a copy of the color transform matrix set for a given level. 74 */ 75 public float[] getColorMatrix(int key) { 76 synchronized (mColorMatrix) { 77 final float[] value = mColorMatrix.get(key); 78 return value == null ? null : Arrays.copyOf(value, value.length); 79 } 80 } 81 82 /** 83 * Sets and applies a current color transform matrix for a given level. 84 * <p> 85 * Note: all color transforms are first composed to a single matrix in ascending order based 86 * on level before being applied to the display. 87 * 88 * @param level the level used to identify and compose the color transform (low -> high) 89 * @param value the 4x4 color transform matrix (in column-major order), or {@code null} to 90 * remove the color transform matrix associated with the provided level 91 */ 92 public void setColorMatrix(int level, float[] value) { 93 if (value != null && value.length != 16) { 94 throw new IllegalArgumentException("Expected length: 16 (4x4 matrix)" 95 + ", actual length: " + value.length); 96 } 97 98 synchronized (mColorMatrix) { 99 final float[] oldValue = mColorMatrix.get(level); 100 if (!Arrays.equals(oldValue, value)) { 101 if (value == null) { 102 mColorMatrix.remove(level); 103 } else if (oldValue == null) { 104 mColorMatrix.put(level, Arrays.copyOf(value, value.length)); 105 } else { 106 System.arraycopy(value, 0, oldValue, 0, value.length); 107 } 108 109 // Update the current color transform. 110 applyColorMatrix(computeColorMatrixLocked()); 111 } 112 } 113 } 114 115 /** 116 * Returns the composition of all current color matrices, or {@code null} if there are none. 117 */ 118 @GuardedBy("mColorMatrix") 119 private float[] computeColorMatrixLocked() { 120 final int count = mColorMatrix.size(); 121 if (count == 0) { 122 return null; 123 } 124 125 final float[][] result = mTempColorMatrix; 126 Matrix.setIdentityM(result[0], 0); 127 for (int i = 0; i < count; i++) { 128 float[] rhs = mColorMatrix.valueAt(i); 129 Matrix.multiplyMM(result[(i + 1) % 2], 0, result[i % 2], 0, rhs, 0); 130 } 131 return result[count % 2]; 132 } 133 134 /** 135 * Returns the current Daltonization mode. 136 */ 137 public int getDaltonizerMode() { 138 synchronized (mDaltonizerModeLock) { 139 return mDaltonizerMode; 140 } 141 } 142 143 /** 144 * Sets the current Daltonization mode. This adjusts the color space to correct for or simulate 145 * various types of color blindness. 146 * 147 * @param mode the new Daltonization mode, or -1 to disable 148 */ 149 public void setDaltonizerMode(int mode) { 150 synchronized (mDaltonizerModeLock) { 151 if (mDaltonizerMode != mode) { 152 mDaltonizerMode = mode; 153 applyDaltonizerMode(mode); 154 } 155 } 156 } 157 158 /** 159 * Propagates the provided color transformation matrix to the SurfaceFlinger. 160 */ 161 private static void applyColorMatrix(float[] m) { 162 final IBinder flinger = ServiceManager.getService("SurfaceFlinger"); 163 if (flinger != null) { 164 final Parcel data = Parcel.obtain(); 165 data.writeInterfaceToken("android.ui.ISurfaceComposer"); 166 if (m != null) { 167 data.writeInt(1); 168 for (int i = 0; i < 16; i++) { 169 data.writeFloat(m[i]); 170 } 171 } else { 172 data.writeInt(0); 173 } 174 try { 175 flinger.transact(1015, data, null, 0); 176 } catch (RemoteException ex) { 177 Slog.e(TAG, "Failed to set color transform", ex); 178 } finally { 179 data.recycle(); 180 } 181 } 182 } 183 184 /** 185 * Propagates the provided Daltonization mode to the SurfaceFlinger. 186 */ 187 private static void applyDaltonizerMode(int mode) { 188 final IBinder flinger = ServiceManager.getService("SurfaceFlinger"); 189 if (flinger != null) { 190 final Parcel data = Parcel.obtain(); 191 data.writeInterfaceToken("android.ui.ISurfaceComposer"); 192 data.writeInt(mode); 193 try { 194 flinger.transact(1014, data, null, 0); 195 } catch (RemoteException ex) { 196 Slog.e(TAG, "Failed to set Daltonizer mode", ex); 197 } finally { 198 data.recycle(); 199 } 200 } 201 } 202 } 203