CE: Unit test coverage for Output::updateColorProfile()

Since these tests are complicated, it also introduces
CallOrderStateMachineHelper, which is used to enforce the setup/execute
progression in a way that intentionally highlights what is unique about
the set up for each test case.

Bug: 144116957
Test: atest libcompositionengine_test
Change-Id: I691e1cba27ae63b051151e6d884671ce921d19ff
diff --git a/services/surfaceflinger/CompositionEngine/tests/CallOrderStateMachineHelper.h b/services/surfaceflinger/CompositionEngine/tests/CallOrderStateMachineHelper.h
new file mode 100644
index 0000000..2675dcf
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/tests/CallOrderStateMachineHelper.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2019 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
+
+/**
+ * CallOrderStateMachineHelper is a helper class for setting up a compile-time
+ * checked state machine that a sequence of calls is correct for completely
+ * setting up the state for some other type.
+ *
+ * Two examples where this could be used are with setting up a "Builder" flow
+ * for initializing an instance of some type, and writing tests where the state
+ * machine sets up expectations and preconditions, calls the function under
+ * test, and then evaluations postconditions.
+ *
+ * The purpose of this helper is to offload some of the boilerplate code to
+ * simplify the actual state classes, and is also a place to document how to
+ * go about setting up the state classes.
+ *
+ * To work at compile time, the idea is that each state is a unique C++ type,
+ * and the valid transitions between states are given by member functions on
+ * those types, with those functions returning a simple value type expressing
+ * the new state to use. Illegal state transitions become a compile error because
+ * a named member function does not exist.
+ *
+ * Example usage in a test:
+ *
+ *    A two step (+ terminator step) setup process can defined using:
+ *
+ *        class Step1 : public CallOrderStateMachineHelper<TestFixtureType, Step1> {
+ *           [[nodiscard]] auto firstMockCalledWith(int value1) {
+ *              // Set up an expectation or initial state using the fixture
+ *              EXPECT_CALL(getInstance->firstMock, FirstCall(value1));
+ *              return nextState<Step2>();
+ *           }
+ *        };
+ *
+ *        class Step2 : public CallOrderStateMachineHelper<TestFixtureType, Step2> {
+ *           [[nodiscard]] auto secondMockCalledWith(int value2) {
+ *              // Set up an expectation or initial state using the fixture
+ *              EXPECT_CALL(getInstance()->secondMock, SecondCall(value2));
+ *              return nextState<StepExecute>();
+ *           }
+ *        };
+ *
+ *        class StepExecute : public CallOrderStateMachineHelper<TestFixtureType, Step3> {
+ *           void execute() {
+ *              invokeFunctionUnderTest();
+ *           }
+ *        };
+ *
+ *    Note how the non-terminator steps return by value and use [[nodiscard]] to
+ *    enforce the setup flow. Only the terminator step returns void.
+ *
+ *    This can then be used in the tests with:
+ *
+ *        Step1::make(this).firstMockCalledWith(value1)
+ *                .secondMockCalledWith(value2)
+ *                .execute);
+ *
+ *    If the test fixture defines a `verify()` helper function which returns
+ *    `Step1::make(this)`, this can be simplified to:
+ *
+ *        verify().firstMockCalledWith(value1)
+ *                .secondMockCalledWith(value2)
+ *                .execute();
+ *
+ *    This is equivalent to the following calls made by the text function:
+ *
+ *        EXPECT_CALL(firstMock, FirstCall(value1));
+ *        EXPECT_CALL(secondMock, SecondCall(value2));
+ *        invokeFunctionUnderTest();
+ */
+template <typename InstanceType, typename CurrentStateType>
+class CallOrderStateMachineHelper {
+public:
+    CallOrderStateMachineHelper() = default;
+
+    // Disallow copying
+    CallOrderStateMachineHelper(const CallOrderStateMachineHelper&) = delete;
+    CallOrderStateMachineHelper& operator=(const CallOrderStateMachineHelper&) = delete;
+
+    // Moving is intended use case.
+    CallOrderStateMachineHelper(CallOrderStateMachineHelper&&) = default;
+    CallOrderStateMachineHelper& operator=(CallOrderStateMachineHelper&&) = default;
+
+    // Using a static "Make" function means the CurrentStateType classes do not
+    // need anything other than a default no-argument constructor.
+    static CurrentStateType make(InstanceType* instance) {
+        auto helper = CurrentStateType();
+        helper.mInstance = instance;
+        return helper;
+    }
+
+    // Each non-terminal state function
+    template <typename NextStateType>
+    auto nextState() {
+        // Note: Further operations on the current state become undefined
+        // operations as the instance pointer is moved to the next state type.
+        // But that doesn't stop someone from storing an intermediate state
+        // instance as a local and possibly calling one than one member function
+        // on it. By swapping with nullptr, we at least can try to catch this
+        // this at runtime.
+        InstanceType* instance = nullptr;
+        std::swap(instance, mInstance);
+        return NextStateType::make(instance);
+    }
+
+    InstanceType* getInstance() const { return mInstance; }
+
+private:
+    InstanceType* mInstance;
+};
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 242ccd5..615fb94 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -16,6 +16,7 @@
 
 #include <cmath>
 
+#include <android-base/stringprintf.h>
 #include <compositionengine/LayerFECompositionState.h>
 #include <compositionengine/impl/Output.h>
 #include <compositionengine/impl/OutputCompositionState.h>
@@ -31,6 +32,7 @@
 #include <ui/Rect.h>
 #include <ui/Region.h>
 
+#include "CallOrderStateMachineHelper.h"
 #include "RegionMatcher.h"
 #include "TransformMatcher.h"
 
@@ -39,10 +41,13 @@
 
 using testing::_;
 using testing::ByMove;
+using testing::DoAll;
 using testing::InSequence;
+using testing::Mock;
 using testing::Ref;
 using testing::Return;
 using testing::ReturnRef;
+using testing::SetArgPointee;
 using testing::StrictMock;
 
 constexpr auto TR_IDENT = 0u;
@@ -52,6 +57,9 @@
 const mat4 kNonIdentityHalf = mat4() * 0.5;
 const mat4 kNonIdentityQuarter = mat4() * 0.25;
 
+constexpr OutputColorSetting kVendorSpecifiedOutputColorSetting =
+        static_cast<OutputColorSetting>(0x100);
+
 struct OutputPartialMockBase : public impl::Output {
     // compositionengine::Output overrides
     const OutputCompositionState& getState() const override { return mState; }
@@ -107,6 +115,30 @@
 
 const Rect OutputTest::kDefaultDisplaySize{100, 200};
 
+using ColorProfile = compositionengine::Output::ColorProfile;
+
+void dumpColorProfile(ColorProfile profile, std::string& result, const char* name) {
+    android::base::StringAppendF(&result, "%s (%s[%d] %s[%d] %s[%d] %s[%d]) ", name,
+                                 toString(profile.mode).c_str(), profile.mode,
+                                 toString(profile.dataspace).c_str(), profile.dataspace,
+                                 toString(profile.renderIntent).c_str(), profile.renderIntent,
+                                 toString(profile.colorSpaceAgnosticDataspace).c_str(),
+                                 profile.colorSpaceAgnosticDataspace);
+}
+
+// Checks for a ColorProfile match
+MATCHER_P(ColorProfileEq, expected, "") {
+    std::string buf;
+    buf.append("ColorProfiles are not equal\n");
+    dumpColorProfile(expected, buf, "expected value");
+    dumpColorProfile(arg, buf, "actual value");
+    *result_listener << buf;
+
+    return (expected.mode == arg.mode) && (expected.dataspace == arg.dataspace) &&
+            (expected.renderIntent == arg.renderIntent) &&
+            (expected.colorSpaceAgnosticDataspace == arg.colorSpaceAgnosticDataspace);
+}
+
 /*
  * Basic construction
  */
@@ -296,10 +328,12 @@
 }
 
 /*
- * Output::setColorMode
+ * Output::setColorProfile
  */
 
-TEST_F(OutputTest, setColorModeSetsStateAndDirtiesOutputIfChanged) {
+using OutputSetColorProfileTest = OutputTest;
+
+TEST_F(OutputSetColorProfileTest, setsStateAndDirtiesOutputIfChanged) {
     using ColorProfile = Output::ColorProfile;
 
     EXPECT_CALL(*mDisplayColorProfile,
@@ -320,7 +354,7 @@
     EXPECT_THAT(mOutput->getState().dirtyRegion, RegionEq(Region(kDefaultDisplaySize)));
 }
 
-TEST_F(OutputTest, setColorModeDoesNothingIfNoChange) {
+TEST_F(OutputSetColorProfileTest, doesNothingIfNoChange) {
     using ColorProfile = Output::ColorProfile;
 
     EXPECT_CALL(*mDisplayColorProfile,
@@ -658,7 +692,732 @@
  * Output::updateColorProfile()
  */
 
-// TODO(b/144060211) - Add coverage
+struct OutputUpdateColorProfileTest : public testing::Test {
+    using TestType = OutputUpdateColorProfileTest;
+
+    struct OutputPartialMock : public OutputPartialMockBase {
+        // All child helper functions Output::present() are defined as mocks,
+        // and those are tested separately, allowing the present() test to
+        // just cover the high level flow.
+        MOCK_METHOD1(setColorProfile, void(const ColorProfile&));
+    };
+
+    struct Layer {
+        Layer() {
+            EXPECT_CALL(mOutputLayer, getLayer()).WillRepeatedly(ReturnRef(mLayer));
+            EXPECT_CALL(mOutputLayer, getLayerFE()).WillRepeatedly(ReturnRef(mLayerFE));
+            EXPECT_CALL(mLayer, getFEState()).WillRepeatedly(ReturnRef(mLayerFEState));
+        }
+
+        StrictMock<mock::OutputLayer> mOutputLayer;
+        StrictMock<mock::Layer> mLayer;
+        StrictMock<mock::LayerFE> mLayerFE;
+        LayerFECompositionState mLayerFEState;
+    };
+
+    OutputUpdateColorProfileTest() {
+        mOutput.setDisplayColorProfileForTest(
+                std::unique_ptr<DisplayColorProfile>(mDisplayColorProfile));
+        mOutput.setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
+
+        EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0))
+                .WillRepeatedly(Return(&mLayer1.mOutputLayer));
+        EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(1))
+                .WillRepeatedly(Return(&mLayer2.mOutputLayer));
+        EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(2))
+                .WillRepeatedly(Return(&mLayer3.mOutputLayer));
+    }
+
+    struct ExecuteState : public CallOrderStateMachineHelper<TestType, ExecuteState> {
+        void execute() { getInstance()->mOutput.updateColorProfile(getInstance()->mRefreshArgs); }
+    };
+
+    mock::DisplayColorProfile* mDisplayColorProfile = new StrictMock<mock::DisplayColorProfile>();
+    mock::RenderSurface* mRenderSurface = new StrictMock<mock::RenderSurface>();
+    StrictMock<OutputPartialMock> mOutput;
+
+    Layer mLayer1;
+    Layer mLayer2;
+    Layer mLayer3;
+
+    CompositionRefreshArgs mRefreshArgs;
+};
+
+// TODO(b/144522012): Refactor Output::updateColorProfile and the related code
+// to make it easier to write unit tests.
+
+TEST_F(OutputUpdateColorProfileTest, setsAColorProfileWhenUnmanaged) {
+    // When the outputColorSetting is set to kUnmanaged, the implementation sets
+    // a simple default color profile without looking at anything else.
+
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(3));
+    EXPECT_CALL(mOutput,
+                setColorProfile(ColorProfileEq(
+                        ColorProfile{ui::ColorMode::NATIVE, ui::Dataspace::UNKNOWN,
+                                     ui::RenderIntent::COLORIMETRIC, ui::Dataspace::UNKNOWN})));
+
+    mRefreshArgs.outputColorSetting = OutputColorSetting::kUnmanaged;
+    mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
+
+    mOutput.updateColorProfile(mRefreshArgs);
+}
+
+struct OutputUpdateColorProfileTest_GetBestColorModeResultBecomesSetProfile
+      : public OutputUpdateColorProfileTest {
+    OutputUpdateColorProfileTest_GetBestColorModeResultBecomesSetProfile() {
+        EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(0));
+        mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
+        mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
+    }
+
+    struct ExpectBestColorModeCallResultUsedToSetColorProfileState
+          : public CallOrderStateMachineHelper<
+                    TestType, ExpectBestColorModeCallResultUsedToSetColorProfileState> {
+        [[nodiscard]] auto expectBestColorModeCallResultUsedToSetColorProfile(
+                ui::ColorMode colorMode, ui::Dataspace dataspace, ui::RenderIntent renderIntent) {
+            EXPECT_CALL(*getInstance()->mDisplayColorProfile,
+                        getBestColorMode(ui::Dataspace::V0_SRGB, ui::RenderIntent::ENHANCE, _, _,
+                                         _))
+                    .WillOnce(DoAll(SetArgPointee<2>(dataspace), SetArgPointee<3>(colorMode),
+                                    SetArgPointee<4>(renderIntent)));
+            EXPECT_CALL(getInstance()->mOutput,
+                        setColorProfile(
+                                ColorProfileEq(ColorProfile{colorMode, dataspace, renderIntent,
+                                                            ui::Dataspace::UNKNOWN})));
+            return nextState<ExecuteState>();
+        }
+    };
+
+    // Call this member function to start using the mini-DSL defined above.
+    [[nodiscard]] auto verify() {
+        return ExpectBestColorModeCallResultUsedToSetColorProfileState::make(this);
+    }
+};
+
+TEST_F(OutputUpdateColorProfileTest_GetBestColorModeResultBecomesSetProfile,
+       Native_Unknown_Colorimetric_Set) {
+    verify().expectBestColorModeCallResultUsedToSetColorProfile(ui::ColorMode::NATIVE,
+                                                                ui::Dataspace::UNKNOWN,
+                                                                ui::RenderIntent::COLORIMETRIC)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_GetBestColorModeResultBecomesSetProfile,
+       DisplayP3_DisplayP3_Enhance_Set) {
+    verify().expectBestColorModeCallResultUsedToSetColorProfile(ui::ColorMode::DISPLAY_P3,
+                                                                ui::Dataspace::DISPLAY_P3,
+                                                                ui::RenderIntent::ENHANCE)
+            .execute();
+}
+
+struct OutputUpdateColorProfileTest_ColorSpaceAgnosticeDataspaceAffectsSetColorProfile
+      : public OutputUpdateColorProfileTest {
+    OutputUpdateColorProfileTest_ColorSpaceAgnosticeDataspaceAffectsSetColorProfile() {
+        EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(0));
+        EXPECT_CALL(*mDisplayColorProfile,
+                    getBestColorMode(ui::Dataspace::V0_SRGB, ui::RenderIntent::ENHANCE, _, _, _))
+                .WillRepeatedly(DoAll(SetArgPointee<2>(ui::Dataspace::UNKNOWN),
+                                      SetArgPointee<3>(ui::ColorMode::NATIVE),
+                                      SetArgPointee<4>(ui::RenderIntent::COLORIMETRIC)));
+        mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
+    }
+
+    struct IfColorSpaceAgnosticDataspaceSetToState
+          : public CallOrderStateMachineHelper<TestType, IfColorSpaceAgnosticDataspaceSetToState> {
+        [[nodiscard]] auto ifColorSpaceAgnosticDataspaceSetTo(ui::Dataspace dataspace) {
+            getInstance()->mRefreshArgs.colorSpaceAgnosticDataspace = dataspace;
+            return nextState<ThenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspaceState>();
+        }
+    };
+
+    struct ThenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspaceState
+          : public CallOrderStateMachineHelper<
+                    TestType, ThenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspaceState> {
+        [[nodiscard]] auto thenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspace(
+                ui::Dataspace dataspace) {
+            EXPECT_CALL(getInstance()->mOutput,
+                        setColorProfile(ColorProfileEq(
+                                ColorProfile{ui::ColorMode::NATIVE, ui::Dataspace::UNKNOWN,
+                                             ui::RenderIntent::COLORIMETRIC, dataspace})));
+            return nextState<ExecuteState>();
+        }
+    };
+
+    // Call this member function to start using the mini-DSL defined above.
+    [[nodiscard]] auto verify() { return IfColorSpaceAgnosticDataspaceSetToState::make(this); }
+};
+
+TEST_F(OutputUpdateColorProfileTest_ColorSpaceAgnosticeDataspaceAffectsSetColorProfile, DisplayP3) {
+    verify().ifColorSpaceAgnosticDataspaceSetTo(ui::Dataspace::DISPLAY_P3)
+            .thenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspace(ui::Dataspace::DISPLAY_P3)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_ColorSpaceAgnosticeDataspaceAffectsSetColorProfile, V0_SRGB) {
+    verify().ifColorSpaceAgnosticDataspaceSetTo(ui::Dataspace::V0_SRGB)
+            .thenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspace(ui::Dataspace::V0_SRGB)
+            .execute();
+}
+
+struct OutputUpdateColorProfileTest_TopmostLayerPreferenceSetsOutputPreference
+      : public OutputUpdateColorProfileTest {
+    // Internally the implementation looks through the dataspaces of all the
+    // visible layers. The topmost one that also has an actual dataspace
+    // preference set is used to drive subsequent choices.
+
+    OutputUpdateColorProfileTest_TopmostLayerPreferenceSetsOutputPreference() {
+        mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
+        mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
+
+        EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(3));
+        EXPECT_CALL(mOutput, setColorProfile(_)).WillRepeatedly(Return());
+    }
+
+    struct IfTopLayerDataspaceState
+          : public CallOrderStateMachineHelper<TestType, IfTopLayerDataspaceState> {
+        [[nodiscard]] auto ifTopLayerIs(ui::Dataspace dataspace) {
+            getInstance()->mLayer3.mLayerFEState.dataspace = dataspace;
+            return nextState<AndIfMiddleLayerDataspaceState>();
+        }
+        [[nodiscard]] auto ifTopLayerHasNoPreference() {
+            return ifTopLayerIs(ui::Dataspace::UNKNOWN);
+        }
+    };
+
+    struct AndIfMiddleLayerDataspaceState
+          : public CallOrderStateMachineHelper<TestType, AndIfMiddleLayerDataspaceState> {
+        [[nodiscard]] auto andIfMiddleLayerIs(ui::Dataspace dataspace) {
+            getInstance()->mLayer2.mLayerFEState.dataspace = dataspace;
+            return nextState<AndIfBottomLayerDataspaceState>();
+        }
+        [[nodiscard]] auto andIfMiddleLayerHasNoPreference() {
+            return andIfMiddleLayerIs(ui::Dataspace::UNKNOWN);
+        }
+    };
+
+    struct AndIfBottomLayerDataspaceState
+          : public CallOrderStateMachineHelper<TestType, AndIfBottomLayerDataspaceState> {
+        [[nodiscard]] auto andIfBottomLayerIs(ui::Dataspace dataspace) {
+            getInstance()->mLayer1.mLayerFEState.dataspace = dataspace;
+            return nextState<ThenExpectBestColorModeCallUsesState>();
+        }
+        [[nodiscard]] auto andIfBottomLayerHasNoPreference() {
+            return andIfBottomLayerIs(ui::Dataspace::UNKNOWN);
+        }
+    };
+
+    struct ThenExpectBestColorModeCallUsesState
+          : public CallOrderStateMachineHelper<TestType, ThenExpectBestColorModeCallUsesState> {
+        [[nodiscard]] auto thenExpectBestColorModeCallUses(ui::Dataspace dataspace) {
+            EXPECT_CALL(*getInstance()->mDisplayColorProfile,
+                        getBestColorMode(dataspace, _, _, _, _));
+            return nextState<ExecuteState>();
+        }
+    };
+
+    // Call this member function to start using the mini-DSL defined above.
+    [[nodiscard]] auto verify() { return IfTopLayerDataspaceState::make(this); }
+};
+
+TEST_F(OutputUpdateColorProfileTest_TopmostLayerPreferenceSetsOutputPreference,
+       noStrongLayerPrefenceUses_V0_SRGB) {
+    // If none of the layers indicate a preference, then V0_SRGB is the
+    // preferred choice (subject to additional checks).
+    verify().ifTopLayerHasNoPreference()
+            .andIfMiddleLayerHasNoPreference()
+            .andIfBottomLayerHasNoPreference()
+            .thenExpectBestColorModeCallUses(ui::Dataspace::V0_SRGB)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_TopmostLayerPreferenceSetsOutputPreference,
+       ifTopmostUses_DisplayP3_Then_DisplayP3_Chosen) {
+    // If only the topmost layer has a preference, then that is what is chosen.
+    verify().ifTopLayerIs(ui::Dataspace::DISPLAY_P3)
+            .andIfMiddleLayerHasNoPreference()
+            .andIfBottomLayerHasNoPreference()
+            .thenExpectBestColorModeCallUses(ui::Dataspace::DISPLAY_P3)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_TopmostLayerPreferenceSetsOutputPreference,
+       ifMiddleUses_DisplayP3_Then_DisplayP3_Chosen) {
+    // If only the middle layer has a preference, that that is what is chosen.
+    verify().ifTopLayerHasNoPreference()
+            .andIfMiddleLayerIs(ui::Dataspace::DISPLAY_P3)
+            .andIfBottomLayerHasNoPreference()
+            .thenExpectBestColorModeCallUses(ui::Dataspace::DISPLAY_P3)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_TopmostLayerPreferenceSetsOutputPreference,
+       ifBottomUses_DisplayP3_Then_DisplayP3_Chosen) {
+    // If only the middle layer has a preference, that that is what is chosen.
+    verify().ifTopLayerHasNoPreference()
+            .andIfMiddleLayerHasNoPreference()
+            .andIfBottomLayerIs(ui::Dataspace::DISPLAY_P3)
+            .thenExpectBestColorModeCallUses(ui::Dataspace::DISPLAY_P3)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_TopmostLayerPreferenceSetsOutputPreference,
+       ifTopUses_DisplayBT2020_AndBottomUses_DisplayP3_Then_DisplayBT2020_Chosen) {
+    // If multiple layers have a preference, the topmost value is what is used.
+    verify().ifTopLayerIs(ui::Dataspace::DISPLAY_BT2020)
+            .andIfMiddleLayerHasNoPreference()
+            .andIfBottomLayerIs(ui::Dataspace::DISPLAY_P3)
+            .thenExpectBestColorModeCallUses(ui::Dataspace::DISPLAY_BT2020)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_TopmostLayerPreferenceSetsOutputPreference,
+       ifTopUses_DisplayP3_AndBottomUses_V0_SRGB_Then_DisplayP3_Chosen) {
+    // If multiple layers have a preference, the topmost value is what is used.
+    verify().ifTopLayerIs(ui::Dataspace::DISPLAY_P3)
+            .andIfMiddleLayerHasNoPreference()
+            .andIfBottomLayerIs(ui::Dataspace::DISPLAY_BT2020)
+            .thenExpectBestColorModeCallUses(ui::Dataspace::DISPLAY_P3)
+            .execute();
+}
+
+struct OutputUpdateColorProfileTest_ForceOutputColorOverrides
+      : public OutputUpdateColorProfileTest {
+    // If CompositionRefreshArgs::forceOutputColorMode is set to some specific
+    // values, it overrides the layer dataspace choice.
+
+    OutputUpdateColorProfileTest_ForceOutputColorOverrides() {
+        mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
+        mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
+
+        mLayer1.mLayerFEState.dataspace = ui::Dataspace::DISPLAY_BT2020;
+
+        EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(1));
+        EXPECT_CALL(mOutput, setColorProfile(_)).WillRepeatedly(Return());
+    }
+
+    struct IfForceOutputColorModeState
+          : public CallOrderStateMachineHelper<TestType, IfForceOutputColorModeState> {
+        [[nodiscard]] auto ifForceOutputColorMode(ui::ColorMode colorMode) {
+            getInstance()->mRefreshArgs.forceOutputColorMode = colorMode;
+            return nextState<ThenExpectBestColorModeCallUsesState>();
+        }
+        [[nodiscard]] auto ifNoOverride() { return ifForceOutputColorMode(ui::ColorMode::NATIVE); }
+    };
+
+    struct ThenExpectBestColorModeCallUsesState
+          : public CallOrderStateMachineHelper<TestType, ThenExpectBestColorModeCallUsesState> {
+        [[nodiscard]] auto thenExpectBestColorModeCallUses(ui::Dataspace dataspace) {
+            EXPECT_CALL(*getInstance()->mDisplayColorProfile,
+                        getBestColorMode(dataspace, _, _, _, _));
+            return nextState<ExecuteState>();
+        }
+    };
+
+    // Call this member function to start using the mini-DSL defined above.
+    [[nodiscard]] auto verify() { return IfForceOutputColorModeState::make(this); }
+};
+
+TEST_F(OutputUpdateColorProfileTest_ForceOutputColorOverrides, NoOverride_DoesNotOverride) {
+    // By default the layer state is used to set the preferred dataspace
+    verify().ifNoOverride()
+            .thenExpectBestColorModeCallUses(ui::Dataspace::DISPLAY_BT2020)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_ForceOutputColorOverrides, SRGB_Override_USES_V0_SRGB) {
+    // Setting ui::ColorMode::SRGB overrides it with ui::Dataspace::V0_SRGB
+    verify().ifForceOutputColorMode(ui::ColorMode::SRGB)
+            .thenExpectBestColorModeCallUses(ui::Dataspace::V0_SRGB)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_ForceOutputColorOverrides, DisplayP3_Override_Uses_DisplayP3) {
+    // Setting ui::ColorMode::DISPLAY_P3 overrides it with ui::Dataspace::DISPLAY_P3
+    verify().ifForceOutputColorMode(ui::ColorMode::DISPLAY_P3)
+            .thenExpectBestColorModeCallUses(ui::Dataspace::DISPLAY_P3)
+            .execute();
+}
+
+// HDR output requires all layers to be compatible with the chosen HDR
+// dataspace, along with there being proper support.
+struct OutputUpdateColorProfileTest_Hdr : public OutputUpdateColorProfileTest {
+    OutputUpdateColorProfileTest_Hdr() {
+        mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
+        mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
+        EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(2));
+        EXPECT_CALL(mOutput, setColorProfile(_)).WillRepeatedly(Return());
+    }
+
+    static constexpr ui::Dataspace kNonHdrDataspace = ui::Dataspace::DISPLAY_P3;
+    static constexpr ui::Dataspace BT2020_PQ = ui::Dataspace::BT2020_PQ;
+    static constexpr ui::Dataspace BT2020_HLG = ui::Dataspace::BT2020_HLG;
+    static constexpr ui::Dataspace DISPLAY_P3 = ui::Dataspace::DISPLAY_P3;
+
+    struct IfTopLayerDataspaceState
+          : public CallOrderStateMachineHelper<TestType, IfTopLayerDataspaceState> {
+        [[nodiscard]] auto ifTopLayerIs(ui::Dataspace dataspace) {
+            getInstance()->mLayer2.mLayerFEState.dataspace = dataspace;
+            return nextState<AndTopLayerCompositionTypeState>();
+        }
+        [[nodiscard]] auto ifTopLayerIsNotHdr() { return ifTopLayerIs(kNonHdrDataspace); }
+    };
+
+    struct AndTopLayerCompositionTypeState
+          : public CallOrderStateMachineHelper<TestType, AndTopLayerCompositionTypeState> {
+        [[nodiscard]] auto andTopLayerIsREComposed(bool renderEngineComposed) {
+            getInstance()->mLayer2.mLayerFEState.forceClientComposition = renderEngineComposed;
+            return nextState<AndIfBottomLayerDataspaceState>();
+        }
+    };
+
+    struct AndIfBottomLayerDataspaceState
+          : public CallOrderStateMachineHelper<TestType, AndIfBottomLayerDataspaceState> {
+        [[nodiscard]] auto andIfBottomLayerIs(ui::Dataspace dataspace) {
+            getInstance()->mLayer1.mLayerFEState.dataspace = dataspace;
+            return nextState<AndBottomLayerCompositionTypeState>();
+        }
+        [[nodiscard]] auto andIfBottomLayerIsNotHdr() {
+            return andIfBottomLayerIs(kNonHdrDataspace);
+        }
+    };
+
+    struct AndBottomLayerCompositionTypeState
+          : public CallOrderStateMachineHelper<TestType, AndBottomLayerCompositionTypeState> {
+        [[nodiscard]] auto andBottomLayerIsREComposed(bool renderEngineComposed) {
+            getInstance()->mLayer1.mLayerFEState.forceClientComposition = renderEngineComposed;
+            return nextState<AndIfHasLegacySupportState>();
+        }
+    };
+
+    struct AndIfHasLegacySupportState
+          : public CallOrderStateMachineHelper<TestType, AndIfHasLegacySupportState> {
+        [[nodiscard]] auto andIfLegacySupportFor(ui::Dataspace dataspace, bool legacySupport) {
+            EXPECT_CALL(*getInstance()->mDisplayColorProfile, hasLegacyHdrSupport(dataspace))
+                    .WillOnce(Return(legacySupport));
+            return nextState<ThenExpectBestColorModeCallUsesState>();
+        }
+    };
+
+    struct ThenExpectBestColorModeCallUsesState
+          : public CallOrderStateMachineHelper<TestType, ThenExpectBestColorModeCallUsesState> {
+        [[nodiscard]] auto thenExpectBestColorModeCallUses(ui::Dataspace dataspace) {
+            EXPECT_CALL(*getInstance()->mDisplayColorProfile,
+                        getBestColorMode(dataspace, _, _, _, _));
+            return nextState<ExecuteState>();
+        }
+    };
+
+    // Call this member function to start using the mini-DSL defined above.
+    [[nodiscard]] auto verify() { return IfTopLayerDataspaceState::make(this); }
+};
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, PQ_HW_On_PQ_HW_Uses_PQ) {
+    // If all layers use BT2020_PQ, and there are no other special conditions,
+    // BT2020_PQ is used.
+    verify().ifTopLayerIs(BT2020_PQ)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_PQ)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_PQ, false)
+            .thenExpectBestColorModeCallUses(BT2020_PQ)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, PQ_HW_On_PQ_HW_IfPQHasLegacySupport_Uses_DisplayP3) {
+    // BT2020_PQ is not used if there is only legacy support for it.
+    verify().ifTopLayerIs(BT2020_PQ)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_PQ)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_PQ, true)
+            .thenExpectBestColorModeCallUses(DISPLAY_P3)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, PQ_HW_On_PQ_RE_Uses_PQ) {
+    // BT2020_PQ is still used if the bottom layer is RenderEngine composed.
+    verify().ifTopLayerIs(BT2020_PQ)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_PQ)
+            .andBottomLayerIsREComposed(true)
+            .andIfLegacySupportFor(BT2020_PQ, false)
+            .thenExpectBestColorModeCallUses(BT2020_PQ)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, PQ_RE_On_PQ_HW_Uses_DisplayP3) {
+    // BT2020_PQ is not used if the top layer is RenderEngine composed.
+    verify().ifTopLayerIs(BT2020_PQ)
+            .andTopLayerIsREComposed(true)
+            .andIfBottomLayerIs(BT2020_PQ)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_PQ, false)
+            .thenExpectBestColorModeCallUses(DISPLAY_P3)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, PQ_HW_On_HLG_HW_Uses_PQ) {
+    // If there is mixed HLG/PQ use, and the topmost layer is PQ, then PQ is used if there
+    // are no other special conditions.
+    verify().ifTopLayerIs(BT2020_PQ)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_HLG)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_PQ, false)
+            .thenExpectBestColorModeCallUses(BT2020_PQ)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, PQ_HW_On_HLG_HW_IfPQHasLegacySupport_Uses_DisplayP3) {
+    // BT2020_PQ is not used if there is only legacy support for it.
+    verify().ifTopLayerIs(BT2020_PQ)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_HLG)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_PQ, true)
+            .thenExpectBestColorModeCallUses(DISPLAY_P3)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, PQ_HW_On_HLG_RE_Uses_PQ) {
+    // BT2020_PQ is used if the bottom HLG layer is RenderEngine composed.
+    verify().ifTopLayerIs(BT2020_PQ)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_HLG)
+            .andBottomLayerIsREComposed(true)
+            .andIfLegacySupportFor(BT2020_PQ, false)
+            .thenExpectBestColorModeCallUses(BT2020_PQ)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, PQ_RE_On_HLG_HW_Uses_DisplayP3) {
+    // BT2020_PQ is not used if the top PQ layer is RenderEngine composed.
+    verify().ifTopLayerIs(BT2020_PQ)
+            .andTopLayerIsREComposed(true)
+            .andIfBottomLayerIs(BT2020_HLG)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_PQ, false)
+            .thenExpectBestColorModeCallUses(DISPLAY_P3)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, HLG_HW_On_PQ_HW_Uses_PQ) {
+    // If there is mixed HLG/PQ use, and the topmost layer is HLG, then PQ is
+    // used if there are no other special conditions.
+    verify().ifTopLayerIs(BT2020_HLG)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_PQ)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_PQ, false)
+            .thenExpectBestColorModeCallUses(BT2020_PQ)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, HLG_HW_On_PQ_HW_IfPQHasLegacySupport_Uses_DisplayP3) {
+    // BT2020_PQ is not used if there is only legacy support for it.
+    verify().ifTopLayerIs(BT2020_HLG)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_PQ)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_PQ, true)
+            .thenExpectBestColorModeCallUses(DISPLAY_P3)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, HLG_HW_On_PQ_RE_Uses_DisplayP3) {
+    // BT2020_PQ is not used if the bottom PQ layer is RenderEngine composed.
+    verify().ifTopLayerIs(BT2020_HLG)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_PQ)
+            .andBottomLayerIsREComposed(true)
+            .andIfLegacySupportFor(BT2020_PQ, false)
+            .thenExpectBestColorModeCallUses(DISPLAY_P3)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, HLG_RE_On_PQ_HW_Uses_PQ) {
+    // BT2020_PQ is still used if the top HLG layer is RenderEngine composed.
+    verify().ifTopLayerIs(BT2020_HLG)
+            .andTopLayerIsREComposed(true)
+            .andIfBottomLayerIs(BT2020_PQ)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_PQ, false)
+            .thenExpectBestColorModeCallUses(BT2020_PQ)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, HLG_HW_On_HLG_HW_Uses_HLG) {
+    // If all layers use HLG then HLG is used if there are no other special
+    // conditions.
+    verify().ifTopLayerIs(BT2020_HLG)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_HLG)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_HLG, false)
+            .thenExpectBestColorModeCallUses(BT2020_HLG)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, HLG_HW_On_HLG_HW_IfPQHasLegacySupport_Uses_DisplayP3) {
+    // BT2020_HLG is not used if there is legacy support for it.
+    verify().ifTopLayerIs(BT2020_HLG)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_HLG)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_HLG, true)
+            .thenExpectBestColorModeCallUses(DISPLAY_P3)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, HLG_HW_On_HLG_RE_Uses_HLG) {
+    // BT2020_HLG is used even if the bottom layer is client composed.
+    verify().ifTopLayerIs(BT2020_HLG)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIs(BT2020_HLG)
+            .andBottomLayerIsREComposed(true)
+            .andIfLegacySupportFor(BT2020_HLG, false)
+            .thenExpectBestColorModeCallUses(BT2020_HLG)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, HLG_RE_On_HLG_HW_Uses_HLG) {
+    // BT2020_HLG is used even if the top layer is client composed.
+    verify().ifTopLayerIs(BT2020_HLG)
+            .andTopLayerIsREComposed(true)
+            .andIfBottomLayerIs(BT2020_HLG)
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_HLG, false)
+            .thenExpectBestColorModeCallUses(BT2020_HLG)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, PQ_HW_On_NonHdr_HW_Uses_PQ) {
+    // Even if there are non-HDR layers present, BT2020_PQ can still be used.
+    verify().ifTopLayerIs(BT2020_PQ)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIsNotHdr()
+            .andBottomLayerIsREComposed(false)
+            .andIfLegacySupportFor(BT2020_PQ, false)
+            .thenExpectBestColorModeCallUses(BT2020_PQ)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfileTest_Hdr, HLG_HW_On_NonHdr_RE_Uses_HLG) {
+    // If all layers use HLG then HLG is used if there are no other special
+    // conditions.
+    verify().ifTopLayerIs(BT2020_HLG)
+            .andTopLayerIsREComposed(false)
+            .andIfBottomLayerIsNotHdr()
+            .andBottomLayerIsREComposed(true)
+            .andIfLegacySupportFor(BT2020_HLG, false)
+            .thenExpectBestColorModeCallUses(BT2020_HLG)
+            .execute();
+}
+
+struct OutputUpdateColorProfile_AffectsChosenRenderIntentTest
+      : public OutputUpdateColorProfileTest {
+    // The various values for CompositionRefreshArgs::outputColorSetting affect
+    // the chosen renderIntent, along with whether the preferred dataspace is an
+    // HDR dataspace or not.
+
+    OutputUpdateColorProfile_AffectsChosenRenderIntentTest() {
+        mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
+        mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
+        mLayer1.mLayerFEState.dataspace = ui::Dataspace::BT2020_PQ;
+        EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(1));
+        EXPECT_CALL(mOutput, setColorProfile(_)).WillRepeatedly(Return());
+        EXPECT_CALL(*mDisplayColorProfile, hasLegacyHdrSupport(ui::Dataspace::BT2020_PQ))
+                .WillRepeatedly(Return(false));
+    }
+
+    // The tests here involve enough state and GMock setup that using a mini-DSL
+    // makes the tests much more readable, and allows the test to focus more on
+    // the intent than on some of the details.
+
+    static constexpr ui::Dataspace kNonHdrDataspace = ui::Dataspace::DISPLAY_P3;
+    static constexpr ui::Dataspace kHdrDataspace = ui::Dataspace::BT2020_PQ;
+
+    struct IfDataspaceChosenState
+          : public CallOrderStateMachineHelper<TestType, IfDataspaceChosenState> {
+        [[nodiscard]] auto ifDataspaceChosenIs(ui::Dataspace dataspace) {
+            getInstance()->mLayer1.mLayerFEState.dataspace = dataspace;
+            return nextState<AndOutputColorSettingState>();
+        }
+        [[nodiscard]] auto ifDataspaceChosenIsNonHdr() {
+            return ifDataspaceChosenIs(kNonHdrDataspace);
+        }
+        [[nodiscard]] auto ifDataspaceChosenIsHdr() { return ifDataspaceChosenIs(kHdrDataspace); }
+    };
+
+    struct AndOutputColorSettingState
+          : public CallOrderStateMachineHelper<TestType, AndOutputColorSettingState> {
+        [[nodiscard]] auto andOutputColorSettingIs(OutputColorSetting setting) {
+            getInstance()->mRefreshArgs.outputColorSetting = setting;
+            return nextState<ThenExpectBestColorModeCallUsesState>();
+        }
+    };
+
+    struct ThenExpectBestColorModeCallUsesState
+          : public CallOrderStateMachineHelper<TestType, ThenExpectBestColorModeCallUsesState> {
+        [[nodiscard]] auto thenExpectBestColorModeCallUses(ui::RenderIntent intent) {
+            EXPECT_CALL(*getInstance()->mDisplayColorProfile,
+                        getBestColorMode(getInstance()->mLayer1.mLayerFEState.dataspace, intent, _,
+                                         _, _));
+            return nextState<ExecuteState>();
+        }
+    };
+
+    // Tests call one of these two helper member functions to start using the
+    // mini-DSL defined above.
+    [[nodiscard]] auto verify() { return IfDataspaceChosenState::make(this); }
+};
+
+TEST_F(OutputUpdateColorProfile_AffectsChosenRenderIntentTest,
+       Managed_NonHdr_Prefers_Colorimetric) {
+    verify().ifDataspaceChosenIsNonHdr()
+            .andOutputColorSettingIs(OutputColorSetting::kManaged)
+            .thenExpectBestColorModeCallUses(ui::RenderIntent::COLORIMETRIC)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfile_AffectsChosenRenderIntentTest,
+       Managed_Hdr_Prefers_ToneMapColorimetric) {
+    verify().ifDataspaceChosenIsHdr()
+            .andOutputColorSettingIs(OutputColorSetting::kManaged)
+            .thenExpectBestColorModeCallUses(ui::RenderIntent::TONE_MAP_COLORIMETRIC)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfile_AffectsChosenRenderIntentTest, Enhanced_NonHdr_Prefers_Enhance) {
+    verify().ifDataspaceChosenIsNonHdr()
+            .andOutputColorSettingIs(OutputColorSetting::kEnhanced)
+            .thenExpectBestColorModeCallUses(ui::RenderIntent::ENHANCE)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfile_AffectsChosenRenderIntentTest,
+       Enhanced_Hdr_Prefers_ToneMapEnhance) {
+    verify().ifDataspaceChosenIsHdr()
+            .andOutputColorSettingIs(OutputColorSetting::kEnhanced)
+            .thenExpectBestColorModeCallUses(ui::RenderIntent::TONE_MAP_ENHANCE)
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfile_AffectsChosenRenderIntentTest, Vendor_NonHdr_Prefers_Vendor) {
+    verify().ifDataspaceChosenIsNonHdr()
+            .andOutputColorSettingIs(kVendorSpecifiedOutputColorSetting)
+            .thenExpectBestColorModeCallUses(
+                    static_cast<ui::RenderIntent>(kVendorSpecifiedOutputColorSetting))
+            .execute();
+}
+
+TEST_F(OutputUpdateColorProfile_AffectsChosenRenderIntentTest, Vendor_Hdr_Prefers_Vendor) {
+    verify().ifDataspaceChosenIsHdr()
+            .andOutputColorSettingIs(kVendorSpecifiedOutputColorSetting)
+            .thenExpectBestColorModeCallUses(
+                    static_cast<ui::RenderIntent>(kVendorSpecifiedOutputColorSetting))
+            .execute();
+}
 
 /*
  * Output::beginFrame()