Home | History | Annotate | Download | only in display
      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