Rename color transform to color mode and persist the value.

Also, standardize on a set of possible modes for the displays to
enter and separate the configuration of the color mode from the
configuration of the display mode.

Bug: 29044347

Change-Id: I6af0a7d1f11bc72d4cefc380f115c1fb00788864
diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java
index 701b9f1..6ba25a5 100644
--- a/services/core/java/com/android/server/display/DisplayAdapter.java
+++ b/services/core/java/com/android/server/display/DisplayAdapter.java
@@ -49,13 +49,6 @@
      */
     private static final AtomicInteger NEXT_DISPLAY_MODE_ID = new AtomicInteger(1);  // 0 = no mode.
 
-    /**
-     * Used to generate globally unique color transform ids.
-     *
-     * Valid IDs start at 1 with 0 as the sentinel value for the default mode.
-     */
-    private static final AtomicInteger NEXT_COLOR_TRANSFORM_ID = new AtomicInteger(1);
-
     // Called with SyncRoot lock held.
     public DisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
             Context context, Handler handler, Listener listener, String name) {
@@ -141,11 +134,6 @@
                 NEXT_DISPLAY_MODE_ID.getAndIncrement(), width, height, refreshRate);
     }
 
-    public static Display.ColorTransform createColorTransform(int colorTransform) {
-        return new Display.ColorTransform(
-                NEXT_COLOR_TRANSFORM_ID.getAndIncrement(), colorTransform);
-    }
-
     public interface Listener {
         public void onDisplayDeviceEvent(DisplayDevice device, int event);
         public void onTraversalRequested();
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 7af0bdb..839ab4d 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -94,6 +94,11 @@
     }
 
     /**
+     * Returns whether the unique id of the device is stable across reboots.
+     */
+    public abstract boolean hasStableUniqueId();
+
+    /**
      * Gets information about the display device.
      *
      * The information returned should not change between calls unless the display
@@ -135,7 +140,7 @@
     /**
      * Sets the mode, if supported.
      */
-    public void requestColorTransformAndModeInTransactionLocked(int colorTransformId, int modeId) {
+    public void requestDisplayModesInTransactionLocked(int colorMode, int modeId) {
     }
 
     /**
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 5ce66fa..6719182 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -118,6 +118,11 @@
     public static final int DIFF_OTHER = 1 << 1;
 
     /**
+     * Diff result: The color mode fields differ.
+     */
+    public static final int DIFF_COLOR_MODE = 1 << 2;
+
+    /**
      * Gets the name of the display device, which may be derived from EDID or
      * other sources. The name may be localized and displayed to the user.
      */
@@ -155,14 +160,11 @@
      */
     public Display.Mode[] supportedModes = Display.Mode.EMPTY_ARRAY;
 
-    /** The active color transform of the display */
-    public int colorTransformId;
+    /** The active color mode of the display */
+    public int colorMode;
 
-    /** The default color transform of the display */
-    public int defaultColorTransformId;
-
-    /** The supported color transforms of the display */
-    public Display.ColorTransform[] supportedColorTransforms = Display.ColorTransform.EMPTY_ARRAY;
+    /** The supported color modes of the display */
+    public int[] supportedColorModes = { Display.COLOR_MODE_DEFAULT };
 
     /**
      * The HDR capabilities this display claims to support.
@@ -283,6 +285,9 @@
         if (state != other.state) {
             diff |= DIFF_STATE;
         }
+        if (colorMode != other.colorMode) {
+            diff |= DIFF_COLOR_MODE;
+        }
         if (!Objects.equal(name, other.name)
                 || !Objects.equal(uniqueId, other.uniqueId)
                 || width != other.width
@@ -290,9 +295,7 @@
                 || modeId != other.modeId
                 || defaultModeId != other.defaultModeId
                 || !Arrays.equals(supportedModes, other.supportedModes)
-                || colorTransformId != other.colorTransformId
-                || defaultColorTransformId != other.defaultColorTransformId
-                || !Arrays.equals(supportedColorTransforms, other.supportedColorTransforms)
+                || !Arrays.equals(supportedColorModes, other.supportedColorModes)
                 || !Objects.equal(hdrCapabilities, other.hdrCapabilities)
                 || densityDpi != other.densityDpi
                 || xDpi != other.xDpi
@@ -324,9 +327,8 @@
         modeId = other.modeId;
         defaultModeId = other.defaultModeId;
         supportedModes = other.supportedModes;
-        colorTransformId = other.colorTransformId;
-        defaultColorTransformId = other.defaultColorTransformId;
-        supportedColorTransforms = other.supportedColorTransforms;
+        colorMode = other.colorMode;
+        supportedColorModes = other.supportedColorModes;
         hdrCapabilities = other.hdrCapabilities;
         densityDpi = other.densityDpi;
         xDpi = other.xDpi;
@@ -353,9 +355,8 @@
         sb.append(", modeId ").append(modeId);
         sb.append(", defaultModeId ").append(defaultModeId);
         sb.append(", supportedModes ").append(Arrays.toString(supportedModes));
-        sb.append(", colorTransformId ").append(colorTransformId);
-        sb.append(", defaultColorTransformId ").append(defaultColorTransformId);
-        sb.append(", supportedColorTransforms ").append(Arrays.toString(supportedColorTransforms));
+        sb.append(", colorMode ").append(colorMode);
+        sb.append(", supportedColorModes ").append(Arrays.toString(supportedColorModes));
         sb.append(", HdrCapabilities ").append(hdrCapabilities);
         sb.append(", density ").append(densityDpi);
         sb.append(", ").append(xDpi).append(" x ").append(yDpi).append(" dpi");
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index fec7ed1..0abd2e7 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -239,6 +239,11 @@
 
     @Override
     public void onStart() {
+        // We need to pre-load the persistent data store so it's ready before the default display
+        // adapter is up so that we have it's configuration. We could load it lazily, but since
+        // we're going to have to read it in eventually we may as well do it here rather than after
+        // we've waited for the diplay to register itself with us.
+        mPersistentDataStore.loadIfNeeded();
         mHandler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER);
 
         publishBinderService(Context.DISPLAY_SERVICE, new BinderService(),
@@ -541,12 +546,12 @@
         }
     }
 
-    private void requestColorTransformInternal(int displayId, int colorTransformId) {
+    private void requestColorModeInternal(int displayId, int colorMode) {
         synchronized (mSyncRoot) {
             LogicalDisplay display = mLogicalDisplays.get(displayId);
             if (display != null &&
-                    display.getRequestedColorTransformIdLocked() != colorTransformId) {
-                display.setRequestedColorTransformIdLocked(colorTransformId);
+                    display.getRequestedColorModeLocked() != colorMode) {
+                display.setRequestedColorModeLocked(colorMode);
                 scheduleTraversalLocked(false);
             }
         }
@@ -691,11 +696,15 @@
         device.mDebugLastLoggedDeviceInfo = info;
 
         mDisplayDevices.add(device);
-        addLogicalDisplayLocked(device);
+        LogicalDisplay display = addLogicalDisplayLocked(device);
         Runnable work = updateDisplayStateLocked(device);
         if (work != null) {
             work.run();
         }
+        if (display != null && display.getPrimaryDisplayDeviceLocked() == device) {
+            int colorMode = mPersistentDataStore.getColorMode(device);
+            display.setRequestedColorModeLocked(colorMode);
+        }
         scheduleTraversalLocked(false);
     }
 
@@ -714,6 +723,13 @@
             } else if (diff != 0) {
                 Slog.i(TAG, "Display device changed: " + info);
             }
+            if ((diff & DisplayDeviceInfo.DIFF_COLOR_MODE) != 0) {
+                try {
+                    mPersistentDataStore.setColorMode(device, info.colorMode);
+                } finally {
+                    mPersistentDataStore.saveIfNeeded();
+                }
+            }
             device.mDebugLastLoggedDeviceInfo = info;
 
             device.applyPendingDisplayDeviceInfoChangesLocked();
@@ -766,7 +782,7 @@
 
     // Adds a new logical display based on the given display device.
     // Sends notifications if needed.
-    private void addLogicalDisplayLocked(DisplayDevice device) {
+    private LogicalDisplay addLogicalDisplayLocked(DisplayDevice device) {
         DisplayDeviceInfo deviceInfo = device.getDisplayDeviceInfoLocked();
         boolean isDefault = (deviceInfo.flags
                 & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0;
@@ -778,7 +794,7 @@
         if (!isDefault && mSingleDisplayDemoMode) {
             Slog.i(TAG, "Not creating a logical display for a secondary display "
                     + " because single display demo mode is enabled: " + deviceInfo);
-            return;
+            return null;
         }
 
         final int displayId = assignDisplayIdLocked(isDefault);
@@ -790,7 +806,7 @@
             // This should never happen currently.
             Slog.w(TAG, "Ignoring display device because the logical display "
                     + "created from it was not considered valid: " + deviceInfo);
-            return;
+            return null;
         }
 
         mLogicalDisplays.put(displayId, display);
@@ -801,6 +817,7 @@
         }
 
         sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
+        return display;
     }
 
     private int assignDisplayIdLocked(boolean isDefault) {
@@ -1068,6 +1085,9 @@
             if (mDisplayPowerController != null) {
                 mDisplayPowerController.dump(pw);
             }
+
+            pw.println();
+            mPersistentDataStore.dump(pw);
         }
     }
 
@@ -1352,13 +1372,13 @@
         }
 
         @Override // Binder call
-        public void requestColorTransform(int displayId, int colorTransformId) {
+        public void requestColorMode(int displayId, int colorMode) {
             mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.CONFIGURE_DISPLAY_COLOR_TRANSFORM,
-                    "Permission required to change the display color transform");
+                    Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE,
+                    "Permission required to change the display color mode");
             final long token = Binder.clearCallingIdentity();
             try {
-                requestColorTransformInternal(displayId, colorTransformId);
+                requestColorModeInternal(displayId, colorMode);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 7b16ea6..61c2eac 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -39,6 +39,8 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * A display adapter for the local displays managed by Surface Flinger.
@@ -100,14 +102,25 @@
                         builtInDisplayId);
                 return;
             }
+            int activeColorMode = SurfaceControl.getActiveColorMode(displayToken);
+            if (activeColorMode < 0) {
+                // We failed to get the active color mode. We don't bail out here since on the next
+                // configuration pass we'll go ahead and set it to whatever it was set to last (or
+                // COLOR_MODE_NATIVE if this is the first configuration).
+                Slog.w(TAG, "Unable to get active color mode for display device " +
+                        builtInDisplayId);
+                activeColorMode = Display.COLOR_MODE_INVALID;
+            }
+            int[] colorModes = SurfaceControl.getDisplayColorModes(displayToken);
             LocalDisplayDevice device = mDevices.get(builtInDisplayId);
             if (device == null) {
                 // Display was added.
                 device = new LocalDisplayDevice(displayToken, builtInDisplayId,
-                        configs, activeConfig);
+                        configs, activeConfig, colorModes, activeColorMode);
                 mDevices.put(builtInDisplayId, device);
                 sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);
-            } else if (device.updatePhysicalDisplayInfoLocked(configs, activeConfig)) {
+            } else if (device.updatePhysicalDisplayInfoLocked(configs, activeConfig,
+                        colorModes, activeColorMode)) {
                 // Display properties changed.
                 sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED);
             }
@@ -144,8 +157,7 @@
         private final int mBuiltInDisplayId;
         private final Light mBacklight;
         private final SparseArray<DisplayModeRecord> mSupportedModes = new SparseArray<>();
-        private final SparseArray<Display.ColorTransform> mSupportedColorTransforms =
-                new SparseArray<>();
+        private final ArrayList<Integer> mSupportedColorModes = new ArrayList<>();
 
         private DisplayDeviceInfo mInfo;
         private boolean mHavePendingChanges;
@@ -155,18 +167,20 @@
         private int mDefaultModeId;
         private int mActiveModeId;
         private boolean mActiveModeInvalid;
-        private int mDefaultColorTransformId;
-        private int mActiveColorTransformId;
-        private boolean mActiveColorTransformInvalid;
+        private int mActiveColorMode;
+        private boolean mActiveColorModeInvalid;
         private Display.HdrCapabilities mHdrCapabilities;
 
         private  SurfaceControl.PhysicalDisplayInfo mDisplayInfos[];
 
         public LocalDisplayDevice(IBinder displayToken, int builtInDisplayId,
-                SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo) {
+                SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo,
+                int[] colorModes, int activeColorMode) {
             super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + builtInDisplayId);
             mBuiltInDisplayId = builtInDisplayId;
-            updatePhysicalDisplayInfoLocked(physicalDisplayInfos, activeDisplayInfo);
+            updatePhysicalDisplayInfoLocked(physicalDisplayInfos, activeDisplayInfo,
+                    colorModes, activeColorMode);
+            updateColorModesLocked(colorModes, activeColorMode);
             if (mBuiltInDisplayId == SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
                 LightsManager lights = LocalServices.getService(LightsManager.class);
                 mBacklight = lights.getLight(LightsManager.LIGHT_ID_BACKLIGHT);
@@ -176,39 +190,16 @@
             mHdrCapabilities = SurfaceControl.getHdrCapabilities(displayToken);
         }
 
+        @Override
+        public boolean hasStableUniqueId() {
+            return true;
+        }
+
         public boolean updatePhysicalDisplayInfoLocked(
-                SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo) {
+                SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo,
+                int[] colorModes, int activeColorMode) {
             mDisplayInfos = Arrays.copyOf(physicalDisplayInfos, physicalDisplayInfos.length);
             mActivePhysIndex = activeDisplayInfo;
-            ArrayList<Display.ColorTransform> colorTransforms = new ArrayList<>();
-
-            // Build an updated list of all existing color transforms.
-            boolean colorTransformsAdded = false;
-            Display.ColorTransform activeColorTransform = null;
-            for (int i = 0; i < physicalDisplayInfos.length; i++) {
-                SurfaceControl.PhysicalDisplayInfo info = physicalDisplayInfos[i];
-                // First check to see if we've already added this color transform
-                boolean existingMode = false;
-                for (int j = 0; j < colorTransforms.size(); j++) {
-                    if (colorTransforms.get(j).getColorTransform() == info.colorTransform) {
-                        existingMode = true;
-                        break;
-                    }
-                }
-                if (existingMode) {
-                    continue;
-                }
-                Display.ColorTransform colorTransform = findColorTransform(info);
-                if (colorTransform == null) {
-                    colorTransform = createColorTransform(info.colorTransform);
-                    colorTransformsAdded = true;
-                }
-                colorTransforms.add(colorTransform);
-                if (i == activeDisplayInfo) {
-                    activeColorTransform = colorTransform;
-                }
-            }
-
             // Build an updated list of all existing modes.
             ArrayList<DisplayModeRecord> records = new ArrayList<DisplayModeRecord>();
             boolean modesAdded = false;
@@ -254,21 +245,10 @@
                 mActiveModeInvalid = true;
                 sendTraversalRequestLocked();
             }
-            // Check whether surface flinger spontaneously changed color transforms out from under
-            // us.
-            if (mActiveColorTransformId != 0
-                    && mActiveColorTransformId != activeColorTransform.getId()) {
-                mActiveColorTransformInvalid = true;
-                sendTraversalRequestLocked();
-            }
 
-            boolean colorTransformsChanged =
-                    colorTransforms.size() != mSupportedColorTransforms.size()
-                    || colorTransformsAdded;
             boolean recordsChanged = records.size() != mSupportedModes.size() || modesAdded;
-            // If neither the records nor the supported color transforms have changed then we're
-            // done here.
-            if (!recordsChanged && !colorTransformsChanged) {
+            // If the records haven't changed then we're done here.
+            if (!recordsChanged) {
                 return false;
             }
             // Update the index of modes.
@@ -278,24 +258,13 @@
             for (DisplayModeRecord record : records) {
                 mSupportedModes.put(record.mMode.getModeId(), record);
             }
-            mSupportedColorTransforms.clear();
-            for (Display.ColorTransform colorTransform : colorTransforms) {
-                mSupportedColorTransforms.put(colorTransform.getId(), colorTransform);
-            }
-
-            // Update the default mode and color transform if needed. This needs to be done in
-            // tandem so we always have a default state to fall back to.
-            if (findDisplayInfoIndexLocked(mDefaultColorTransformId, mDefaultModeId) < 0) {
+            // Update the default mode, if needed.
+            if (findDisplayInfoIndexLocked(mDefaultModeId) < 0) {
                 if (mDefaultModeId != 0) {
                     Slog.w(TAG, "Default display mode no longer available, using currently"
                             + " active mode as default.");
                 }
                 mDefaultModeId = activeRecord.mMode.getModeId();
-                if (mDefaultColorTransformId != 0) {
-                    Slog.w(TAG, "Default color transform no longer available, using currently"
-                            + " active color transform as default");
-                }
-                mDefaultColorTransformId = activeColorTransform.getId();
             }
             // Determine whether the active mode is still there.
             if (mSupportedModes.indexOfKey(mActiveModeId) < 0) {
@@ -307,20 +276,62 @@
                 mActiveModeInvalid = true;
             }
 
-            // Determine whether the active color transform is still there.
-            if (mSupportedColorTransforms.indexOfKey(mActiveColorTransformId) < 0) {
-                if (mActiveColorTransformId != 0) {
-                    Slog.w(TAG, "Active color transform no longer available, reverting"
-                            + " to default transform.");
-                }
-                mActiveColorTransformId = mDefaultColorTransformId;
-                mActiveColorTransformInvalid = true;
-            }
             // Schedule traversals so that we apply pending changes.
             sendTraversalRequestLocked();
             return true;
         }
 
+        private boolean updateColorModesLocked(int[] colorModes,
+                int activeColorMode) {
+            List<Integer> pendingColorModes = new ArrayList<>();
+
+            // Build an updated list of all existing color modes.
+            boolean colorModesAdded = false;
+            for (int colorMode: colorModes) {
+                if (!mSupportedColorModes.contains(colorMode)) {
+                    colorModesAdded = true;
+                }
+                pendingColorModes.add(colorMode);
+            }
+
+            boolean colorModesChanged =
+                    pendingColorModes.size() != mSupportedColorModes.size()
+                    || colorModesAdded;
+
+            // If the supported color modes haven't changed then we're done here.
+            if (!colorModesChanged) {
+                return false;
+            }
+
+            mHavePendingChanges = true;
+
+            mSupportedColorModes.clear();
+            mSupportedColorModes.addAll(pendingColorModes);
+            Collections.sort(mSupportedColorModes);
+
+            // Determine whether the active color mode is still there.
+            if (!mSupportedColorModes.contains(mActiveColorMode)) {
+                if (mActiveColorMode != 0) {
+                    Slog.w(TAG, "Active color mode no longer available, reverting"
+                            + " to default mode.");
+                    mActiveColorMode = Display.COLOR_MODE_DEFAULT;
+                    mActiveColorModeInvalid = true;
+                } else {
+                    if (!mSupportedColorModes.isEmpty()) {
+                        // This should never happen.
+                        Slog.e(TAG, "Default and active color mode is no longer available!"
+                                + " Reverting to first available mode.");
+                        mActiveColorMode = mSupportedColorModes.get(0);
+                        mActiveColorModeInvalid = true;
+                    } else {
+                        // This should really never happen.
+                        Slog.e(TAG, "No color modes available!");
+                    }
+                }
+            }
+            return true;
+        }
+
         private DisplayModeRecord findDisplayModeRecord(SurfaceControl.PhysicalDisplayInfo info) {
             for (int i = 0; i < mSupportedModes.size(); i++) {
                 DisplayModeRecord record = mSupportedModes.valueAt(i);
@@ -331,16 +342,6 @@
             return null;
         }
 
-        private Display.ColorTransform findColorTransform(SurfaceControl.PhysicalDisplayInfo info) {
-            for (int i = 0; i < mSupportedColorTransforms.size(); i++) {
-                Display.ColorTransform transform = mSupportedColorTransforms.valueAt(i);
-                if (transform.getColorTransform() == info.colorTransform) {
-                    return transform;
-                }
-            }
-            return null;
-        }
-
         @Override
         public void applyPendingDisplayDeviceInfoChangesLocked() {
             if (mHavePendingChanges) {
@@ -363,12 +364,11 @@
                     DisplayModeRecord record = mSupportedModes.valueAt(i);
                     mInfo.supportedModes[i] = record.mMode;
                 }
-                mInfo.colorTransformId = mActiveColorTransformId;
-                mInfo.defaultColorTransformId = mDefaultColorTransformId;
-                mInfo.supportedColorTransforms =
-                        new Display.ColorTransform[mSupportedColorTransforms.size()];
-                for (int i = 0; i < mSupportedColorTransforms.size(); i++) {
-                    mInfo.supportedColorTransforms[i] = mSupportedColorTransforms.valueAt(i);
+                mInfo.colorMode = mActiveColorMode;
+                mInfo.supportedColorModes =
+                        new int[mSupportedColorModes.size()];
+                for (int i = 0; i < mSupportedColorModes.size(); i++) {
+                    mInfo.supportedColorModes[i] = mSupportedColorModes.get(i);
                 }
                 mInfo.hdrCapabilities = mHdrCapabilities;
                 mInfo.appVsyncOffsetNanos = phys.appVsyncOffsetNanos;
@@ -520,8 +520,15 @@
         }
 
         @Override
-        public void requestColorTransformAndModeInTransactionLocked(
-                int colorTransformId, int modeId) {
+        public void requestDisplayModesInTransactionLocked(
+                int colorMode, int modeId) {
+            if (requestModeInTransactionLocked(modeId) ||
+                    requestColorModeInTransactionLocked(colorMode)) {
+                updateDeviceInfoLocked();
+            }
+        }
+
+        public boolean requestModeInTransactionLocked(int modeId) {
             if (modeId == 0) {
                 modeId = mDefaultModeId;
             } else if (mSupportedModes.indexOfKey(modeId) < 0) {
@@ -530,37 +537,36 @@
                 modeId = mDefaultModeId;
             }
 
-            if (colorTransformId == 0) {
-                colorTransformId = mDefaultColorTransformId;
-            } else if (mSupportedColorTransforms.indexOfKey(colorTransformId) < 0) {
-                Slog.w(TAG, "Requested color transform " + colorTransformId + " is not supported"
-                        + " by this display, reverting to the default color transform");
-                colorTransformId = mDefaultColorTransformId;
-            }
-            int physIndex = findDisplayInfoIndexLocked(colorTransformId, modeId);
+            int physIndex = findDisplayInfoIndexLocked(modeId);
             if (physIndex < 0) {
-                Slog.w(TAG, "Requested color transform, mode ID pair (" + colorTransformId + ", "
-                        + modeId + ") not available, trying color transform with default mode ID");
+                Slog.w(TAG, "Requested mode ID " + modeId + " not available,"
+                        + " trying with default mode ID");
                 modeId = mDefaultModeId;
-                physIndex = findDisplayInfoIndexLocked(colorTransformId, modeId);
-                if (physIndex < 0) {
-                    Slog.w(TAG, "Requested color transform with default mode ID still not"
-                            + " available, falling back to default color transform with default"
-                            + " mode.");
-                    colorTransformId = mDefaultColorTransformId;
-                    physIndex = findDisplayInfoIndexLocked(colorTransformId, modeId);
-                }
+                physIndex = findDisplayInfoIndexLocked(modeId);
             }
             if (mActivePhysIndex == physIndex) {
-                return;
+                return false;
             }
             SurfaceControl.setActiveConfig(getDisplayTokenLocked(), physIndex);
             mActivePhysIndex = physIndex;
             mActiveModeId = modeId;
             mActiveModeInvalid = false;
-            mActiveColorTransformId = colorTransformId;
-            mActiveColorTransformInvalid = false;
-            updateDeviceInfoLocked();
+            return true;
+        }
+
+        public boolean requestColorModeInTransactionLocked(int colorMode) {
+            if (mActiveColorMode == colorMode) {
+                return false;
+            }
+            if (!mSupportedColorModes.contains(colorMode)) {
+                Slog.w(TAG, "Unable to find color mode " + colorMode
+                        + ", ignoring request.");
+                return false;
+            }
+            SurfaceControl.setActiveColorMode(getDisplayTokenLocked(), colorMode);
+            mActiveColorMode = colorMode;
+            mActiveColorModeInvalid = false;
+            return true;
         }
 
         @Override
@@ -569,7 +575,7 @@
             pw.println("mBuiltInDisplayId=" + mBuiltInDisplayId);
             pw.println("mActivePhysIndex=" + mActivePhysIndex);
             pw.println("mActiveModeId=" + mActiveModeId);
-            pw.println("mActiveColorTransformId=" + mActiveColorTransformId);
+            pw.println("mActiveColorMode=" + mActiveColorMode);
             pw.println("mState=" + Display.stateToString(mState));
             pw.println("mBrightness=" + mBrightness);
             pw.println("mBacklight=" + mBacklight);
@@ -581,24 +587,22 @@
             for (int i = 0; i < mSupportedModes.size(); i++) {
                 pw.println("  " + mSupportedModes.valueAt(i));
             }
-            pw.println("mSupportedColorTransforms=[");
-            for (int i = 0; i < mSupportedColorTransforms.size(); i++) {
+            pw.print("mSupportedColorModes=[");
+            for (int i = 0; i < mSupportedColorModes.size(); i++) {
                 if (i != 0) {
                     pw.print(", ");
                 }
-                pw.print(mSupportedColorTransforms.valueAt(i));
+                pw.print(mSupportedColorModes.get(i));
             }
             pw.println("]");
         }
 
-        private int findDisplayInfoIndexLocked(int colorTransformId, int modeId) {
+        private int findDisplayInfoIndexLocked(int modeId) {
             DisplayModeRecord record = mSupportedModes.get(modeId);
-            Display.ColorTransform transform = mSupportedColorTransforms.get(colorTransformId);
-            if (record != null && transform != null) {
+            if (record != null) {
                 for (int i = 0; i < mDisplayInfos.length; i++) {
                     SurfaceControl.PhysicalDisplayInfo info = mDisplayInfos[i];
-                    if (info.colorTransform == transform.getColorTransform()
-                            && record.hasMatchingMode(info)){
+                    if (record.hasMatchingMode(info)){
                         return i;
                     }
                 }
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 973f04c..287a25a 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -74,7 +74,7 @@
     private boolean mHasContent;
 
     private int mRequestedModeId;
-    private int mRequestedColorTransformId;
+    private int mRequestedColorMode;
 
     // The display offsets to apply to the display projection.
     private int mDisplayOffsetX;
@@ -236,11 +236,10 @@
             mBaseDisplayInfo.defaultModeId = deviceInfo.defaultModeId;
             mBaseDisplayInfo.supportedModes = Arrays.copyOf(
                     deviceInfo.supportedModes, deviceInfo.supportedModes.length);
-            mBaseDisplayInfo.colorTransformId = deviceInfo.colorTransformId;
-            mBaseDisplayInfo.defaultColorTransformId = deviceInfo.defaultColorTransformId;
-            mBaseDisplayInfo.supportedColorTransforms = Arrays.copyOf(
-                    deviceInfo.supportedColorTransforms,
-                    deviceInfo.supportedColorTransforms.length);
+            mBaseDisplayInfo.colorMode = deviceInfo.colorMode;
+            mBaseDisplayInfo.supportedColorModes = Arrays.copyOf(
+                    deviceInfo.supportedColorModes,
+                    deviceInfo.supportedColorModes.length);
             mBaseDisplayInfo.hdrCapabilities = deviceInfo.hdrCapabilities;
             mBaseDisplayInfo.logicalDensityDpi = deviceInfo.densityDpi;
             mBaseDisplayInfo.physicalXDpi = deviceInfo.xDpi;
@@ -282,12 +281,12 @@
         // Set the layer stack.
         device.setLayerStackInTransactionLocked(isBlanked ? BLANK_LAYER_STACK : mLayerStack);
 
-        // Set the color transform and mode.
+        // Set the color mode and mode.
         if (device == mPrimaryDisplayDevice) {
-            device.requestColorTransformAndModeInTransactionLocked(
-                    mRequestedColorTransformId, mRequestedModeId);
+            device.requestDisplayModesInTransactionLocked(
+                    mRequestedColorMode, mRequestedModeId);
         } else {
-            device.requestColorTransformAndModeInTransactionLocked(0, 0);  // Revert to default.
+            device.requestDisplayModesInTransactionLocked(0, 0);  // Revert to default.
         }
 
         // Only grab the display info now as it may have been changed based on the requests above.
@@ -391,15 +390,15 @@
     }
 
     /**
-     * Requests the given color transform.
+     * Requests the given color mode.
      */
-    public void setRequestedColorTransformIdLocked(int colorTransformId) {
-        mRequestedColorTransformId = colorTransformId;
+    public void setRequestedColorModeLocked(int colorMode) {
+        mRequestedColorMode = colorMode;
     }
 
-    /** Returns the pending requested color transform. */
-    public int getRequestedColorTransformIdLocked() {
-        return mRequestedColorTransformId;
+    /** Returns the pending requested color mode. */
+    public int getRequestedColorModeLocked() {
+        return mRequestedColorMode;
     }
 
     /**
@@ -429,7 +428,7 @@
         pw.println("mLayerStack=" + mLayerStack);
         pw.println("mHasContent=" + mHasContent);
         pw.println("mRequestedMode=" + mRequestedModeId);
-        pw.println("mRequestedColorTransformId=" + mRequestedColorTransformId);
+        pw.println("mRequestedColorMode=" + mRequestedColorMode);
         pw.println("mDisplayOffset=(" + mDisplayOffsetX + ", " + mDisplayOffsetY + ")");
         pw.println("mPrimaryDisplayDevice=" + (mPrimaryDisplayDevice != null ?
                 mPrimaryDisplayDevice.getNameLocked() : "null"));
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index cf6264a..27327d4 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -266,6 +266,11 @@
         }
 
         @Override
+        public boolean hasStableUniqueId() {
+            return false;
+        }
+
+        @Override
         public void performTraversalInTransactionLocked() {
             if (mSurfaceTexture != null) {
                 if (mSurface == null) {
@@ -310,7 +315,7 @@
         }
 
         @Override
-        public void requestColorTransformAndModeInTransactionLocked(int color, int id) {
+        public void requestDisplayModesInTransactionLocked(int color, int id) {
             int index = -1;
             if (id == 0) {
                 // Use the default.
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index d676b35..5616fb9 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -27,6 +27,7 @@
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.Xml;
+import android.view.Display;
 
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
@@ -35,8 +36,11 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
 
 import libcore.io.IoUtils;
 import libcore.util.Objects;
@@ -50,8 +54,13 @@
  * &lt;display-manager-state>
  *   &lt;remembered-wifi-displays>
  *     &lt;wifi-display deviceAddress="00:00:00:00:00:00" deviceName="XXXX" deviceAlias="YYYY" />
- *   &gt;remembered-wifi-displays>
- * &gt;/display-manager-state>
+ *   &lt;remembered-wifi-displays>
+ *   &lt;display-states>
+ *      &lt;display>
+ *          &lt;color-mode>0&lt;/color-mode>
+ *      &lt;/display>
+ *  &lt;/display-states>
+ * &lt;/display-manager-state>
  * </code>
  *
  * TODO: refactor this to extract common code shared with the input manager's data store
@@ -62,6 +71,10 @@
     // Remembered Wifi display devices.
     private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>();
 
+    // Display state by unique id.
+    private final HashMap<String, DisplayState> mDisplayStates =
+            new HashMap<String, DisplayState>();
+
     // The atomic file used to safely read or write the file.
     private final AtomicFile mAtomicFile;
 
@@ -168,7 +181,41 @@
         return -1;
     }
 
-    private void loadIfNeeded() {
+    public int getColorMode(DisplayDevice device) {
+        if (!device.hasStableUniqueId()) {
+            return Display.COLOR_MODE_DEFAULT;
+        }
+        DisplayState state = getDisplayState(device.getUniqueId(), false);
+        if (state == null) {
+            return Display.COLOR_MODE_DEFAULT;
+        }
+        return state.getColorMode();
+    }
+
+    public boolean setColorMode(DisplayDevice device, int colorMode) {
+        if (!device.hasStableUniqueId()) {
+            return false;
+        }
+        DisplayState state = getDisplayState(device.getUniqueId(), true);
+        if (state.setColorMode(colorMode)) {
+            setDirty();
+            return true;
+        }
+        return false;
+    }
+
+    private DisplayState getDisplayState(String uniqueId, boolean createIfAbsent) {
+        loadIfNeeded();
+        DisplayState state = mDisplayStates.get(uniqueId);
+        if (state == null && createIfAbsent) {
+            state = new DisplayState();
+            mDisplayStates.put(uniqueId, state);
+            setDirty();
+        }
+        return state;
+    }
+
+    public void loadIfNeeded() {
         if (!mLoaded) {
             load();
             mLoaded = true;
@@ -240,6 +287,9 @@
             if (parser.getName().equals("remembered-wifi-displays")) {
                 loadRememberedWifiDisplaysFromXml(parser);
             }
+            if (parser.getName().equals("display-states")) {
+                loadDisplaysFromXml(parser);
+            }
         }
     }
 
@@ -267,6 +317,27 @@
         }
     }
 
+    private void loadDisplaysFromXml(XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        final int outerDepth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+            if (parser.getName().equals("display")) {
+                String uniqueId = parser.getAttributeValue(null, "unique-id");
+                if (uniqueId == null) {
+                    throw new XmlPullParserException(
+                            "Missing unique-id attribute on display.");
+                }
+                if (mDisplayStates.containsKey(uniqueId)) {
+                    throw new XmlPullParserException("Found duplicate display.");
+                }
+
+                DisplayState state = new DisplayState();
+                state.loadFromXml(parser);
+                mDisplayStates.put(uniqueId, state);
+            }
+        }
+    }
+
     private void saveToXml(XmlSerializer serializer) throws IOException {
         serializer.startDocument(null, true);
         serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
@@ -282,7 +353,72 @@
             serializer.endTag(null, "wifi-display");
         }
         serializer.endTag(null, "remembered-wifi-displays");
+        serializer.startTag(null, "display-states");
+        for (Map.Entry<String, DisplayState> entry : mDisplayStates.entrySet()) {
+            final String uniqueId = entry.getKey();
+            final DisplayState state = entry.getValue();
+            serializer.startTag(null, "display");
+            serializer.attribute(null, "unique-id", uniqueId);
+            state.saveToXml(serializer);
+            serializer.endTag(null, "display");
+        }
+        serializer.endTag(null, "display-states");
         serializer.endTag(null, "display-manager-state");
         serializer.endDocument();
     }
+
+    public void dump(PrintWriter pw) {
+        pw.println("PersistentDataStore");
+        pw.println("  mLoaded=" + mLoaded);
+        pw.println("  mDirty=" + mDirty);
+        pw.println("  RememberedWifiDisplays:");
+        int i = 0;
+        for (WifiDisplay display : mRememberedWifiDisplays) {
+            pw.println("    " + i++ + ": " + display);
+        }
+        pw.println("  DisplayStates:");
+        i = 0;
+        for (Map.Entry<String, DisplayState> entry : mDisplayStates.entrySet()) {
+            pw.println("    " + i++ + ": " + entry.getKey());
+            entry.getValue().dump(pw, "      ");
+        }
+    }
+
+    private static final class DisplayState {
+        private int mColorMode;
+
+        public boolean setColorMode(int colorMode) {
+            if (colorMode == mColorMode) {
+                return false;
+            }
+            mColorMode = colorMode;
+            return true;
+        }
+
+        public int getColorMode() {
+            return mColorMode;
+        }
+
+        public void loadFromXml(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            final int outerDepth = parser.getDepth();
+
+            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+                if (parser.getName().equals("color-mode")) {
+                    String value = parser.nextText();
+                    mColorMode = Integer.parseInt(value);
+                }
+            }
+        }
+
+        public void saveToXml(XmlSerializer serializer) throws IOException {
+            serializer.startTag(null, "color-mode");
+            serializer.text(Integer.toString(mColorMode));
+            serializer.endTag(null, "color-mode");
+        }
+
+        private void dump(final PrintWriter pw, final String prefix) {
+            pw.println(prefix + "ColorMode=" + mColorMode);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 986efd69..9d0fde5 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -225,6 +225,11 @@
         }
 
         @Override
+        public boolean hasStableUniqueId() {
+            return false;
+        }
+
+        @Override
         public Runnable requestDisplayStateLocked(int state, int brightness) {
             if (state != mDisplayState) {
                 mDisplayState = state;
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
index 64bc729..08c0a1a 100644
--- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -602,6 +602,11 @@
             mMode = createMode(width, height, refreshRate);
         }
 
+        @Override
+        public boolean hasStableUniqueId() {
+            return true;
+        }
+
         public void destroyLocked() {
             if (mSurface != null) {
                 mSurface.release();