Add new SF backdoor option: hdr/sdr ratio overlay

 - follow how RefreshRateOverlay did in general.
 - 1043 as new opcode to enable hdr/sdr ratio overlay.
 - decouple SevenSegmentDrawer and SurfaceControlHandle helper classes
   from RefreshRateOverlay.
 - add corresponding SF backdoor test for validation.
 - for non-HDR-supported device, we don't reject them to use SF backdoor
   but 1.00 will be always shown on the screen if enabling.

Test: adb shell call service call SurfaceFlinger 1043 1; atest
SurfaceFlinger_test
Bug: 277957056

Change-Id: Ifce2d435f127b53ab9d01aba4e24ffd4fdb010a7
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 89c80bc..326645e 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -168,6 +168,7 @@
         "FrameTracer/FrameTracer.cpp",
         "FrameTracker.cpp",
         "HdrLayerInfoReporter.cpp",
+        "HdrSdrRatioOverlay.cpp",
         "WindowInfosListenerInvoker.cpp",
         "Layer.cpp",
         "LayerFE.cpp",
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index f6ca9e2..32bd890 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -37,11 +37,11 @@
 #include <configstore/Utils.h>
 #include <log/log.h>
 #include <system/window.h>
-#include <ui/GraphicTypes.h>
 
 #include "Display/DisplaySnapshot.h"
 #include "DisplayDevice.h"
 #include "FrontEnd/DisplayInfo.h"
+#include "HdrSdrRatioOverlay.h"
 #include "Layer.h"
 #include "RefreshRateOverlay.h"
 #include "SurfaceFlinger.h"
@@ -261,6 +261,9 @@
     if (mRefreshRateOverlay) {
         mRefreshRateOverlay->setLayerStack(filter.layerStack);
     }
+    if (mHdrSdrRatioOverlay) {
+        mHdrSdrRatioOverlay->setLayerStack(filter.layerStack);
+    }
 }
 
 void DisplayDevice::setFlags(uint32_t flags) {
@@ -274,10 +277,14 @@
     if (mRefreshRateOverlay) {
         mRefreshRateOverlay->setViewport(size);
     }
+    if (mHdrSdrRatioOverlay) {
+        mHdrSdrRatioOverlay->setViewport(size);
+    }
 }
 
 void DisplayDevice::setProjection(ui::Rotation orientation, Rect layerStackSpaceRect,
                                   Rect orientedDisplaySpaceRect) {
+    mIsOrientationChanged = mOrientation != orientation;
     mOrientation = orientation;
 
     // We need to take care of display rotation for globalTransform for case if the panel is not
@@ -411,6 +418,26 @@
                            capabilities.getDesiredMinLuminance());
 }
 
+void DisplayDevice::enableHdrSdrRatioOverlay(bool enable) {
+    if (!enable) {
+        mHdrSdrRatioOverlay.reset();
+        return;
+    }
+
+    mHdrSdrRatioOverlay = std::make_unique<HdrSdrRatioOverlay>();
+    mHdrSdrRatioOverlay->setLayerStack(getLayerStack());
+    mHdrSdrRatioOverlay->setViewport(getSize());
+    updateHdrSdrRatioOverlayRatio(mHdrSdrRatio);
+}
+
+void DisplayDevice::updateHdrSdrRatioOverlayRatio(float currentHdrSdrRatio) {
+    ATRACE_CALL();
+    mHdrSdrRatio = currentHdrSdrRatio;
+    if (mHdrSdrRatioOverlay) {
+        mHdrSdrRatioOverlay->changeHdrSdrRatio(currentHdrSdrRatio);
+    }
+}
+
 void DisplayDevice::enableRefreshRateOverlay(bool enable, bool setByHwc, bool showSpinner,
                                              bool showRenderRate, bool showInMiddle) {
     if (!enable) {
@@ -463,10 +490,23 @@
     return false;
 }
 
-void DisplayDevice::animateRefreshRateOverlay() {
+void DisplayDevice::animateOverlay() {
     if (mRefreshRateOverlay) {
         mRefreshRateOverlay->animate();
     }
+    if (mHdrSdrRatioOverlay) {
+        // hdr sdr ratio is designed to be on the top right of the screen,
+        // therefore, we need to re-calculate the display's width and height
+        if (mIsOrientationChanged) {
+            auto width = getWidth();
+            auto height = getHeight();
+            if (mOrientation == ui::ROTATION_90 || mOrientation == ui::ROTATION_270) {
+                std::swap(width, height);
+            }
+            mHdrSdrRatioOverlay->setViewport({width, height});
+        }
+        mHdrSdrRatioOverlay->animate();
+    }
 }
 
 auto DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info, bool force)
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index dc5f8a8..e92125a 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -55,6 +55,7 @@
 
 class Fence;
 class HWComposer;
+class HdrSdrRatioOverlay;
 class IGraphicBufferProducer;
 class Layer;
 class RefreshRateOverlay;
@@ -235,13 +236,19 @@
         return mRefreshRateSelector;
     }
 
+    void animateOverlay();
+
     // Enables an overlay to be displayed with the current refresh rate
     void enableRefreshRateOverlay(bool enable, bool setByHwc, bool showSpinner, bool showRenderRate,
                                   bool showInMiddle) REQUIRES(kMainThreadContext);
     void updateRefreshRateOverlayRate(Fps displayFps, Fps renderFps, bool setByHwc = false);
     bool isRefreshRateOverlayEnabled() const { return mRefreshRateOverlay != nullptr; }
     bool onKernelTimerChanged(std::optional<DisplayModeId>, bool timerExpired);
-    void animateRefreshRateOverlay();
+
+    // Enables an overlay to be display with the hdr/sdr ratio
+    void enableHdrSdrRatioOverlay(bool enable) REQUIRES(kMainThreadContext);
+    void updateHdrSdrRatioOverlayRatio(float currentHdrSdrRatio);
+    bool isHdrSdrRatioOverlayEnabled() const { return mHdrSdrRatioOverlay != nullptr; }
 
     nsecs_t getVsyncPeriodFromHWC() const;
 
@@ -271,6 +278,7 @@
 
     const ui::Rotation mPhysicalOrientation;
     ui::Rotation mOrientation = ui::ROTATION_0;
+    bool mIsOrientationChanged = false;
 
     // Allow nullopt as initial power mode.
     using TracedPowerMode = TracedOrdinal<hardware::graphics::composer::hal::PowerMode>;
@@ -297,6 +305,9 @@
 
     std::shared_ptr<scheduler::RefreshRateSelector> mRefreshRateSelector;
     std::unique_ptr<RefreshRateOverlay> mRefreshRateOverlay;
+    std::unique_ptr<HdrSdrRatioOverlay> mHdrSdrRatioOverlay;
+    // This parameter is only used for hdr/sdr ratio overlay
+    float mHdrSdrRatio = 1.0f;
 
     mutable std::mutex mActiveModeLock;
     ActiveModeInfo mDesiredActiveMode GUARDED_BY(mActiveModeLock);
diff --git a/services/surfaceflinger/HdrSdrRatioOverlay.cpp b/services/surfaceflinger/HdrSdrRatioOverlay.cpp
new file mode 100644
index 0000000..2c0f518
--- /dev/null
+++ b/services/surfaceflinger/HdrSdrRatioOverlay.cpp
@@ -0,0 +1,178 @@
+/**
+ * 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.
+ */
+// #define LOG_NDEBUG 0
+#include <algorithm>
+
+#include "HdrSdrRatioOverlay.h"
+
+#include <SkSurface.h>
+
+#undef LOG_TAG
+#define LOG_TAG "HdrSdrRatioOverlay"
+
+namespace android {
+
+void HdrSdrRatioOverlay::drawNumber(float number, int left, SkColor color, SkCanvas& canvas) {
+    if (!isfinite(number) || number >= 10.f) return;
+    // We assume that the number range is [1.f, 10.f)
+    // and the decimal places are 2.
+    int value = static_cast<int>(number * 100);
+    SegmentDrawer::drawDigit(value / 100, left, color, canvas);
+
+    left += kDigitWidth + kDigitSpace;
+    SegmentDrawer::drawSegment(SegmentDrawer::Segment::DecimalPoint, left, color, canvas);
+    left += kDigitWidth + kDigitSpace;
+
+    SegmentDrawer::drawDigit((value / 10) % 10, left, color, canvas);
+    left += kDigitWidth + kDigitSpace;
+    SegmentDrawer::drawDigit(value % 10, left, color, canvas);
+}
+
+sp<GraphicBuffer> HdrSdrRatioOverlay::draw(float currentHdrSdrRatio, SkColor color,
+                                           ui::Transform::RotationFlags rotation) {
+    SkMatrix canvasTransform = SkMatrix();
+    const auto [bufferWidth, bufferHeight] = [&]() -> std::pair<int, int> {
+        switch (rotation) {
+            case ui::Transform::ROT_90:
+                canvasTransform.setTranslate(kBufferHeight, 0);
+                canvasTransform.preRotate(90.f);
+                return {kBufferHeight, kBufferWidth};
+            case ui::Transform::ROT_270:
+                canvasTransform.setRotate(270.f, kBufferWidth / 2.f, kBufferWidth / 2.f);
+                return {kBufferHeight, kBufferWidth};
+            default:
+                return {kBufferWidth, kBufferHeight};
+        }
+    }();
+
+    const auto kUsageFlags = static_cast<uint64_t>(
+            GRALLOC_USAGE_SW_WRITE_RARELY | GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_TEXTURE);
+    sp<GraphicBuffer> buffer =
+            sp<GraphicBuffer>::make(static_cast<uint32_t>(bufferWidth),
+                                    static_cast<uint32_t>(bufferHeight), HAL_PIXEL_FORMAT_RGBA_8888,
+                                    1u, kUsageFlags, "HdrSdrRatioOverlay");
+
+    const status_t bufferStatus = buffer->initCheck();
+    LOG_ALWAYS_FATAL_IF(bufferStatus != OK, "HdrSdrRatioOverlay: Buffer failed to allocate: %d",
+                        bufferStatus);
+
+    sk_sp<SkSurface> surface =
+            SkSurfaces::Raster(SkImageInfo::MakeN32Premul(bufferWidth, bufferHeight));
+    SkCanvas* canvas = surface->getCanvas();
+    canvas->setMatrix(canvasTransform);
+
+    drawNumber(currentHdrSdrRatio, 0, color, *canvas);
+
+    void* pixels = nullptr;
+    buffer->lock(GRALLOC_USAGE_SW_WRITE_RARELY, reinterpret_cast<void**>(&pixels));
+
+    const SkImageInfo& imageInfo = surface->imageInfo();
+    const size_t dstRowBytes = buffer->getStride() * static_cast<size_t>(imageInfo.bytesPerPixel());
+
+    canvas->readPixels(imageInfo, pixels, dstRowBytes, 0, 0);
+    buffer->unlock();
+    return buffer;
+}
+
+HdrSdrRatioOverlay::HdrSdrRatioOverlay()
+      : mSurfaceControl(
+                SurfaceControlHolder::createSurfaceControlHolder(String8("HdrSdrRatioOverlay"))) {
+    if (!mSurfaceControl) {
+        ALOGE("%s: Failed to create buffer state layer", __func__);
+        return;
+    }
+    SurfaceComposerClient::Transaction()
+            .setLayer(mSurfaceControl->get(), INT32_MAX - 2)
+            .setTrustedOverlay(mSurfaceControl->get(), true)
+            .apply();
+}
+
+void HdrSdrRatioOverlay::changeHdrSdrRatio(float currentHdrSdrRatio) {
+    mCurrentHdrSdrRatio = currentHdrSdrRatio;
+    animate();
+}
+
+void HdrSdrRatioOverlay::setLayerStack(ui::LayerStack stack) {
+    SurfaceComposerClient::Transaction().setLayerStack(mSurfaceControl->get(), stack).apply();
+}
+
+void HdrSdrRatioOverlay::setViewport(ui::Size viewport) {
+    constexpr int32_t kMaxWidth = 1000;
+    const auto width = std::min({kMaxWidth, viewport.width, viewport.height});
+    const auto height = 2 * width;
+    Rect frame((5 * width) >> 4, height >> 5);
+    // set the ratio frame to the top right of the screen
+    frame.offsetBy(viewport.width - frame.width(), height >> 4);
+
+    SurfaceComposerClient::Transaction()
+            .setMatrix(mSurfaceControl->get(), frame.getWidth() / static_cast<float>(kBufferWidth),
+                       0, 0, frame.getHeight() / static_cast<float>(kBufferHeight))
+            .setPosition(mSurfaceControl->get(), frame.left, frame.top)
+            .apply();
+}
+
+auto HdrSdrRatioOverlay::getOrCreateBuffers(float currentHdrSdrRatio) -> const sp<GraphicBuffer> {
+    static const sp<GraphicBuffer> kNoBuffer;
+    if (!mSurfaceControl) return kNoBuffer;
+
+    const auto transformHint =
+            static_cast<ui::Transform::RotationFlags>(mSurfaceControl->get()->getTransformHint());
+
+    // Tell SurfaceFlinger about the pre-rotation on the buffer.
+    const auto transform = [&] {
+        switch (transformHint) {
+            case ui::Transform::ROT_90:
+                return ui::Transform::ROT_270;
+            case ui::Transform::ROT_270:
+                return ui::Transform::ROT_90;
+            default:
+                return ui::Transform::ROT_0;
+        }
+    }();
+
+    SurfaceComposerClient::Transaction().setTransform(mSurfaceControl->get(), transform).apply();
+
+    constexpr SkColor kMinRatioColor = SK_ColorBLUE;
+    constexpr SkColor kMaxRatioColor = SK_ColorGREEN;
+    constexpr float kAlpha = 0.8f;
+
+    // 9.f is picked here as ratio range, given that we assume that
+    // hdr/sdr ratio is [1.f, 10.f)
+    const float scale = currentHdrSdrRatio / 9.f;
+
+    SkColor4f colorBase = SkColor4f::FromColor(kMaxRatioColor) * scale;
+    const SkColor4f minRatioColor = SkColor4f::FromColor(kMinRatioColor) * (1 - scale);
+
+    colorBase.fR = colorBase.fR + minRatioColor.fR;
+    colorBase.fG = colorBase.fG + minRatioColor.fG;
+    colorBase.fB = colorBase.fB + minRatioColor.fB;
+    colorBase.fA = kAlpha;
+
+    const SkColor color = colorBase.toSkColor();
+
+    auto buffer = draw(currentHdrSdrRatio, color, transformHint);
+    return buffer;
+}
+
+void HdrSdrRatioOverlay::animate() {
+    if (!std::isfinite(mCurrentHdrSdrRatio) || mCurrentHdrSdrRatio < 1.0f) return;
+
+    SurfaceComposerClient::Transaction()
+            .setBuffer(mSurfaceControl->get(), getOrCreateBuffers(mCurrentHdrSdrRatio))
+            .apply();
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/HdrSdrRatioOverlay.h b/services/surfaceflinger/HdrSdrRatioOverlay.h
new file mode 100644
index 0000000..8a2586e
--- /dev/null
+++ b/services/surfaceflinger/HdrSdrRatioOverlay.h
@@ -0,0 +1,45 @@
+/**
+ * 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.
+ */
+
+#pragma once
+
+#include "Utils/OverlayUtils.h"
+
+#include <ui/Size.h>
+#include <utils/StrongPointer.h>
+
+class SkCanvas;
+
+namespace android {
+class HdrSdrRatioOverlay {
+public:
+    HdrSdrRatioOverlay();
+    void setLayerStack(ui::LayerStack);
+    void setViewport(ui::Size);
+    void animate();
+    void changeHdrSdrRatio(float currentRatio);
+
+private:
+    float mCurrentHdrSdrRatio = 1.f;
+
+    static sp<GraphicBuffer> draw(float currentHdrSdrRatio, SkColor, ui::Transform::RotationFlags);
+    static void drawNumber(float number, int left, SkColor, SkCanvas&);
+
+    const sp<GraphicBuffer> getOrCreateBuffers(float currentHdrSdrRatio);
+
+    const std::unique_ptr<SurfaceControlHolder> mSurfaceControl;
+};
+} // namespace android
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index 607bec2..577211f 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -16,104 +16,20 @@
 
 #include <algorithm>
 
-#include "BackgroundExecutor.h"
 #include "Client.h"
 #include "Layer.h"
 #include "RefreshRateOverlay.h"
 
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-#include <SkCanvas.h>
-#include <SkPaint.h>
-#pragma clang diagnostic pop
-#include <SkBlendMode.h>
-#include <SkRect.h>
 #include <SkSurface.h>
-#include <gui/SurfaceControl.h>
 
 #undef LOG_TAG
 #define LOG_TAG "RefreshRateOverlay"
 
 namespace android {
-namespace {
 
-constexpr int kDigitWidth = 64;
-constexpr int kDigitHeight = 100;
-constexpr int kDigitSpace = 16;
-
-constexpr int kMaxDigits = /*displayFps*/ 3 + /*renderFps*/ 3 + /*spinner*/ 1;
-constexpr int kBufferWidth = kMaxDigits * kDigitWidth + (kMaxDigits - 1) * kDigitSpace;
-constexpr int kBufferHeight = kDigitHeight;
-
-} // namespace
-
-SurfaceControlHolder::~SurfaceControlHolder() {
-    // Hand the sp<SurfaceControl> to the helper thread to release the last
-    // reference. This makes sure that the SurfaceControl is destructed without
-    // SurfaceFlinger::mStateLock held.
-    BackgroundExecutor::getInstance().sendCallbacks(
-            {[sc = std::move(mSurfaceControl)]() mutable { sc.clear(); }});
-}
-
-void RefreshRateOverlay::SevenSegmentDrawer::drawSegment(Segment segment, int left, SkColor color,
-                                                         SkCanvas& canvas) {
-    const SkRect rect = [&]() {
-        switch (segment) {
-            case Segment::Upper:
-                return SkRect::MakeLTRB(left, 0, left + kDigitWidth, kDigitSpace);
-            case Segment::UpperLeft:
-                return SkRect::MakeLTRB(left, 0, left + kDigitSpace, kDigitHeight / 2);
-            case Segment::UpperRight:
-                return SkRect::MakeLTRB(left + kDigitWidth - kDigitSpace, 0, left + kDigitWidth,
-                                        kDigitHeight / 2);
-            case Segment::Middle:
-                return SkRect::MakeLTRB(left, kDigitHeight / 2 - kDigitSpace / 2,
-                                        left + kDigitWidth, kDigitHeight / 2 + kDigitSpace / 2);
-            case Segment::LowerLeft:
-                return SkRect::MakeLTRB(left, kDigitHeight / 2, left + kDigitSpace, kDigitHeight);
-            case Segment::LowerRight:
-                return SkRect::MakeLTRB(left + kDigitWidth - kDigitSpace, kDigitHeight / 2,
-                                        left + kDigitWidth, kDigitHeight);
-            case Segment::Bottom:
-                return SkRect::MakeLTRB(left, kDigitHeight - kDigitSpace, left + kDigitWidth,
-                                        kDigitHeight);
-        }
-    }();
-
-    SkPaint paint;
-    paint.setColor(color);
-    paint.setBlendMode(SkBlendMode::kSrc);
-    canvas.drawRect(rect, paint);
-}
-
-void RefreshRateOverlay::SevenSegmentDrawer::drawDigit(int digit, int left, SkColor color,
-                                                       SkCanvas& canvas) {
-    if (digit < 0 || digit > 9) return;
-
-    if (digit == 0 || digit == 2 || digit == 3 || digit == 5 || digit == 6 || digit == 7 ||
-        digit == 8 || digit == 9)
-        drawSegment(Segment::Upper, left, color, canvas);
-    if (digit == 0 || digit == 4 || digit == 5 || digit == 6 || digit == 8 || digit == 9)
-        drawSegment(Segment::UpperLeft, left, color, canvas);
-    if (digit == 0 || digit == 1 || digit == 2 || digit == 3 || digit == 4 || digit == 7 ||
-        digit == 8 || digit == 9)
-        drawSegment(Segment::UpperRight, left, color, canvas);
-    if (digit == 2 || digit == 3 || digit == 4 || digit == 5 || digit == 6 || digit == 8 ||
-        digit == 9)
-        drawSegment(Segment::Middle, left, color, canvas);
-    if (digit == 0 || digit == 2 || digit == 6 || digit == 8)
-        drawSegment(Segment::LowerLeft, left, color, canvas);
-    if (digit == 0 || digit == 1 || digit == 3 || digit == 4 || digit == 5 || digit == 6 ||
-        digit == 7 || digit == 8 || digit == 9)
-        drawSegment(Segment::LowerRight, left, color, canvas);
-    if (digit == 0 || digit == 2 || digit == 3 || digit == 5 || digit == 6 || digit == 8 ||
-        digit == 9)
-        drawSegment(Segment::Bottom, left, color, canvas);
-}
-
-auto RefreshRateOverlay::SevenSegmentDrawer::draw(int displayFps, int renderFps, SkColor color,
-                                                  ui::Transform::RotationFlags rotation,
-                                                  ftl::Flags<Features> features) -> Buffers {
+auto RefreshRateOverlay::draw(int displayFps, int renderFps, SkColor color,
+                              ui::Transform::RotationFlags rotation, ftl::Flags<Features> features)
+        -> Buffers {
     const size_t loopCount = features.test(Features::Spinner) ? 6 : 1;
 
     Buffers buffers;
@@ -159,22 +75,27 @@
         if (features.test(Features::Spinner)) {
             switch (i) {
                 case 0:
-                    drawSegment(Segment::Upper, left, color, *canvas);
+                    SegmentDrawer::drawSegment(SegmentDrawer::Segment::Upper, left, color, *canvas);
                     break;
                 case 1:
-                    drawSegment(Segment::UpperRight, left, color, *canvas);
+                    SegmentDrawer::drawSegment(SegmentDrawer::Segment::UpperRight, left, color,
+                                               *canvas);
                     break;
                 case 2:
-                    drawSegment(Segment::LowerRight, left, color, *canvas);
+                    SegmentDrawer::drawSegment(SegmentDrawer::Segment::LowerRight, left, color,
+                                               *canvas);
                     break;
                 case 3:
-                    drawSegment(Segment::Bottom, left, color, *canvas);
+                    SegmentDrawer::drawSegment(SegmentDrawer::Segment::Bottom, left, color,
+                                               *canvas);
                     break;
                 case 4:
-                    drawSegment(Segment::LowerLeft, left, color, *canvas);
+                    SegmentDrawer::drawSegment(SegmentDrawer::Segment::LowerLeft, left, color,
+                                               *canvas);
                     break;
                 case 5:
-                    drawSegment(Segment::UpperLeft, left, color, *canvas);
+                    SegmentDrawer::drawSegment(SegmentDrawer::Segment::UpperLeft, left, color,
+                                               *canvas);
                     break;
             }
         }
@@ -200,34 +121,27 @@
     return buffers;
 }
 
-void RefreshRateOverlay::SevenSegmentDrawer::drawNumber(int number, int left, SkColor color,
-                                                        SkCanvas& canvas) {
+void RefreshRateOverlay::drawNumber(int number, int left, SkColor color, SkCanvas& canvas) {
     if (number < 0 || number >= 1000) return;
 
     if (number >= 100) {
-        drawDigit(number / 100, left, color, canvas);
+        SegmentDrawer::drawDigit(number / 100, left, color, canvas);
     }
     left += kDigitWidth + kDigitSpace;
 
     if (number >= 10) {
-        drawDigit((number / 10) % 10, left, color, canvas);
+        SegmentDrawer::drawDigit((number / 10) % 10, left, color, canvas);
     }
     left += kDigitWidth + kDigitSpace;
 
-    drawDigit(number % 10, left, color, canvas);
-}
-
-std::unique_ptr<SurfaceControlHolder> createSurfaceControlHolder() {
-    sp<SurfaceControl> surfaceControl =
-            SurfaceComposerClient::getDefault()
-                    ->createSurface(String8("RefreshRateOverlay"), kBufferWidth, kBufferHeight,
-                                    PIXEL_FORMAT_RGBA_8888,
-                                    ISurfaceComposerClient::eFXSurfaceBufferState);
-    return std::make_unique<SurfaceControlHolder>(std::move(surfaceControl));
+    SegmentDrawer::drawDigit(number % 10, left, color, canvas);
 }
 
 RefreshRateOverlay::RefreshRateOverlay(FpsRange fpsRange, ftl::Flags<Features> features)
-      : mFpsRange(fpsRange), mFeatures(features), mSurfaceControl(createSurfaceControlHolder()) {
+      : mFpsRange(fpsRange),
+        mFeatures(features),
+        mSurfaceControl(
+                SurfaceControlHolder::createSurfaceControlHolder(String8("RefreshRateOverlay"))) {
     if (!mSurfaceControl) {
         ALOGE("%s: Failed to create buffer state layer", __func__);
         return;
@@ -296,8 +210,7 @@
 
         const SkColor color = colorBase.toSkColor();
 
-        auto buffers = SevenSegmentDrawer::draw(displayIntFps, renderIntFps, color, transformHint,
-                                                mFeatures);
+        auto buffers = draw(displayIntFps, renderIntFps, color, transformHint, mFeatures);
         it = mBufferCache
                      .try_emplace({displayIntFps, renderIntFps, transformHint}, std::move(buffers))
                      .first;
diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h
index 0b89b8e..65c61cb 100644
--- a/services/surfaceflinger/RefreshRateOverlay.h
+++ b/services/surfaceflinger/RefreshRateOverlay.h
@@ -16,12 +16,12 @@
 
 #pragma once
 
-#include <SkColor.h>
+#include "Utils/OverlayUtils.h"
+
 #include <vector>
 
 #include <ftl/flags.h>
 #include <ftl/small_map.h>
-#include <gui/SurfaceComposerClient.h>
 #include <ui/LayerStack.h>
 #include <ui/Size.h>
 #include <ui/Transform.h>
@@ -34,22 +34,8 @@
 namespace android {
 
 class GraphicBuffer;
-class SurfaceControl;
 class SurfaceFlinger;
 
-// Helper class to delete the SurfaceControl on a helper thread as
-// SurfaceControl assumes its destruction happens without SurfaceFlinger::mStateLock held.
-class SurfaceControlHolder {
-public:
-    explicit SurfaceControlHolder(sp<SurfaceControl> sc) : mSurfaceControl(std::move(sc)){};
-    ~SurfaceControlHolder();
-
-    const sp<SurfaceControl>& get() const { return mSurfaceControl; }
-
-private:
-    sp<SurfaceControl> mSurfaceControl;
-};
-
 class RefreshRateOverlay {
 public:
     enum class Features {
@@ -70,18 +56,9 @@
 private:
     using Buffers = std::vector<sp<GraphicBuffer>>;
 
-    class SevenSegmentDrawer {
-    public:
-        static Buffers draw(int displayFps, int renderFps, SkColor, ui::Transform::RotationFlags,
-                            ftl::Flags<Features>);
-
-    private:
-        enum class Segment { Upper, UpperLeft, UpperRight, Middle, LowerLeft, LowerRight, Bottom };
-
-        static void drawSegment(Segment, int left, SkColor, SkCanvas&);
-        static void drawDigit(int digit, int left, SkColor, SkCanvas&);
-        static void drawNumber(int number, int left, SkColor, SkCanvas&);
-    };
+    static Buffers draw(int displayFps, int renderFps, SkColor, ui::Transform::RotationFlags,
+                        ftl::Flags<Features>);
+    static void drawNumber(int number, int left, SkColor, SkCanvas&);
 
     const Buffers& getOrCreateBuffers(Fps, Fps);
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index c46da11..8646c0c 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1963,6 +1963,11 @@
 
                        FTL_FAKE_GUARD(kMainThreadContext,
                                       display->stageBrightness(brightness.displayBrightness));
+                       float currentHdrSdrRatio =
+                               compositionDisplay->editState().displayBrightnessNits /
+                               compositionDisplay->editState().sdrWhitePointNits;
+                       FTL_FAKE_GUARD(kMainThreadContext,
+                                      display->updateHdrSdrRatioOverlayRatio(currentHdrSdrRatio));
 
                        if (brightness.sdrWhitePointNits / brightness.displayBrightnessNits !=
                            currentDimmingRatio) {
@@ -2454,10 +2459,10 @@
         mPowerAdvisor->updateTargetWorkDuration(idealVsyncPeriod);
     }
 
-    if (mRefreshRateOverlaySpinner) {
+    if (mRefreshRateOverlaySpinner || mHdrSdrRatioOverlay) {
         Mutex::Autolock lock(mStateLock);
         if (const auto display = getDefaultDisplayDeviceLocked()) {
-            display->animateRefreshRateOverlay();
+            display->animateOverlay();
         }
     }
 
@@ -6462,9 +6467,9 @@
         code == IBinder::SYSPROPS_TRANSACTION) {
         return OK;
     }
-    // Numbers from 1000 to 1042 are currently used for backdoors. The code
+    // Numbers from 1000 to 1043 are currently used for backdoors. The code
     // in onTransact verifies that the user is root, and has access to use SF.
-    if (code >= 1000 && code <= 1042) {
+    if (code >= 1000 && code <= 1043) {
         ALOGV("Accessing SurfaceFlinger through backdoor code: %u", code);
         return OK;
     }
@@ -6925,6 +6930,24 @@
                 reply->writeInt32(NO_ERROR);
                 return NO_ERROR;
             }
+            // hdr sdr ratio overlay
+            case 1043: {
+                auto future = mScheduler->schedule(
+                        [&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) {
+                            n = data.readInt32();
+                            mHdrSdrRatioOverlay = n != 0;
+                            switch (n) {
+                                case 0:
+                                case 1:
+                                    enableHdrSdrRatioOverlay(mHdrSdrRatioOverlay);
+                                    break;
+                                default:
+                                    reply->writeBool(isHdrSdrRatioOverlayEnabled());
+                            }
+                        });
+                future.wait();
+                return NO_ERROR;
+            }
         }
     }
     return err;
@@ -8023,6 +8046,16 @@
     }
 }
 
+void SurfaceFlinger::enableHdrSdrRatioOverlay(bool enable) {
+    for (const auto& [id, display] : mPhysicalDisplays) {
+        if (display.snapshot().connectionType() == ui::DisplayConnectionType::Internal) {
+            if (const auto device = getDisplayDeviceLocked(id)) {
+                device->enableHdrSdrRatioOverlay(enable);
+            }
+        }
+    }
+}
+
 int SurfaceFlinger::getGpuContextPriority() {
     return getRenderEngine().getContextPriority();
 }
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index d97a747..49aff14 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -672,6 +672,8 @@
     bool mRefreshRateOverlayRenderRate = false;
     // Show render rate overlay offseted to the middle of the screen (e.g. for circular displays)
     bool mRefreshRateOverlayShowInMiddle = false;
+    // Show hdr sdr ratio overlay
+    bool mHdrSdrRatioOverlay = false;
 
     void setDesiredActiveMode(display::DisplayModeRequest&&, bool force = false)
             REQUIRES(mStateLock);
@@ -1288,7 +1290,6 @@
     ui::Dataspace mDefaultCompositionDataspace;
     ui::Dataspace mWideColorGamutCompositionDataspace;
     ui::Dataspace mColorSpaceAgnosticDataspace;
-    float mDimmingRatio = -1.f;
 
     std::unique_ptr<renderengine::RenderEngine> mRenderEngine;
     std::atomic<int> mNumTrustedPresentationListeners = 0;
@@ -1336,6 +1337,8 @@
 
     void enableRefreshRateOverlay(bool enable) REQUIRES(mStateLock, kMainThreadContext);
 
+    void enableHdrSdrRatioOverlay(bool enable) REQUIRES(mStateLock, kMainThreadContext);
+
     // Flag used to set override desired display mode from backdoor
     bool mDebugDisplayModeSetByBackdoor = false;
 
@@ -1381,6 +1384,10 @@
         return hasDisplay(
                 [](const auto& display) { return display.isRefreshRateOverlayEnabled(); });
     }
+    bool isHdrSdrRatioOverlayEnabled() const REQUIRES(mStateLock) {
+        return hasDisplay(
+                [](const auto& display) { return display.isHdrSdrRatioOverlayEnabled(); });
+    }
     std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()> getLayerSnapshotsForScreenshots(
             std::optional<ui::LayerStack> layerStack, uint32_t uid,
             std::function<bool(const frontend::LayerSnapshot&, bool& outStopTraversal)>
diff --git a/services/surfaceflinger/Utils/OverlayUtils.h b/services/surfaceflinger/Utils/OverlayUtils.h
new file mode 100644
index 0000000..0ef0be1
--- /dev/null
+++ b/services/surfaceflinger/Utils/OverlayUtils.h
@@ -0,0 +1,146 @@
+/**
+ * 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.
+ */
+
+// #define LOG_NDEBUG 0
+#pragma once
+
+#include "BackgroundExecutor.h"
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+#include <SkCanvas.h>
+#include <SkPaint.h>
+#pragma clang diagnostic pop
+
+#include <gui/SurfaceComposerClient.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+
+inline constexpr int kDigitWidth = 64;
+inline constexpr int kDigitHeight = 100;
+inline constexpr int kDigitSpace = 16;
+
+// HdrSdrRatioOverlay re-uses this value though it doesn't really need such amount buffer.
+// for output good-looking and code conciseness.
+inline constexpr int kMaxDigits = /*displayFps*/ 3 + /*renderFps*/ 3 + /*spinner*/ 1;
+inline constexpr int kBufferWidth = kMaxDigits * kDigitWidth + (kMaxDigits - 1) * kDigitSpace;
+inline constexpr int kBufferHeight = kDigitHeight;
+
+class SurfaceControl;
+
+// Helper class to delete the SurfaceControl on a helper thread as
+// SurfaceControl assumes its destruction happens without SurfaceFlinger::mStateLock held.
+class SurfaceControlHolder {
+public:
+    explicit SurfaceControlHolder(sp<SurfaceControl> sc) : mSurfaceControl(std::move(sc)){};
+
+    ~SurfaceControlHolder() {
+        // Hand the sp<SurfaceControl> to the helper thread to release the last
+        // reference. This makes sure that the SurfaceControl is destructed without
+        // SurfaceFlinger::mStateLock held.
+        BackgroundExecutor::getInstance().sendCallbacks(
+                {[sc = std::move(mSurfaceControl)]() mutable { sc.clear(); }});
+    }
+
+    static std::unique_ptr<SurfaceControlHolder> createSurfaceControlHolder(const String8& name) {
+        sp<SurfaceControl> surfaceControl =
+                SurfaceComposerClient::getDefault()
+                        ->createSurface(name, kBufferWidth, kBufferHeight, PIXEL_FORMAT_RGBA_8888,
+                                        ISurfaceComposerClient::eFXSurfaceBufferState);
+        return std::make_unique<SurfaceControlHolder>(std::move(surfaceControl));
+    }
+
+    const sp<SurfaceControl>& get() const { return mSurfaceControl; }
+
+private:
+    sp<SurfaceControl> mSurfaceControl;
+};
+
+// Helper class to draw digit and decimal point.
+class SegmentDrawer {
+public:
+    enum class Segment {
+        Upper,
+        UpperLeft,
+        UpperRight,
+        Middle,
+        LowerLeft,
+        LowerRight,
+        Bottom,
+        DecimalPoint
+    };
+    static void drawSegment(Segment segment, int left, SkColor color, SkCanvas& canvas) {
+        const SkRect rect = [&]() {
+            switch (segment) {
+                case Segment::Upper:
+                    return SkRect::MakeLTRB(left, 0, left + kDigitWidth, kDigitSpace);
+                case Segment::UpperLeft:
+                    return SkRect::MakeLTRB(left, 0, left + kDigitSpace, kDigitHeight / 2.);
+                case Segment::UpperRight:
+                    return SkRect::MakeLTRB(left + kDigitWidth - kDigitSpace, 0, left + kDigitWidth,
+                                            kDigitHeight / 2.);
+                case Segment::Middle:
+                    return SkRect::MakeLTRB(left, kDigitHeight / 2. - kDigitSpace / 2.,
+                                            left + kDigitWidth,
+                                            kDigitHeight / 2. + kDigitSpace / 2.);
+                case Segment::LowerLeft:
+                    return SkRect::MakeLTRB(left, kDigitHeight / 2., left + kDigitSpace,
+                                            kDigitHeight);
+                case Segment::LowerRight:
+                    return SkRect::MakeLTRB(left + kDigitWidth - kDigitSpace, kDigitHeight / 2.,
+                                            left + kDigitWidth, kDigitHeight);
+                case Segment::Bottom:
+                    return SkRect::MakeLTRB(left, kDigitHeight - kDigitSpace, left + kDigitWidth,
+                                            kDigitHeight);
+                case Segment::DecimalPoint:
+                    return SkRect::MakeLTRB(left, kDigitHeight - kDigitSpace, left + kDigitSpace,
+                                            kDigitHeight);
+            }
+        }();
+
+        SkPaint paint;
+        paint.setColor(color);
+        paint.setBlendMode(SkBlendMode::kSrc);
+        canvas.drawRect(rect, paint);
+    }
+
+    static void drawDigit(int digit, int left, SkColor color, SkCanvas& canvas) {
+        if (digit < 0 || digit > 9) return;
+
+        if (digit == 0 || digit == 2 || digit == 3 || digit == 5 || digit == 6 || digit == 7 ||
+            digit == 8 || digit == 9)
+            drawSegment(Segment::Upper, left, color, canvas);
+        if (digit == 0 || digit == 4 || digit == 5 || digit == 6 || digit == 8 || digit == 9)
+            drawSegment(Segment::UpperLeft, left, color, canvas);
+        if (digit == 0 || digit == 1 || digit == 2 || digit == 3 || digit == 4 || digit == 7 ||
+            digit == 8 || digit == 9)
+            drawSegment(Segment::UpperRight, left, color, canvas);
+        if (digit == 2 || digit == 3 || digit == 4 || digit == 5 || digit == 6 || digit == 8 ||
+            digit == 9)
+            drawSegment(Segment::Middle, left, color, canvas);
+        if (digit == 0 || digit == 2 || digit == 6 || digit == 8)
+            drawSegment(Segment::LowerLeft, left, color, canvas);
+        if (digit == 0 || digit == 1 || digit == 3 || digit == 4 || digit == 5 || digit == 6 ||
+            digit == 7 || digit == 8 || digit == 9)
+            drawSegment(Segment::LowerRight, left, color, canvas);
+        if (digit == 0 || digit == 2 || digit == 3 || digit == 5 || digit == 6 || digit == 8 ||
+            digit == 9)
+            drawSegment(Segment::Bottom, left, color, canvas);
+    }
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index 62b539a..b5168b0 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -37,8 +37,9 @@
         "DisplayConfigs_test.cpp",
         "DisplayEventReceiver_test.cpp",
         "EffectLayer_test.cpp",
-        "LayerBorder_test.cpp",
+        "HdrSdrRatioOverlay_test.cpp",
         "InvalidHandles_test.cpp",
+        "LayerBorder_test.cpp",
         "LayerCallback_test.cpp",
         "LayerRenderTypeTransaction_test.cpp",
         "LayerState_test.cpp",
diff --git a/services/surfaceflinger/tests/HdrSdrRatioOverlay_test.cpp b/services/surfaceflinger/tests/HdrSdrRatioOverlay_test.cpp
new file mode 100644
index 0000000..77a8f9c
--- /dev/null
+++ b/services/surfaceflinger/tests/HdrSdrRatioOverlay_test.cpp
@@ -0,0 +1,89 @@
+/**
+ * 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.
+ */
+#include <thread>
+
+#include <gtest/gtest.h>
+
+#include <gui/SurfaceComposerClient.h>
+#include <private/gui/ComposerService.h>
+#include <chrono>
+
+using ::std::literals::chrono_literals::operator""s;
+
+static constexpr int kHdrSdrRatioOverlayCode = 1043;
+static constexpr int kHdrSdrRatioOverlayEnable = 1;
+static constexpr int kHdrSdrRatioOverlayDisable = 0;
+static constexpr int kHdrSdrRatioOverlayQuery = 2;
+
+// These values must match the ones we used for developer options in
+// com.android.settings.development.ShowHdrSdrRatioPreferenceController
+static_assert(kHdrSdrRatioOverlayCode == 1043);
+static_assert(kHdrSdrRatioOverlayEnable == 1);
+static_assert(kHdrSdrRatioOverlayDisable == 0);
+static_assert(kHdrSdrRatioOverlayQuery == 2);
+
+namespace android {
+
+namespace {
+void sendCommandToSf(int command, Parcel& reply) {
+    sp<ISurfaceComposer> sf(ComposerService::getComposerService());
+    Parcel request;
+    request.writeInterfaceToken(String16("android.ui.ISurfaceComposer"));
+    request.writeInt32(command);
+    ASSERT_EQ(NO_ERROR,
+              IInterface::asBinder(sf)->transact(kHdrSdrRatioOverlayCode, request, &reply));
+}
+
+bool isOverlayEnabled() {
+    Parcel reply;
+    sendCommandToSf(kHdrSdrRatioOverlayQuery, reply);
+    return reply.readBool();
+}
+
+void waitForOverlay(bool enabled) {
+    static constexpr auto kTimeout = std::chrono::nanoseconds(1s);
+    static constexpr auto kIterations = 10;
+    for (int i = 0; i < kIterations; i++) {
+        if (enabled == isOverlayEnabled()) {
+            return;
+        }
+        std::this_thread::sleep_for(kTimeout / kIterations);
+    }
+}
+
+void toggleOverlay(bool enabled) {
+    if (enabled == isOverlayEnabled()) {
+        return;
+    }
+
+    Parcel reply;
+    const auto command = enabled ? kHdrSdrRatioOverlayEnable : kHdrSdrRatioOverlayDisable;
+    sendCommandToSf(command, reply);
+    waitForOverlay(enabled);
+    ASSERT_EQ(enabled, isOverlayEnabled());
+}
+
+} // namespace
+
+TEST(HdrSdrRatioOverlayTest, enableAndDisableOverlay) {
+    toggleOverlay(true);
+    toggleOverlay(false);
+
+    toggleOverlay(true);
+    toggleOverlay(false);
+}
+
+} // namespace android