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