Add a SystemApi to control display saturation.

Allows an app with the appropriate permission to reduce
(make more gray) the saturation of colors on the display.

Test: manual
Bug: 77189882
Change-Id: I2b7b065d07bd2ca24730b2e8c52d161c22b307ab
diff --git a/api/system-current.txt b/api/system-current.txt
index a81afed5..ed763ba 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -57,6 +57,7 @@
     field public static final java.lang.String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS";
     field public static final java.lang.String CONNECTIVITY_INTERNAL = "android.permission.CONNECTIVITY_INTERNAL";
     field public static final java.lang.String CONNECTIVITY_USE_RESTRICTED_NETWORKS = "android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS";
+    field public static final java.lang.String CONTROL_DISPLAY_SATURATION = "android.permission.CONTROL_DISPLAY_SATURATION";
     field public static final java.lang.String CONTROL_INCALL_EXPERIENCE = "android.permission.CONTROL_INCALL_EXPERIENCE";
     field public static final java.lang.String CONTROL_LOCATION_UPDATES = "android.permission.CONTROL_LOCATION_UPDATES";
     field public static final java.lang.String CONTROL_VPN = "android.permission.CONTROL_VPN";
@@ -1252,6 +1253,7 @@
     method public android.hardware.display.BrightnessConfiguration getDefaultBrightnessConfiguration();
     method public android.graphics.Point getStableDisplaySize();
     method public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration);
+    method public void setSaturationLevel(float);
   }
 
 }
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index a3b2d22..efb9517 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -535,6 +535,19 @@
     }
 
     /**
+     * Set the level of color saturation to apply to the display.
+     * @param level The amount of saturation to apply, between 0 and 1 inclusive.
+     * 0 produces a grayscale image, 1 is normal.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_SATURATION)
+    public void setSaturationLevel(float level) {
+        mGlobal.setSaturationLevel(level);
+    }
+
+    /**
      * Creates a virtual display.
      *
      * @see #createVirtualDisplay(String, int, int, int, Surface, int,
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 1f67a6b..2d0ef2f 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -384,6 +384,17 @@
         }
     }
 
+    /**
+     * Set the level of color saturation to apply to the display.
+     */
+    public void setSaturationLevel(float level) {
+        try {
+            mDm.setSaturationLevel(level);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
     public VirtualDisplay createVirtualDisplay(Context context, MediaProjection projection,
             String name, int width, int height, int densityDpi, Surface surface, int flags,
             VirtualDisplay.Callback callback, Handler handler, String uniqueId) {
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 9fcb9d3..b77de748 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -65,6 +65,9 @@
     // Requires CONFIGURE_DISPLAY_COLOR_MODE
     void requestColorMode(int displayId, int colorMode);
 
+    // Requires CONTROL_DISPLAY_SATURATION
+    void setSaturationLevel(float level);
+
     // Requires CAPTURE_VIDEO_OUTPUT, CAPTURE_SECURE_VIDEO_OUTPUT, or an appropriate
     // MediaProjection token for certain combinations of flags.
     int createVirtualDisplay(in IVirtualDisplayCallback callback,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index cb375d5..f4715fc 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3121,6 +3121,12 @@
     <permission android:name="android.permission.CONFIGURE_DISPLAY_COLOR_MODE"
         android:protectionLevel="signature" />
 
+    <!-- Allows an application to control the color saturation of the display.
+         @hide
+         @SystemApi -->
+    <permission android:name="android.permission.CONTROL_DISPLAY_SATURATION"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Allows an application to collect usage infomation about brightness slider changes.
          <p>Not for use by third-party applications.</p>
          @hide
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index c4b2b5e..c7ae1f4 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -695,6 +695,27 @@
         }
     }
 
+    private void setSaturationLevelInternal(float level) {
+        if (level < 0 || level > 1) {
+            throw new IllegalArgumentException("Saturation level must be between 0 and 1");
+        }
+        float[] matrix = (level == 1.0f ? null : computeSaturationMatrix(level));
+        DisplayTransformManager dtm = LocalServices.getService(DisplayTransformManager.class);
+        dtm.setColorMatrix(DisplayTransformManager.LEVEL_COLOR_MATRIX_SATURATION, matrix);
+    }
+
+    private static float[] computeSaturationMatrix(float saturation) {
+        float desaturation = 1.0f - saturation;
+        float[] luminance = {0.231f * desaturation, 0.715f * desaturation, 0.072f * desaturation};
+        float[] matrix = {
+            luminance[0] + saturation, luminance[0], luminance[0], 0,
+            luminance[1], luminance[1] + saturation, luminance[1], 0,
+            luminance[2], luminance[2], luminance[2] + saturation, 0,
+            0, 0, 0, 1
+        };
+        return matrix;
+    }
+
     private int createVirtualDisplayInternal(IVirtualDisplayCallback callback,
             IMediaProjection projection, int callingUid, String packageName, String name, int width,
             int height, int densityDpi, Surface surface, int flags, String uniqueId) {
@@ -1687,6 +1708,19 @@
         }
 
         @Override // Binder call
+        public void setSaturationLevel(float level) {
+            mContext.enforceCallingOrSelfPermission(
+                   Manifest.permission.CONTROL_DISPLAY_SATURATION,
+                   "Permission required to set display saturation level");
+            final long token = Binder.clearCallingIdentity();
+            try {
+                setSaturationLevelInternal(level);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override // Binder call
         public int createVirtualDisplay(IVirtualDisplayCallback callback,
                 IMediaProjection projection, String packageName, String name,
                 int width, int height, int densityDpi, Surface surface, int flags,
diff --git a/services/core/java/com/android/server/display/DisplayTransformManager.java b/services/core/java/com/android/server/display/DisplayTransformManager.java
index 000fcf3..a94f049 100644
--- a/services/core/java/com/android/server/display/DisplayTransformManager.java
+++ b/services/core/java/com/android/server/display/DisplayTransformManager.java
@@ -17,7 +17,6 @@
 package com.android.server.display;
 
 import android.app.ActivityManager;
-import android.app.IActivityManager;
 import android.opengl.Matrix;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -28,7 +27,6 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import com.android.internal.annotations.GuardedBy;
-
 import com.android.internal.app.ColorDisplayController;
 import java.util.Arrays;
 
@@ -46,6 +44,10 @@
      */
     public static final int LEVEL_COLOR_MATRIX_NIGHT_DISPLAY = 100;
     /**
+     * Color transform level used to adjust the color saturation of the display.
+     */
+    public static final int LEVEL_COLOR_MATRIX_SATURATION = 150;
+    /**
      * Color transform level used by A11y services to make the display monochromatic.
      */
     public static final int LEVEL_COLOR_MATRIX_GRAYSCALE = 200;