Merge "Trace input only if the layer needs an input info" into tm-dev
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index f3d0b65..b696707 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -5146,28 +5146,54 @@
     return true;
 }
 
+/**
+ * Get the touched foreground window on the given display.
+ * Return null if there are no windows touched on that display, or if more than one foreground
+ * window is being touched.
+ */
+sp<WindowInfoHandle> InputDispatcher::findTouchedForegroundWindowLocked(int32_t displayId) const {
+    auto stateIt = mTouchStatesByDisplay.find(displayId);
+    if (stateIt == mTouchStatesByDisplay.end()) {
+        ALOGI("No touch state on display %" PRId32, displayId);
+        return nullptr;
+    }
+
+    const TouchState& state = stateIt->second;
+    sp<WindowInfoHandle> touchedForegroundWindow;
+    // If multiple foreground windows are touched, return nullptr
+    for (const TouchedWindow& window : state.windows) {
+        if (window.targetFlags & InputTarget::FLAG_FOREGROUND) {
+            if (touchedForegroundWindow != nullptr) {
+                ALOGI("Two or more foreground windows: %s and %s",
+                      touchedForegroundWindow->getName().c_str(),
+                      window.windowHandle->getName().c_str());
+                return nullptr;
+            }
+            touchedForegroundWindow = window.windowHandle;
+        }
+    }
+    return touchedForegroundWindow;
+}
+
 // Binder call
-bool InputDispatcher::transferTouch(const sp<IBinder>& destChannelToken) {
+bool InputDispatcher::transferTouch(const sp<IBinder>& destChannelToken, int32_t displayId) {
     sp<IBinder> fromToken;
     { // acquire lock
         std::scoped_lock _l(mLock);
-
-        auto it = std::find_if(mTouchStatesByDisplay.begin(), mTouchStatesByDisplay.end(),
-                               [](const auto& pair) { return pair.second.windows.size() == 1; });
-        if (it == mTouchStatesByDisplay.end()) {
-            ALOGW("Cannot transfer touch state because there is no exact window being touched");
-            return false;
-        }
-        const int32_t displayId = it->first;
         sp<WindowInfoHandle> toWindowHandle = getWindowHandleLocked(destChannelToken, displayId);
         if (toWindowHandle == nullptr) {
-            ALOGW("Could not find window associated with token=%p", destChannelToken.get());
+            ALOGW("Could not find window associated with token=%p on display %" PRId32,
+                  destChannelToken.get(), displayId);
             return false;
         }
 
-        TouchState& state = it->second;
-        const TouchedWindow& touchedWindow = state.windows[0];
-        fromToken = touchedWindow.windowHandle->getToken();
+        sp<WindowInfoHandle> from = findTouchedForegroundWindowLocked(displayId);
+        if (from == nullptr) {
+            ALOGE("Could not find a source window in %s for %p", __func__, destChannelToken.get());
+            return false;
+        }
+
+        fromToken = from->getToken();
     } // release lock
 
     return transferTouchFocus(fromToken, destChannelToken);
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index a9fb5c6..d9677eb 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -125,7 +125,7 @@
 
     bool transferTouchFocus(const sp<IBinder>& fromToken, const sp<IBinder>& toToken,
                             bool isDragDrop = false) override;
-    bool transferTouch(const sp<IBinder>& destChannelToken) override;
+    bool transferTouch(const sp<IBinder>& destChannelToken, int32_t displayId) override;
 
     base::Result<std::unique_ptr<InputChannel>> createInputChannel(
             const std::string& name) override;
@@ -245,6 +245,9 @@
     std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAtLocked(
             int32_t displayId, int32_t x, int32_t y, bool isStylus) const REQUIRES(mLock);
 
+    sp<android::gui::WindowInfoHandle> findTouchedForegroundWindowLocked(int32_t displayId) const
+            REQUIRES(mLock);
+
     sp<Connection> getConnectionLocked(const sp<IBinder>& inputConnectionToken) const
             REQUIRES(mLock);
 
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index 100bd18..67fed8b 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -161,7 +161,7 @@
      *
      * Return true on success, false if there was no on-going touch.
      */
-    virtual bool transferTouch(const sp<IBinder>& destChannelToken) = 0;
+    virtual bool transferTouch(const sp<IBinder>& destChannelToken, int32_t displayId) = 0;
 
     /**
      * Sets focus on the specified window.
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index f15ed9e..e22ae2b 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -2476,6 +2476,63 @@
     secondWindow->consumeMotionUp();
 }
 
+/**
+ * When 'transferTouch' API is invoked, dispatcher needs to find the "best" window to take touch
+ * from. When we have spy windows, there are several windows to choose from: either spy, or the
+ * 'real' (non-spy) window. Always prefer the 'real' window because that's what would be most
+ * natural to the user.
+ * In this test, we are sending a pointer to both spy window and first window. We then try to
+ * transfer touch to the second window. The dispatcher should identify the first window as the
+ * one that should lose the gesture, and therefore the action should be to move the gesture from
+ * the first window to the second.
+ * The main goal here is to test the behaviour of 'transferTouch' API, but it's still valid to test
+ * the other API, as well.
+ */
+TEST_P(TransferTouchFixture, TransferTouch_MultipleWindowsWithSpy) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    // Create a couple of windows + a spy window
+    sp<FakeWindowHandle> spyWindow =
+            new FakeWindowHandle(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setSpy(true);
+    sp<FakeWindowHandle> firstWindow =
+            new FakeWindowHandle(application, mDispatcher, "First", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> secondWindow =
+            new FakeWindowHandle(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
+
+    // Add the windows to the dispatcher
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, firstWindow, secondWindow}}});
+
+    // Send down to the first window
+    NotifyMotionArgs downMotionArgs =
+            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyMotion(&downMotionArgs);
+    // Only the first window and spy should get the down event
+    spyWindow->consumeMotionDown();
+    firstWindow->consumeMotionDown();
+
+    // Transfer touch to the second window. Non-spy window should be preferred over the spy window
+    // if f === 'transferTouch'.
+    TransferFunction f = GetParam();
+    const bool success = f(mDispatcher, firstWindow->getToken(), secondWindow->getToken());
+    ASSERT_TRUE(success);
+    // The first window gets cancel and the second gets down
+    firstWindow->consumeMotionCancel();
+    secondWindow->consumeMotionDown();
+
+    // Send up event to the second window
+    NotifyMotionArgs upMotionArgs =
+            generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                               ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyMotion(&upMotionArgs);
+    // The first  window gets no events and the second+spy get up
+    firstWindow->assertNoEvents();
+    spyWindow->consumeMotionUp();
+    secondWindow->consumeMotionUp();
+}
+
 TEST_P(TransferTouchFixture, TransferTouch_TwoPointersNonSplitTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
@@ -2545,7 +2602,8 @@
                          ::testing::Values(
                                  [&](const std::unique_ptr<InputDispatcher>& dispatcher,
                                      sp<IBinder> /*ignored*/, sp<IBinder> destChannelToken) {
-                                     return dispatcher->transferTouch(destChannelToken);
+                                     return dispatcher->transferTouch(destChannelToken,
+                                                                      ADISPLAY_ID_DEFAULT);
                                  },
                                  [&](const std::unique_ptr<InputDispatcher>& dispatcher,
                                      sp<IBinder> from, sp<IBinder> to) {
@@ -2653,7 +2711,8 @@
     secondWindow->consumeMotionDown();
 
     // Transfer touch focus to the second window
-    const bool transferred = mDispatcher->transferTouch(secondWindow->getToken());
+    const bool transferred =
+            mDispatcher->transferTouch(secondWindow->getToken(), ADISPLAY_ID_DEFAULT);
     // The 'transferTouch' call should not succeed, because there are 2 touched windows
     ASSERT_FALSE(transferred);
     firstWindow->assertNoEvents();
@@ -2777,7 +2836,7 @@
     firstWindowInPrimary->consumeMotionDown(SECOND_DISPLAY_ID);
 
     // Transfer touch focus
-    ASSERT_TRUE(mDispatcher->transferTouch(secondWindowInSecondary->getToken()));
+    ASSERT_TRUE(mDispatcher->transferTouch(secondWindowInSecondary->getToken(), SECOND_DISPLAY_ID));
 
     // The first window gets cancel.
     firstWindowInPrimary->consumeMotionCancel(SECOND_DISPLAY_ID);
diff --git a/services/surfaceflinger/EffectLayer.cpp b/services/surfaceflinger/EffectLayer.cpp
index cc85352..e8c590e 100644
--- a/services/surfaceflinger/EffectLayer.cpp
+++ b/services/surfaceflinger/EffectLayer.cpp
@@ -114,7 +114,13 @@
 }
 
 sp<compositionengine::LayerFE> EffectLayer::getCompositionEngineLayerFE() const {
-    return asLayerFE();
+    // There's no need to get a CE Layer if the EffectLayer isn't going to draw anything. In that
+    // case, it acts more like a ContainerLayer so returning a null CE Layer makes more sense
+    if (hasSomethingToDraw()) {
+        return asLayerFE();
+    } else {
+        return nullptr;
+    }
 }
 
 compositionengine::LayerFECompositionState* EffectLayer::editCompositionState() {
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 7ec8e6f..1d43c6e 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -95,6 +95,7 @@
 #include <cmath>
 #include <cstdint>
 #include <functional>
+#include <memory>
 #include <mutex>
 #include <optional>
 #include <type_traits>
@@ -113,6 +114,7 @@
 #include "DisplayHardware/FramebufferSurface.h"
 #include "DisplayHardware/HWComposer.h"
 #include "DisplayHardware/Hal.h"
+#include "DisplayHardware/PowerAdvisor.h"
 #include "DisplayHardware/VirtualDisplaySurface.h"
 #include "DisplayRenderArea.h"
 #include "EffectLayer.h"
@@ -327,7 +329,7 @@
         mTunnelModeEnabledReporter(new TunnelModeEnabledReporter()),
         mInternalDisplayDensity(getDensityFromProperty("ro.sf.lcd_density", true)),
         mEmulatedDisplayDensity(getDensityFromProperty("qemu.sf.lcd_density", false)),
-        mPowerAdvisor(*this),
+        mPowerAdvisor(std::make_unique<Hwc2::impl::PowerAdvisor>(*this)),
         mWindowInfosListenerInvoker(sp<WindowInfosListenerInvoker>::make(*this)) {
     ALOGI("Using HWComposer service: %s", mHwcServiceName.c_str());
 }
@@ -413,7 +415,7 @@
     property_get("debug.sf.disable_client_composition_cache", value, "0");
     mDisableClientCompositionCache = atoi(value);
 
-    property_get("debug.sf.predict_hwc_composition_strategy", value, "0");
+    property_get("debug.sf.predict_hwc_composition_strategy", value, "1");
     mPredictCompositionStrategy = atoi(value);
 
     // We should be reading 'persist.sys.sf.color_saturation' here
@@ -677,16 +679,16 @@
         }
 
         readPersistentProperties();
-        mPowerAdvisor.onBootFinished();
-        mPowerAdvisor.enablePowerHint(mFlagManager.use_adpf_cpu_hint());
-        if (mPowerAdvisor.usePowerHintSession()) {
+        mPowerAdvisor->onBootFinished();
+        mPowerAdvisor->enablePowerHint(mFlagManager.use_adpf_cpu_hint());
+        if (mPowerAdvisor->usePowerHintSession()) {
             std::optional<pid_t> renderEngineTid = getRenderEngine().getRenderEngineTid();
             std::vector<int32_t> tidList;
             tidList.emplace_back(gettid());
             if (renderEngineTid.has_value()) {
                 tidList.emplace_back(*renderEngineTid);
             }
-            if (!mPowerAdvisor.startPowerHintSession(tidList)) {
+            if (!mPowerAdvisor->startPowerHintSession(tidList)) {
                 ALOGW("Cannot start power hint session");
             }
         }
@@ -812,7 +814,7 @@
     // set initial conditions (e.g. unblank default device)
     initializeDisplays();
 
-    mPowerAdvisor.init();
+    mPowerAdvisor->init();
 
     char primeShaderCache[PROPERTY_VALUE_MAX];
     property_get("service.sf.prime_shader_cache", primeShaderCache, "1");
@@ -1284,10 +1286,10 @@
     const char* const whence = __func__;
     auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
         ATRACE_NAME(whence);
-        if (mPowerAdvisor.isUsingExpensiveRendering()) {
+        if (mPowerAdvisor->isUsingExpensiveRendering()) {
             for (const auto& [_, display] : mDisplays) {
                 constexpr bool kDisable = false;
-                mPowerAdvisor.setExpensiveRenderingExpected(display->getId(), kDisable);
+                mPowerAdvisor->setExpensiveRenderingExpected(display->getId(), kDisable);
             }
         }
     });
@@ -1809,7 +1811,7 @@
     if (hint == FrameHint::kActive) {
         mScheduler->resetIdleTimer();
     }
-    mPowerAdvisor.notifyDisplayUpdateImminent();
+    mPowerAdvisor->notifyDisplayUpdateImminent();
     mScheduler->scheduleFrame();
 }
 
@@ -1979,7 +1981,7 @@
 bool SurfaceFlinger::commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expectedVsyncTime)
         FTL_FAKE_GUARD(kMainThreadContext) {
     // we set this once at the beginning of commit to ensure consistency throughout the whole frame
-    mPowerHintSessionData.sessionEnabled = mPowerAdvisor.usePowerHintSession();
+    mPowerHintSessionData.sessionEnabled = mPowerAdvisor->usePowerHintSession();
     if (mPowerHintSessionData.sessionEnabled) {
         mPowerHintSessionData.commitStart = systemTime();
     }
@@ -1998,8 +2000,8 @@
     mScheduledPresentTime = expectedVsyncTime;
 
     if (mPowerHintSessionData.sessionEnabled) {
-        mPowerAdvisor.setTargetWorkDuration(mExpectedPresentTime -
-                                            mPowerHintSessionData.commitStart);
+        mPowerAdvisor->setTargetWorkDuration(mExpectedPresentTime -
+                                             mPowerHintSessionData.commitStart);
     }
     const auto vsyncIn = [&] {
         if (!ATRACE_ENABLED()) return 0.f;
@@ -2263,7 +2265,7 @@
     if (mPowerHintSessionData.sessionEnabled) {
         const nsecs_t flingerDuration =
                 (mPowerHintSessionData.presentEnd - mPowerHintSessionData.commitStart);
-        mPowerAdvisor.sendActualWorkDuration(flingerDuration, mPowerHintSessionData.presentEnd);
+        mPowerAdvisor->sendActualWorkDuration(flingerDuration, mPowerHintSessionData.presentEnd);
     }
 }
 
@@ -2918,7 +2920,7 @@
 
     builder.setPixels(resolution);
     builder.setIsSecure(state.isSecure);
-    builder.setPowerAdvisor(&mPowerAdvisor);
+    builder.setPowerAdvisor(mPowerAdvisor.get());
     builder.setName(state.displayName);
     auto compositionDisplay = getCompositionEngine().createDisplay(builder.build());
     compositionDisplay->setLayerCachingEnabled(mLayerCachingEnabled);
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 910c5bb..07da731 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -1383,7 +1383,7 @@
     sp<os::IInputFlinger> mInputFlinger;
     InputWindowCommands mInputWindowCommands;
 
-    Hwc2::impl::PowerAdvisor mPowerAdvisor;
+    std::unique_ptr<Hwc2::PowerAdvisor> mPowerAdvisor;
 
     void enableRefreshRateOverlay(bool enable) REQUIRES(mStateLock);
 
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index cc9d48c..7823363 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -103,6 +103,7 @@
         "SurfaceFlinger_HotplugTest.cpp",
         "SurfaceFlinger_NotifyPowerBoostTest.cpp",
         "SurfaceFlinger_OnInitializeDisplaysTest.cpp",
+        "SurfaceFlinger_PowerHintTest.cpp",
         "SurfaceFlinger_SetDisplayStateTest.cpp",
         "SurfaceFlinger_SetPowerModeInternalTest.cpp",
         "SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 15c9d19..c541b92 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -113,8 +113,9 @@
         mFlinger.setupTimeStats(std::shared_ptr<TimeStats>(mTimeStats));
 
         mComposer = new Hwc2::mock::Composer();
+        mPowerAdvisor = new Hwc2::mock::PowerAdvisor();
         mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer));
-
+        mFlinger.setupPowerAdvisor(std::unique_ptr<Hwc2::PowerAdvisor>(mPowerAdvisor));
         mFlinger.mutableMaxRenderTargetSize() = 16384;
     }
 
@@ -188,7 +189,7 @@
     Hwc2::mock::Composer* mComposer = nullptr;
     renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
     mock::TimeStats* mTimeStats = new mock::TimeStats();
-    Hwc2::mock::PowerAdvisor mPowerAdvisor;
+    Hwc2::mock::PowerAdvisor* mPowerAdvisor = nullptr;
 
     sp<Fence> mClientTargetAcquireFence = Fence::NO_FENCE;
 
@@ -300,7 +301,7 @@
                                      .setId(DEFAULT_DISPLAY_ID)
                                      .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT})
                                      .setIsSecure(Derived::IS_SECURE)
-                                     .setPowerAdvisor(&test->mPowerAdvisor)
+                                     .setPowerAdvisor(test->mPowerAdvisor)
                                      .setName(std::string("Injected display for ") +
                                               test_info->test_case_name() + "." + test_info->name())
                                      .build();
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
new file mode 100644
index 0000000..0a157c4
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2022 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "SurfaceFlingerPowerHintTest"
+
+#include <compositionengine/Display.h>
+#include <compositionengine/mock/DisplaySurface.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <renderengine/mock/RenderEngine.h>
+#include <algorithm>
+#include <chrono>
+#include <memory>
+#include "TestableSurfaceFlinger.h"
+#include "mock/DisplayHardware/MockComposer.h"
+#include "mock/DisplayHardware/MockPowerAdvisor.h"
+#include "mock/MockEventThread.h"
+#include "mock/MockTimeStats.h"
+#include "mock/MockVsyncController.h"
+#include "mock/system/window/MockNativeWindow.h"
+
+using namespace android;
+using namespace android::Hwc2::mock;
+using namespace android::hardware::power;
+using namespace std::chrono_literals;
+using namespace testing;
+
+namespace android {
+namespace {
+using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
+
+constexpr hal::HWDisplayId HWC_DISPLAY = FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID;
+constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u);
+constexpr int DEFAULT_DISPLAY_WIDTH = 1920;
+constexpr int DEFAULT_DISPLAY_HEIGHT = 1024;
+
+class SurfaceFlingerPowerHintTest : public Test {
+public:
+    void SetUp() override;
+
+    void setupScheduler();
+
+protected:
+    TestableSurfaceFlinger mFlinger;
+    renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
+    sp<DisplayDevice> mDisplay;
+    sp<compositionengine::mock::DisplaySurface> mDisplaySurface =
+            new compositionengine::mock::DisplaySurface();
+    mock::NativeWindow* mNativeWindow = new mock::NativeWindow();
+    mock::TimeStats* mTimeStats = new mock::TimeStats();
+    Hwc2::mock::PowerAdvisor* mPowerAdvisor = nullptr;
+    Hwc2::mock::Composer* mComposer = nullptr;
+};
+
+void SurfaceFlingerPowerHintTest::SetUp() {
+    setupScheduler();
+    mComposer = new Hwc2::mock::Composer();
+    mPowerAdvisor = new Hwc2::mock::PowerAdvisor();
+    mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
+    mFlinger.setupTimeStats(std::shared_ptr<TimeStats>(mTimeStats));
+    mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer));
+    mFlinger.setupPowerAdvisor(std::unique_ptr<Hwc2::PowerAdvisor>(mPowerAdvisor));
+    static constexpr bool kIsPrimary = true;
+    FakeHwcDisplayInjector(DEFAULT_DISPLAY_ID, hal::DisplayType::PHYSICAL, kIsPrimary)
+            .setPowerMode(hal::PowerMode::ON)
+            .inject(&mFlinger, mComposer);
+    auto compostionEngineDisplayArgs =
+            compositionengine::DisplayCreationArgsBuilder()
+                    .setId(DEFAULT_DISPLAY_ID)
+                    .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT})
+                    .setPowerAdvisor(mPowerAdvisor)
+                    .setName("injected display")
+                    .build();
+    auto compositionDisplay =
+            compositionengine::impl::createDisplay(mFlinger.getCompositionEngine(),
+                                                   std::move(compostionEngineDisplayArgs));
+    mDisplay =
+            FakeDisplayDeviceInjector(mFlinger, compositionDisplay,
+                                      ui::DisplayConnectionType::Internal, HWC_DISPLAY, kIsPrimary)
+                    .setDisplaySurface(mDisplaySurface)
+                    .setNativeWindow(mNativeWindow)
+                    .setPowerMode(hal::PowerMode::ON)
+                    .inject();
+}
+
+void SurfaceFlingerPowerHintTest::setupScheduler() {
+    auto eventThread = std::make_unique<mock::EventThread>();
+    auto sfEventThread = std::make_unique<mock::EventThread>();
+
+    EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
+    EXPECT_CALL(*eventThread, createEventConnection(_, _))
+            .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
+                                                       ResyncCallback())));
+
+    EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
+    EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
+            .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
+                                                       ResyncCallback())));
+
+    auto vsyncController = std::make_unique<mock::VsyncController>();
+    auto vsyncTracker = std::make_unique<mock::VSyncTracker>();
+
+    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+    EXPECT_CALL(*vsyncTracker, currentPeriod())
+            .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
+    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+
+    mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
+                            std::move(eventThread), std::move(sfEventThread),
+                            TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp,
+                            TestableSurfaceFlinger::kTwoDisplayModes);
+}
+
+TEST_F(SurfaceFlingerPowerHintTest, sendDurationsIncludingHwcWaitTime) {
+    ON_CALL(*mPowerAdvisor, usePowerHintSession()).WillByDefault(Return(true));
+
+    const std::chrono::nanoseconds mockVsyncPeriod = 15ms;
+    const std::chrono::nanoseconds expectedTargetTime = 14ms;
+    EXPECT_CALL(*mPowerAdvisor, setTargetWorkDuration(Gt(expectedTargetTime.count()))).Times(1);
+
+    const nsecs_t now = systemTime();
+    const std::chrono::nanoseconds mockHwcRunTime = 20ms;
+    EXPECT_CALL(*mDisplaySurface,
+                prepareFrame(compositionengine::DisplaySurface::CompositionType::Hwc))
+            .Times(1);
+    EXPECT_CALL(*mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _))
+            .WillOnce([mockHwcRunTime] {
+                std::this_thread::sleep_for(mockHwcRunTime);
+                return hardware::graphics::composer::V2_1::Error::NONE;
+            });
+    EXPECT_CALL(*mPowerAdvisor,
+                sendActualWorkDuration(Gt(mockHwcRunTime.count()),
+                                       Gt(now + mockHwcRunTime.count())))
+            .Times(1);
+    static constexpr bool kVsyncId = 123; // arbitrary
+    mFlinger.commitAndComposite(now, kVsyncId, now + mockVsyncPeriod.count());
+}
+
+} // namespace
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index bf2465f..866d9eb 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <algorithm>
+#include <chrono>
 #include <variant>
 
 #include <compositionengine/Display.h>
@@ -200,6 +201,10 @@
                 std::make_unique<impl::HWComposer>(std::move(composer)));
     }
 
+    void setupPowerAdvisor(std::unique_ptr<Hwc2::PowerAdvisor> powerAdvisor) {
+        mFlinger->mPowerAdvisor = std::move(powerAdvisor);
+    }
+
     void setupTimeStats(const std::shared_ptr<TimeStats>& timeStats) {
         mFlinger->mCompositionEngine->setTimeStats(timeStats);
     }
@@ -328,11 +333,26 @@
     /* ------------------------------------------------------------------------
      * Forwarding for functions being tested
      */
+
+    nsecs_t commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expectedVSyncTime) {
+        mFlinger->commit(frameTime, vsyncId, expectedVSyncTime);
+        return frameTime;
+    }
+
+    nsecs_t commit(nsecs_t frameTime, int64_t vsyncId) {
+        std::chrono::nanoseconds period = 10ms;
+        return commit(frameTime, vsyncId, frameTime + period.count());
+    }
+
     nsecs_t commit() {
         const nsecs_t now = systemTime();
         const nsecs_t expectedVsyncTime = now + 10'000'000;
-        mFlinger->commit(now, kVsyncId, expectedVsyncTime);
-        return now;
+        return commit(now, kVsyncId, expectedVsyncTime);
+    }
+
+    void commitAndComposite(const nsecs_t frameTime, const int64_t vsyncId,
+                            const nsecs_t expectedVsyncTime) {
+        mFlinger->composite(commit(frameTime, vsyncId, expectedVsyncTime), kVsyncId);
     }
 
     void commitAndComposite() { mFlinger->composite(commit(), kVsyncId); }
@@ -458,11 +478,6 @@
         mFlinger->onActiveDisplayChangedLocked(activeDisplay);
     }
 
-    auto commit(nsecs_t frameTime, int64_t vsyncId) {
-        const nsecs_t expectedVsyncTime = frameTime + 10'000'000;
-        mFlinger->commit(frameTime, vsyncId, expectedVsyncTime);
-    }
-
     auto createLayer(LayerCreationArgs& args, sp<IBinder>* outHandle,
                      const sp<IBinder>& parentHandle, int32_t* outLayerId,
                      const sp<Layer>& parentLayer, uint32_t* outTransformHint) {
@@ -515,7 +530,6 @@
     auto& mutablePhysicalDisplayTokens() { return mFlinger->mPhysicalDisplayTokens; }
     auto& mutableTexturePool() { return mFlinger->mTexturePool; }
     auto& mutableTransactionFlags() { return mFlinger->mTransactionFlags; }
-    auto& mutablePowerAdvisor() { return mFlinger->mPowerAdvisor; }
     auto& mutableDebugDisableHWC() { return mFlinger->mDebugDisableHWC; }
     auto& mutableMaxRenderTargetSize() { return mFlinger->mMaxRenderTargetSize; }