Add automatic HDR transition animation

Test: enable flag && silkfx
Bug: 314810174

Change-Id: Ic69b633d6042145a537c7d39e713f804faff6600
diff --git a/core/java/android/view/HdrRenderState.java b/core/java/android/view/HdrRenderState.java
new file mode 100644
index 0000000..2fbbf48
--- /dev/null
+++ b/core/java/android/view/HdrRenderState.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.os.SystemClock;
+
+import com.android.graphics.hwui.flags.Flags;
+
+import java.util.function.Consumer;
+
+/** @hide */
+class HdrRenderState implements Consumer<Display> {
+    // Targeting an animation from 1x to 5x over 400ms means we need to increase by 0.01/ms
+    private static final float TRANSITION_PER_MS = 0.01f;
+
+    private static final boolean FLAG_ANIMATE_ENABLED = Flags.animateHdrTransitions();
+
+    private final ViewRootImpl mViewRoot;
+
+    private boolean mIsListenerRegistered = false;
+    private boolean mUpdateHdrSdrRatioInfo = false;
+    private float mDesiredHdrSdrRatio = 1f;
+    private float mTargetHdrSdrRatio = 1f;
+    private float mRenderHdrSdrRatio = 1f;
+    private float mPreviousRenderRatio = 1f;
+    private long mLastUpdateMillis = -1;
+
+    HdrRenderState(ViewRootImpl viewRoot) {
+        mViewRoot = viewRoot;
+    }
+
+    @Override
+    public void accept(Display display) {
+        forceUpdateHdrSdrRatio();
+        mViewRoot.invalidate();
+    }
+
+    boolean isHdrEnabled() {
+        return mDesiredHdrSdrRatio >= 1.01f;
+    }
+
+    void stopListening() {
+        if (mIsListenerRegistered) {
+            mViewRoot.mDisplay.unregisterHdrSdrRatioChangedListener(this);
+            mIsListenerRegistered = false;
+        }
+    }
+
+    void startListening() {
+        if (isHdrEnabled() && !mIsListenerRegistered && mViewRoot.mDisplay != null) {
+            mViewRoot.mDisplay.registerHdrSdrRatioChangedListener(mViewRoot.mExecutor, this);
+        }
+    }
+
+    /** @return true if something changed, else false */
+    boolean updateForFrame(long frameTimeMillis) {
+        boolean hasUpdate = mUpdateHdrSdrRatioInfo;
+        mUpdateHdrSdrRatioInfo = false;
+        mRenderHdrSdrRatio = mTargetHdrSdrRatio;
+        long timeDelta = Math.max(Math.min(32, frameTimeMillis - mLastUpdateMillis), 8);
+        final float maxStep = timeDelta * TRANSITION_PER_MS;
+        mLastUpdateMillis = frameTimeMillis;
+        if (hasUpdate && FLAG_ANIMATE_ENABLED) {
+            if (mTargetHdrSdrRatio == 1.0f) {
+                mPreviousRenderRatio = mTargetHdrSdrRatio;
+            } else {
+                float delta = mTargetHdrSdrRatio - mPreviousRenderRatio;
+                if (delta > maxStep) {
+                    mRenderHdrSdrRatio = mPreviousRenderRatio + maxStep;
+                    mUpdateHdrSdrRatioInfo = true;
+                    mViewRoot.invalidate();
+                }
+                mPreviousRenderRatio = mRenderHdrSdrRatio;
+            }
+        }
+        return hasUpdate;
+    }
+
+    float getDesiredHdrSdrRatio() {
+        return mDesiredHdrSdrRatio;
+    }
+
+    float getRenderHdrSdrRatio() {
+        return mRenderHdrSdrRatio;
+    }
+
+    void forceUpdateHdrSdrRatio() {
+        mTargetHdrSdrRatio = Math.min(mDesiredHdrSdrRatio, mViewRoot.mDisplay.getHdrSdrRatio());
+        mUpdateHdrSdrRatioInfo = true;
+    }
+
+    void setDesiredHdrSdrRatio(float desiredRatio) {
+        mLastUpdateMillis = SystemClock.uptimeMillis();
+        // TODO: When decreasing the desired ratio we need to animate it downwards
+        if (desiredRatio != mDesiredHdrSdrRatio) {
+            mDesiredHdrSdrRatio = desiredRatio;
+            forceUpdateHdrSdrRatio();
+            mViewRoot.invalidate();
+
+            if (isHdrEnabled()) {
+                startListening();
+            } else {
+                stopListening();
+            }
+        }
+    }
+}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9d2ab1f..2b3566f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -729,10 +729,7 @@
 
     private BLASTBufferQueue mBlastBufferQueue;
 
-    private boolean mUpdateHdrSdrRatioInfo = false;
-    private float mDesiredHdrSdrRatio = 1f;
-    private float mRenderHdrSdrRatio = 1f;
-    private Consumer<Display> mHdrSdrRatioChangedListener = null;
+    private final HdrRenderState mHdrRenderState = new HdrRenderState(this);
 
     /**
      * Child container layer of {@code mSurface} with the same bounds as its parent, and cropped to
@@ -1778,7 +1775,7 @@
                 mAttachInfo.mThreadedRenderer = renderer;
                 renderer.setSurfaceControl(mSurfaceControl, mBlastBufferQueue);
                 updateColorModeIfNeeded(attrs.getColorMode(), attrs.getDesiredHdrHeadroom());
-                updateRenderHdrSdrRatio();
+                mHdrRenderState.forceUpdateHdrSdrRatio();
                 updateForceDarkMode();
                 mAttachInfo.mHardwareAccelerated = true;
                 mAttachInfo.mHardwareAccelerationRequested = true;
@@ -2121,9 +2118,7 @@
     private void updateInternalDisplay(int displayId, Resources resources) {
         final Display preferredDisplay =
                 ResourcesManager.getInstance().getAdjustedDisplay(displayId, resources);
-        if (mHdrSdrRatioChangedListener != null && mDisplay != null) {
-            mDisplay.unregisterHdrSdrRatioChangedListener(mHdrSdrRatioChangedListener);
-        }
+        mHdrRenderState.stopListening();
         if (preferredDisplay == null) {
             // Fallback to use default display.
             Slog.w(TAG, "Cannot get desired display with Id: " + displayId);
@@ -2132,9 +2127,7 @@
         } else {
             mDisplay = preferredDisplay;
         }
-        if (mHdrSdrRatioChangedListener != null && mDisplay != null) {
-            mDisplay.registerHdrSdrRatioChangedListener(mExecutor, mHdrSdrRatioChangedListener);
-        }
+        mHdrRenderState.startListening();
         mContext.updateDisplay(mDisplay.getDisplayId());
     }
 
@@ -5100,11 +5093,12 @@
 
                 useAsyncReport = true;
 
-                if (mUpdateHdrSdrRatioInfo) {
-                    mUpdateHdrSdrRatioInfo = false;
+                if (mHdrRenderState.updateForFrame(mAttachInfo.mDrawingTime)) {
+                    final float renderRatio = mHdrRenderState.getRenderHdrSdrRatio();
                     applyTransactionOnDraw(mTransaction.setExtendedRangeBrightness(
-                            getSurfaceControl(), mRenderHdrSdrRatio, mDesiredHdrSdrRatio));
-                    mAttachInfo.mThreadedRenderer.setTargetHdrSdrRatio(mRenderHdrSdrRatio);
+                            getSurfaceControl(), renderRatio,
+                            mHdrRenderState.getDesiredHdrSdrRatio()));
+                    mAttachInfo.mThreadedRenderer.setTargetHdrSdrRatio(renderRatio);
                 }
 
                 if (activeSyncGroup != null) {
@@ -5715,11 +5709,6 @@
         }
     }
 
-    private void updateRenderHdrSdrRatio() {
-        mRenderHdrSdrRatio = Math.min(mDesiredHdrSdrRatio, mDisplay.getHdrSdrRatio());
-        mUpdateHdrSdrRatioInfo = true;
-    }
-
     private void updateColorModeIfNeeded(@ActivityInfo.ColorMode int colorMode,
             float desiredRatio) {
         if (mAttachInfo.mThreadedRenderer == null) {
@@ -5739,22 +5728,8 @@
         if (desiredRatio == 0 || desiredRatio > automaticRatio) {
             desiredRatio = automaticRatio;
         }
-        if (desiredRatio != mDesiredHdrSdrRatio) {
-            mDesiredHdrSdrRatio = desiredRatio;
-            updateRenderHdrSdrRatio();
-            invalidate();
 
-            if (mDesiredHdrSdrRatio < 1.01f) {
-                mDisplay.unregisterHdrSdrRatioChangedListener(mHdrSdrRatioChangedListener);
-                mHdrSdrRatioChangedListener = null;
-            } else {
-                mHdrSdrRatioChangedListener = display -> {
-                    updateRenderHdrSdrRatio();
-                    invalidate();
-                };
-                mDisplay.registerHdrSdrRatioChangedListener(mExecutor, mHdrSdrRatioChangedListener);
-            }
-        }
+        mHdrRenderState.setDesiredHdrSdrRatio(desiredRatio);
     }
 
     @Override
@@ -6365,7 +6340,7 @@
     }
 
     final ViewRootHandler mHandler = new ViewRootHandler();
-    private final Executor mExecutor = (Runnable r) -> {
+    final Executor mExecutor = (Runnable r) -> {
         mHandler.post(r);
     };
 
@@ -8656,7 +8631,7 @@
             if (mAttachInfo.mThreadedRenderer != null) {
                 mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl, mBlastBufferQueue);
             }
-            updateRenderHdrSdrRatio();
+            mHdrRenderState.forceUpdateHdrSdrRatio();
             if (mPreviousTransformHint != transformHint) {
                 mPreviousTransformHint = transformHint;
                 dispatchTransformHintChanged(transformHint);
@@ -9204,9 +9179,7 @@
     private void destroyHardwareRenderer() {
         ThreadedRenderer hardwareRenderer = mAttachInfo.mThreadedRenderer;
 
-        if (mHdrSdrRatioChangedListener != null) {
-            mDisplay.unregisterHdrSdrRatioChangedListener(mHdrSdrRatioChangedListener);
-        }
+        mHdrRenderState.stopListening();
 
         if (hardwareRenderer != null) {
             if (mHardwareRendererObserver != null) {
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index 78a6479..a54a039 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -41,3 +41,10 @@
   description: "Enable r_8, r_16_uint, rg_1616_uint, and rgba_10101010 in the SDK"
   bug: "292545615"
 }
+
+flag {
+  name: "animate_hdr_transitions"
+  namespace: "core_graphics"
+  description: "Automatically animate all changes in HDR headroom"
+  bug: "314810174"
+}