Merge "Set better defaults for layer stack and oriented display space" into tm-dev
diff --git a/cmds/installd/TEST_MAPPING b/cmds/installd/TEST_MAPPING
index 8ccab4c..fc4cfc9 100644
--- a/cmds/installd/TEST_MAPPING
+++ b/cmds/installd/TEST_MAPPING
@@ -32,7 +32,12 @@
       "name": "CtsCompilationTestCases"
     },
     {
-      "name": "SdkSandboxStorageHostTest"
+      "name": "SdkSandboxStorageHostTest",
+      "options": [
+        {
+          "exclude-annotation": "android.platform.test.annotations.LargeTest"
+        }
+      ]
     }
   ]
 }
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 5003151..bdd5172 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -227,6 +227,12 @@
 }
 
 prebuilt_etc {
+    name: "android.software.opengles.deqp.level-2022-03-01.prebuilt.xml",
+    src: "android.software.opengles.deqp.level-2022-03-01.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "android.software.sip.voip.prebuilt.xml",
     src: "android.software.sip.voip.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
@@ -245,6 +251,12 @@
 }
 
 prebuilt_etc {
+    name: "android.software.vulkan.deqp.level-2022-03-01.prebuilt.xml",
+    src: "android.software.vulkan.deqp.level-2022-03-01.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "aosp_excluded_hardware.prebuilt.xml",
     src: "aosp_excluded_hardware.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
diff --git a/libs/binder/ndk/include_cpp/android/binder_interface_utils.h b/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
index b3bc7f4..c8e78fc 100644
--- a/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
+++ b/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
@@ -324,4 +324,42 @@
 
 }  // namespace ndk
 
+// Once minSdkVersion is 30, we are guaranteed to be building with the
+// Android 11 AIDL compiler which supports the SharedRefBase::make API.
+#if !defined(__ANDROID_API__) || __ANDROID_API__ >= 30 || defined(__ANDROID_APEX__)
+namespace ndk::internal {
+template <typename T, typename = void>
+struct is_complete_type : std::false_type {};
+
+template <typename T>
+struct is_complete_type<T, decltype(void(sizeof(T)))> : std::true_type {};
+}  // namespace ndk::internal
+
+namespace std {
+
+// Define `SharedRefBase` specific versions of `std::make_shared` and
+// `std::make_unique` to block people from using them. Using them to allocate
+// `ndk::SharedRefBase` objects results in double ownership. Use
+// `ndk::SharedRefBase::make<T>(...)` instead.
+//
+// Note: We exclude incomplete types because `std::is_base_of` is undefined in
+// that case.
+
+template <typename T, typename... Args,
+          std::enable_if_t<ndk::internal::is_complete_type<T>::value, bool> = true,
+          std::enable_if_t<std::is_base_of<ndk::SharedRefBase, T>::value, bool> = true>
+shared_ptr<T> make_shared(Args...) {  // SEE COMMENT ABOVE.
+    static_assert(!std::is_base_of<ndk::SharedRefBase, T>::value);
+}
+
+template <typename T, typename... Args,
+          std::enable_if_t<ndk::internal::is_complete_type<T>::value, bool> = true,
+          std::enable_if_t<std::is_base_of<ndk::SharedRefBase, T>::value, bool> = true>
+unique_ptr<T> make_unique(Args...) {  // SEE COMMENT ABOVE.
+    static_assert(!std::is_base_of<ndk::SharedRefBase, T>::value);
+}
+
+}  // namespace std
+#endif
+
 /** @} */
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index 357b454..1b136dc 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -231,8 +231,7 @@
 }
 
 TEST(NdkBinder, DetectNoSharedRefBaseCreated) {
-    EXPECT_DEATH(std::make_shared<MyBinderNdkUnitTest>(),
-                 "SharedRefBase: no ref created during lifetime");
+    EXPECT_DEATH(MyBinderNdkUnitTest(), "SharedRefBase: no ref created during lifetime");
 }
 
 TEST(NdkBinder, GetServiceThatDoesntExist) {
diff --git a/libs/binder/tests/parcel_fuzzer/Android.bp b/libs/binder/tests/parcel_fuzzer/Android.bp
index 57d496d..e5d32da 100644
--- a/libs/binder/tests/parcel_fuzzer/Android.bp
+++ b/libs/binder/tests/parcel_fuzzer/Android.bp
@@ -29,7 +29,6 @@
         "libcutils",
         "libhidlbase",
         "liblog",
-        "libutils",
     ],
 
     target: {
@@ -37,12 +36,14 @@
             shared_libs: [
                 "libbinder_ndk",
                 "libbinder",
+                "libutils",
             ],
         },
         host: {
             static_libs: [
                 "libbinder_ndk",
                 "libbinder",
+                "libutils",
             ],
         },
         darwin: {
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index f7cd5c4..502031c 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -16,8 +16,8 @@
 
 #define LOG_TAG "LayerState"
 
-#include <apex/window.h>
-#include <inttypes.h>
+#include <cinttypes>
+#include <cmath>
 
 #include <android/native_window.h>
 #include <binder/Parcel.h>
@@ -25,10 +25,9 @@
 #include <gui/ISurfaceComposerClient.h>
 #include <gui/LayerState.h>
 #include <private/gui/ParcelUtils.h>
+#include <system/window.h>
 #include <utils/Errors.h>
 
-#include <cmath>
-
 namespace android {
 
 using gui::FocusRequest;
@@ -679,7 +678,9 @@
 
     if (compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT &&
         compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE &&
-        (!privileged || compatibility != ANATIVEWINDOW_FRAME_RATE_EXACT)) {
+        (!privileged ||
+         (compatibility != ANATIVEWINDOW_FRAME_RATE_EXACT &&
+          compatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE))) {
         ALOGE("%s failed - invalid compatibility value %d privileged: %s", functionName,
               compatibility, privileged ? "yes" : "no");
         return false;
diff --git a/libs/input/android/os/InputEventInjectionResult.aidl b/libs/input/android/os/InputEventInjectionResult.aidl
index 3bc7068..34f10ec 100644
--- a/libs/input/android/os/InputEventInjectionResult.aidl
+++ b/libs/input/android/os/InputEventInjectionResult.aidl
@@ -29,8 +29,9 @@
     /* Injection succeeded. */
     SUCCEEDED = 0,
 
-    /* Injection failed because the injected event did not target the appropriate window. */
-    TARGET_MISMATCH = 1,
+    /* Injection failed because the injector did not have permission to inject
+     * into the application with input focus. */
+    PERMISSION_DENIED = 1,
 
     /* Injection failed because there were no available input targets. */
     FAILED = 2,
diff --git a/libs/nativewindow/include/apex/window.h b/libs/nativewindow/include/apex/window.h
index 0923438..2d1354c 100644
--- a/libs/nativewindow/include/apex/window.h
+++ b/libs/nativewindow/include/apex/window.h
@@ -39,19 +39,6 @@
     // clang-format on
 };
 
-/*
- * Internal extension of compatibility value for ANativeWindow_setFrameRate. */
-enum ANativeWindow_FrameRateCompatibilityInternal {
-    /**
-     * This surface belongs to an app on the High Refresh Rate Deny list, and needs the display
-     * to operate at the exact frame rate.
-     *
-     * This is used internally by the platform and should not be used by apps.
-     * @hide
-     */
-    ANATIVEWINDOW_FRAME_RATE_EXACT = 100,
-};
-
 /**
  * Prototype of the function that an ANativeWindow implementation would call
  * when ANativeWindow_cancelBuffer is called.
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index a319769..a54af1f 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -1018,6 +1018,24 @@
     return window->perform(window, NATIVE_WINDOW_SET_AUTO_PREROTATION, autoPrerotation);
 }
 
+/*
+ * Internal extension of ANativeWindow_FrameRateCompatibility.
+ */
+enum {
+    /**
+     * This surface belongs to an app on the High Refresh Rate Deny list, and needs the display
+     * to operate at the exact frame rate.
+     *
+     * Keep in sync with Surface.java constant.
+     */
+    ANATIVEWINDOW_FRAME_RATE_EXACT = 100,
+
+    /**
+     * This surface is ignored while choosing the refresh rate.
+     */
+    ANATIVEWINDOW_FRAME_RATE_NO_VOTE,
+};
+
 static inline int native_window_set_frame_rate(struct ANativeWindow* window, float frameRate,
                                         int8_t compatibility, int8_t changeFrameRateStrategy) {
     return window->perform(window, NATIVE_WINDOW_SET_FRAME_RATE, (double)frameRate,
diff --git a/libs/renderengine/skia/ColorSpaces.cpp b/libs/renderengine/skia/ColorSpaces.cpp
index f367a84..37ff5df 100644
--- a/libs/renderengine/skia/ColorSpaces.cpp
+++ b/libs/renderengine/skia/ColorSpaces.cpp
@@ -65,7 +65,15 @@
         case HAL_DATASPACE_TRANSFER_SMPTE_170M:
             return SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, gamut);
         case HAL_DATASPACE_TRANSFER_HLG:
-            return SkColorSpace::MakeRGB(SkNamedTransferFn::kHLG, gamut);
+            // return HLG transfer but scale by 1/12
+            skcms_TransferFunction hlgFn;
+            if (skcms_TransferFunction_makeScaledHLGish(&hlgFn, 1.f / 12.f, 2.f, 2.f,
+                                                        1.f / 0.17883277f, 0.28466892f,
+                                                        0.55991073f)) {
+                return SkColorSpace::MakeRGB(hlgFn, gamut);
+            } else {
+                return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut);
+            }
         case HAL_DATASPACE_TRANSFER_UNSPECIFIED:
         default:
             return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut);
diff --git a/libs/ui/Gralloc4.cpp b/libs/ui/Gralloc4.cpp
index 1fce31d..4f950b8 100644
--- a/libs/ui/Gralloc4.cpp
+++ b/libs/ui/Gralloc4.cpp
@@ -1193,10 +1193,6 @@
 
 Gralloc4Allocator::Gralloc4Allocator(const Gralloc4Mapper& mapper) : mMapper(mapper) {
     mAllocator = IAllocator::getService();
-    if (mAllocator == nullptr) {
-        ALOGW("allocator 4.x is not supported");
-        return;
-    }
     if (__builtin_available(android 31, *)) {
         if (hasIAllocatorAidl()) {
             mAidlAllocator = AidlIAllocator::fromBinder(ndk::SpAIBinder(
@@ -1204,10 +1200,14 @@
             ALOGE_IF(!mAidlAllocator, "AIDL IAllocator declared but failed to get service");
         }
     }
+    if (mAllocator == nullptr && mAidlAllocator == nullptr) {
+        ALOGW("allocator 4.x is not supported");
+        return;
+    }
 }
 
 bool Gralloc4Allocator::isLoaded() const {
-    return mAllocator != nullptr;
+    return mAllocator != nullptr || mAidlAllocator != nullptr;
 }
 
 std::string Gralloc4Allocator::dumpDebugInfo(bool less) const {
diff --git a/libs/ui/tests/Android.bp b/libs/ui/tests/Android.bp
index 22fbf45..831b64d 100644
--- a/libs/ui/tests/Android.bp
+++ b/libs/ui/tests/Android.bp
@@ -114,7 +114,6 @@
     ],
     shared_libs: [
         "libbinder",
-        "libgui",
         "liblog",
         "libui",
         "libutils",
diff --git a/libs/ui/tests/GraphicBufferOverBinder_test.cpp b/libs/ui/tests/GraphicBufferOverBinder_test.cpp
index 126a945..4c9d574 100644
--- a/libs/ui/tests/GraphicBufferOverBinder_test.cpp
+++ b/libs/ui/tests/GraphicBufferOverBinder_test.cpp
@@ -20,9 +20,6 @@
 #include <binder/Parcel.h>
 #include <binder/ProcessState.h>
 #include <gtest/gtest.h>
-#include <gui/BufferQueue.h>
-#include <gui/IGraphicBufferConsumer.h>
-#include <gui/IGraphicBufferProducer.h>
 #include <ui/GraphicBuffer.h>
 #include <utils/Log.h>
 
diff --git a/services/gpuservice/gpuwork/bpfprogs/gpu_work.c b/services/gpuservice/gpuwork/bpfprogs/gpu_work.c
index c8e79a2..95cfae7 100644
--- a/services/gpuservice/gpuwork/bpfprogs/gpu_work.c
+++ b/services/gpuservice/gpuwork/bpfprogs/gpu_work.c
@@ -71,11 +71,14 @@
 //
 // The |total_active_duration_ns| must be set to the approximate total amount of
 // time the GPU spent running work for |uid| within the period, without
-// "double-counting" parallel GPU work on the same GPU for the same |uid|. "GPU
-// work" should correspond to the "GPU slices" shown in the AGI (Android GPU
+// "double-counting" parallel GPU work on the same GPU for the same |uid|. Note
+// that even if the parallel GPU work was submitted from several different
+// processes (i.e. different PIDs) with the same UID, this overlapping work must
+// not be double-counted, as it still came from a single |uid|. "GPU work"
+// should correspond to the "GPU slices" shown in the AGI (Android GPU
 // Inspector) tool, and so should include work such as fragment and non-fragment
 // work/shaders running on the shader cores of the GPU. For example, given the
-// following:
+// following for a single |uid|:
 //  - A period has:
 //    - |start_time_ns|: 100,000,000 ns
 //    - |end_time_ns|:   800,000,000 ns
@@ -99,16 +102,20 @@
 //  - from 600,000,000 ns to 700,000,000 ns, giving a duration of 100,000,000 ns
 //    (GPU work D)
 //
-// Thus, the |total_active_duration_ns| is the sum of the (non-overlapping)
-// durations. Drivers may not have efficient access to the exact start and end
-// times of all GPU work, as shown above, but drivers should try to
-// approximate/aggregate the value of |total_active_duration_ns| as accurately
-// as possible within the limitations of the hardware, without double-counting
-// parallel GPU work for the same |uid|. The |total_active_duration_ns| value
-// must be less than or equal to the period duration (|end_time_ns| -
-// |start_time_ns|); if the aggregation approach might violate this requirement
-// then the driver must clamp |total_active_duration_ns| to be at most the
-// period duration.
+// Thus, the |total_active_duration_ns| is the sum of these two
+// (non-overlapping) durations. Drivers may not have efficient access to the
+// exact start and end times of all GPU work, as shown above, but drivers should
+// try to approximate/aggregate the value of |total_active_duration_ns| as
+// accurately as possible within the limitations of the hardware, without
+// double-counting parallel GPU work for the same |uid|. The
+// |total_active_duration_ns| value must be less than or equal to the period
+// duration (|end_time_ns| - |start_time_ns|); if the aggregation approach might
+// violate this requirement then the driver must clamp
+// |total_active_duration_ns| to be at most the period duration.
+//
+// Protected mode: protected GPU work must not be reported. Periods must be
+// emitted, and the |total_active_duration_ns| value set, as if the protected
+// GPU work did not occur.
 //
 // Note that the above description allows for a certain amount of flexibility in
 // how the driver tracks periods and emits the events. We list a few examples of
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index a2e60c4..32eec29 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -31,11 +31,11 @@
 namespace android::inputdispatcher {
 
 // An arbitrary device id.
-constexpr int32_t DEVICE_ID = 1;
+static const int32_t DEVICE_ID = 1;
 
-// The default pid and uid for windows created by the test.
-constexpr int32_t WINDOW_PID = 999;
-constexpr int32_t WINDOW_UID = 1001;
+// An arbitrary injector pid / uid pair that has permission to inject events.
+static const int32_t INJECTOR_PID = 999;
+static const int32_t INJECTOR_UID = 1001;
 
 static constexpr std::chrono::duration INJECT_EVENT_TIMEOUT = 5s;
 static constexpr std::chrono::nanoseconds DISPATCHING_TIMEOUT = 100ms;
@@ -108,6 +108,8 @@
 
     void pokeUserActivity(nsecs_t, int32_t, int32_t) override {}
 
+    bool checkInjectEventsPermissionNonReentrant(int32_t, int32_t) override { return false; }
+
     void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override {}
 
     void setPointerCapture(const PointerCaptureRequest&) override {}
@@ -194,8 +196,8 @@
         mInfo.globalScaleFactor = 1.0;
         mInfo.touchableRegion.clear();
         mInfo.addTouchableRegion(mFrame);
-        mInfo.ownerPid = WINDOW_PID;
-        mInfo.ownerUid = WINDOW_UID;
+        mInfo.ownerPid = INJECTOR_PID;
+        mInfo.ownerUid = INJECTOR_UID;
         mInfo.displayId = ADISPLAY_ID_DEFAULT;
     }
 
@@ -308,14 +310,14 @@
     for (auto _ : state) {
         MotionEvent event = generateMotionEvent();
         // Send ACTION_DOWN
-        dispatcher.injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
-                                    INJECT_EVENT_TIMEOUT,
+        dispatcher.injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
+                                    InputEventInjectionSync::NONE, INJECT_EVENT_TIMEOUT,
                                     POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
 
         // Send ACTION_UP
         event.setAction(AMOTION_EVENT_ACTION_UP);
-        dispatcher.injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
-                                    INJECT_EVENT_TIMEOUT,
+        dispatcher.injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
+                                    InputEventInjectionSync::NONE, INJECT_EVENT_TIMEOUT,
                                     POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
 
         window->consumeEvent();
diff --git a/services/inputflinger/dispatcher/InjectionState.cpp b/services/inputflinger/dispatcher/InjectionState.cpp
index c2d3ad6..c8024a6 100644
--- a/services/inputflinger/dispatcher/InjectionState.cpp
+++ b/services/inputflinger/dispatcher/InjectionState.cpp
@@ -20,9 +20,10 @@
 
 namespace android::inputdispatcher {
 
-InjectionState::InjectionState(const std::optional<int32_t>& targetUid)
+InjectionState::InjectionState(int32_t injectorPid, int32_t injectorUid)
       : refCount(1),
-        targetUid(targetUid),
+        injectorPid(injectorPid),
+        injectorUid(injectorUid),
         injectionResult(android::os::InputEventInjectionResult::PENDING),
         injectionIsAsync(false),
         pendingForegroundDispatches(0) {}
diff --git a/services/inputflinger/dispatcher/InjectionState.h b/services/inputflinger/dispatcher/InjectionState.h
index 90cf150..0bfafb1 100644
--- a/services/inputflinger/dispatcher/InjectionState.h
+++ b/services/inputflinger/dispatcher/InjectionState.h
@@ -27,12 +27,13 @@
 struct InjectionState {
     mutable int32_t refCount;
 
-    std::optional<int32_t> targetUid;
+    int32_t injectorPid;
+    int32_t injectorUid;
     android::os::InputEventInjectionResult injectionResult; // initially PENDING
     bool injectionIsAsync;               // set to true if injection is not waiting for the result
     int32_t pendingForegroundDispatches; // the number of foreground dispatches in progress
 
-    explicit InjectionState(const std::optional<int32_t>& targetUid);
+    InjectionState(int32_t injectorPid, int32_t injectorUid);
     void release();
 
 private:
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index f3d0b65..1cc4589 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -578,27 +578,6 @@
     return false;
 }
 
-// Checks targeted injection using the window's owner's uid.
-// Returns an empty string if an entry can be sent to the given window, or an error message if the
-// entry is a targeted injection whose uid target doesn't match the window owner.
-std::optional<std::string> verifyTargetedInjection(const sp<WindowInfoHandle>& window,
-                                                   const EventEntry& entry) {
-    if (entry.injectionState == nullptr || !entry.injectionState->targetUid) {
-        // The event was not injected, or the injected event does not target a window.
-        return {};
-    }
-    const int32_t uid = *entry.injectionState->targetUid;
-    if (window == nullptr) {
-        return StringPrintf("No valid window target for injection into uid %d.", uid);
-    }
-    if (entry.injectionState->targetUid != window->getInfo()->ownerUid) {
-        return StringPrintf("Injected event targeted at uid %d would be dispatched to window '%s' "
-                            "owned by uid %d.",
-                            uid, window->getName().c_str(), window->getInfo()->ownerUid);
-    }
-    return {};
-}
-
 } // namespace
 
 // --- InputDispatcher ---
@@ -1057,8 +1036,6 @@
 
     switch (entry.type) {
         case EventEntry::Type::KEY: {
-            LOG_ALWAYS_FATAL_IF((entry.policyFlags & POLICY_FLAG_TRUSTED) == 0,
-                                "Unexpected untrusted event.");
             // Optimize app switch latency.
             // If the application takes too long to catch up then we drop all events preceding
             // the app switch key.
@@ -1096,8 +1073,6 @@
         }
 
         case EventEntry::Type::MOTION: {
-            LOG_ALWAYS_FATAL_IF((entry.policyFlags & POLICY_FLAG_TRUSTED) == 0,
-                                "Unexpected untrusted event.");
             if (shouldPruneInboundQueueLocked(static_cast<MotionEntry&>(entry))) {
                 mNextUnblockedEvent = mInboundQueue.back();
                 needWake = true;
@@ -1743,7 +1718,8 @@
     }
 
     setInjectionResult(*entry, injectionResult);
-    if (injectionResult == InputEventInjectionResult::TARGET_MISMATCH) {
+    if (injectionResult == InputEventInjectionResult::PERMISSION_DENIED) {
+        ALOGW("Permission denied, dropping the motion (isPointer=%s)", toString(isPointerEvent));
         return true;
     }
     if (injectionResult != InputEventInjectionResult::SUCCEEDED) {
@@ -2000,10 +1976,9 @@
     // we have a valid, non-null focused window
     resetNoFocusedWindowTimeoutLocked();
 
-    // Verify targeted injection.
-    if (const auto err = verifyTargetedInjection(focusedWindowHandle, entry); err) {
-        ALOGW("Dropping injected event: %s", (*err).c_str());
-        return InputEventInjectionResult::TARGET_MISMATCH;
+    // Check permissions.
+    if (!checkInjectionPermission(focusedWindowHandle, entry.injectionState)) {
+        return InputEventInjectionResult::PERMISSION_DENIED;
     }
 
     if (focusedWindowHandle->getInfo()->inputConfig.test(
@@ -2069,6 +2044,11 @@
         nsecs_t currentTime, const MotionEntry& entry, std::vector<InputTarget>& inputTargets,
         nsecs_t* nextWakeupTime, bool* outConflictingPointerActions) {
     ATRACE_CALL();
+    enum InjectionPermission {
+        INJECTION_PERMISSION_UNKNOWN,
+        INJECTION_PERMISSION_GRANTED,
+        INJECTION_PERMISSION_DENIED
+    };
 
     // For security reasons, we defer updating the touch state until we are sure that
     // event injection will be allowed.
@@ -2078,6 +2058,7 @@
 
     // Update the touch state as needed based on the properties of the touch event.
     InputEventInjectionResult injectionResult = InputEventInjectionResult::PENDING;
+    InjectionPermission injectionPermission = INJECTION_PERMISSION_UNKNOWN;
     sp<WindowInfoHandle> newHoverWindowHandle(mLastHoverWindowHandle);
     sp<WindowInfoHandle> newTouchedWindowHandle;
 
@@ -2126,7 +2107,7 @@
               "in display %" PRId32,
               displayId);
         // TODO: test multiple simultaneous input streams.
-        injectionResult = InputEventInjectionResult::FAILED;
+        injectionResult = InputEventInjectionResult::PERMISSION_DENIED;
         switchedDevice = false;
         wrongDevice = true;
         goto Failed;
@@ -2159,14 +2140,6 @@
             newTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle();
         }
 
-        // Verify targeted injection.
-        if (const auto err = verifyTargetedInjection(newTouchedWindowHandle, entry); err) {
-            ALOGW("Dropping injected touch event: %s", (*err).c_str());
-            injectionResult = os::InputEventInjectionResult::TARGET_MISMATCH;
-            newTouchedWindowHandle = nullptr;
-            goto Failed;
-        }
-
         // Figure out whether splitting will be allowed for this window.
         if (newTouchedWindowHandle != nullptr) {
             if (newTouchedWindowHandle->getInfo()->supportsSplitTouch()) {
@@ -2210,11 +2183,6 @@
         for (const sp<WindowInfoHandle>& windowHandle : newTouchedWindows) {
             const WindowInfo& info = *windowHandle->getInfo();
 
-            // Skip spy window targets that are not valid for targeted injection.
-            if (const auto err = verifyTargetedInjection(windowHandle, entry); err) {
-                continue;
-            }
-
             if (info.inputConfig.test(WindowInfo::InputConfig::PAUSE_DISPATCHING)) {
                 ALOGI("Not sending touch event to %s because it is paused",
                       windowHandle->getName().c_str());
@@ -2308,14 +2276,6 @@
             newTouchedWindowHandle =
                     findTouchedWindowAtLocked(displayId, x, y, &tempTouchState, isStylus);
 
-            // Verify targeted injection.
-            if (const auto err = verifyTargetedInjection(newTouchedWindowHandle, entry); err) {
-                ALOGW("Dropping injected event: %s", (*err).c_str());
-                injectionResult = os::InputEventInjectionResult::TARGET_MISMATCH;
-                newTouchedWindowHandle = nullptr;
-                goto Failed;
-            }
-
             // Drop touch events if requested by input feature
             if (newTouchedWindowHandle != nullptr &&
                 shouldDropInput(entry, newTouchedWindowHandle)) {
@@ -2407,26 +2367,19 @@
         goto Failed;
     }
 
-    // Ensure that all touched windows are valid for injection.
-    if (entry.injectionState != nullptr) {
-        std::string errs;
-        for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
-            if (touchedWindow.targetFlags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
-                // Allow ACTION_OUTSIDE events generated by targeted injection to be
-                // dispatched to any uid, since the coords will be zeroed out later.
-                continue;
-            }
-            const auto err = verifyTargetedInjection(touchedWindow.windowHandle, entry);
-            if (err) errs += "\n  - " + *err;
-        }
-        if (!errs.empty()) {
-            ALOGW("Dropping targeted injection: At least one touched window is not owned by uid "
-                  "%d:%s",
-                  *entry.injectionState->targetUid, errs.c_str());
-            injectionResult = InputEventInjectionResult::TARGET_MISMATCH;
-            goto Failed;
-        }
+    // Check permission to inject into all touched foreground windows.
+    if (std::any_of(tempTouchState.windows.begin(), tempTouchState.windows.end(),
+                    [this, &entry](const TouchedWindow& touchedWindow) {
+                        return (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) != 0 &&
+                                !checkInjectionPermission(touchedWindow.windowHandle,
+                                                          entry.injectionState);
+                    })) {
+        injectionResult = InputEventInjectionResult::PERMISSION_DENIED;
+        injectionPermission = INJECTION_PERMISSION_DENIED;
+        goto Failed;
     }
+    // Permission granted to inject into all touched foreground windows.
+    injectionPermission = INJECTION_PERMISSION_GRANTED;
 
     // Check whether windows listening for outside touches are owned by the same UID. If it is
     // set the policy flag that we will not reveal coordinate information to this window.
@@ -2492,6 +2445,19 @@
     tempTouchState.filterNonAsIsTouchWindows();
 
 Failed:
+    // Check injection permission once and for all.
+    if (injectionPermission == INJECTION_PERMISSION_UNKNOWN) {
+        if (checkInjectionPermission(nullptr, entry.injectionState)) {
+            injectionPermission = INJECTION_PERMISSION_GRANTED;
+        } else {
+            injectionPermission = INJECTION_PERMISSION_DENIED;
+        }
+    }
+
+    if (injectionPermission != INJECTION_PERMISSION_GRANTED) {
+        return injectionResult;
+    }
+
     // Update final pieces of touch state if the injector had permission.
     if (!wrongDevice) {
         if (switchedDevice) {
@@ -2689,6 +2655,26 @@
     }
 }
 
+bool InputDispatcher::checkInjectionPermission(const sp<WindowInfoHandle>& windowHandle,
+                                               const InjectionState* injectionState) {
+    if (injectionState &&
+        (windowHandle == nullptr ||
+         windowHandle->getInfo()->ownerUid != injectionState->injectorUid) &&
+        !hasInjectionPermission(injectionState->injectorPid, injectionState->injectorUid)) {
+        if (windowHandle != nullptr) {
+            ALOGW("Permission denied: injecting event from pid %d uid %d to window %s "
+                  "owned by uid %d",
+                  injectionState->injectorPid, injectionState->injectorUid,
+                  windowHandle->getName().c_str(), windowHandle->getInfo()->ownerUid);
+        } else {
+            ALOGW("Permission denied: injecting event from pid %d uid %d",
+                  injectionState->injectorPid, injectionState->injectorUid);
+        }
+        return false;
+    }
+    return true;
+}
+
 /**
  * Indicate whether one window handle should be considered as obscuring
  * another window handle. We only check a few preconditions. Actually
@@ -4207,20 +4193,20 @@
     }
 }
 
-InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* event,
-                                                            std::optional<int32_t> targetUid,
-                                                            InputEventInjectionSync syncMode,
-                                                            std::chrono::milliseconds timeout,
-                                                            uint32_t policyFlags) {
+InputEventInjectionResult InputDispatcher::injectInputEvent(
+        const InputEvent* event, int32_t injectorPid, int32_t injectorUid,
+        InputEventInjectionSync syncMode, std::chrono::milliseconds timeout, uint32_t policyFlags) {
     if (DEBUG_INBOUND_EVENT_DETAILS) {
-        ALOGD("injectInputEvent - eventType=%d, targetUid=%s, syncMode=%d, timeout=%lld, "
-              "policyFlags=0x%08x",
-              event->getType(), targetUid ? std::to_string(*targetUid).c_str() : "none", syncMode,
-              timeout.count(), policyFlags);
+        ALOGD("injectInputEvent - eventType=%d, injectorPid=%d, injectorUid=%d, "
+              "syncMode=%d, timeout=%lld, policyFlags=0x%08x",
+              event->getType(), injectorPid, injectorUid, syncMode, timeout.count(), policyFlags);
     }
     nsecs_t endTime = now() + std::chrono::duration_cast<std::chrono::nanoseconds>(timeout).count();
 
-    policyFlags |= POLICY_FLAG_INJECTED | POLICY_FLAG_TRUSTED;
+    policyFlags |= POLICY_FLAG_INJECTED;
+    if (hasInjectionPermission(injectorPid, injectorUid)) {
+        policyFlags |= POLICY_FLAG_TRUSTED;
+    }
 
     // For all injected events, set device id = VIRTUAL_KEYBOARD_ID. The only exception is events
     // that have gone through the InputFilter. If the event passed through the InputFilter, assign
@@ -4361,7 +4347,7 @@
             return InputEventInjectionResult::FAILED;
     }
 
-    InjectionState* injectionState = new InjectionState(targetUid);
+    InjectionState* injectionState = new InjectionState(injectorPid, injectorUid);
     if (syncMode == InputEventInjectionSync::NONE) {
         injectionState->injectionIsAsync = true;
     }
@@ -4433,7 +4419,8 @@
     } // release lock
 
     if (DEBUG_INJECTION) {
-        ALOGD("injectInputEvent - Finished with result %d.", injectionResult);
+        ALOGD("injectInputEvent - Finished with result %d. injectorPid=%d, injectorUid=%d",
+              injectionResult, injectorPid, injectorUid);
     }
 
     return injectionResult;
@@ -4472,12 +4459,19 @@
     return result;
 }
 
+bool InputDispatcher::hasInjectionPermission(int32_t injectorPid, int32_t injectorUid) {
+    return injectorUid == 0 ||
+            mPolicy->checkInjectEventsPermissionNonReentrant(injectorPid, injectorUid);
+}
+
 void InputDispatcher::setInjectionResult(EventEntry& entry,
                                          InputEventInjectionResult injectionResult) {
     InjectionState* injectionState = entry.injectionState;
     if (injectionState) {
         if (DEBUG_INJECTION) {
-            ALOGD("Setting input event injection result to %d.", injectionResult);
+            ALOGD("Setting input event injection result to %d.  "
+                  "injectorPid=%d, injectorUid=%d",
+                  injectionResult, injectionState->injectorPid, injectionState->injectorUid);
         }
 
         if (injectionState->injectionIsAsync && !(entry.policyFlags & POLICY_FLAG_FILTERED)) {
@@ -4486,12 +4480,12 @@
                 case InputEventInjectionResult::SUCCEEDED:
                     ALOGV("Asynchronous input event injection succeeded.");
                     break;
-                case InputEventInjectionResult::TARGET_MISMATCH:
-                    ALOGV("Asynchronous input event injection target mismatch.");
-                    break;
                 case InputEventInjectionResult::FAILED:
                     ALOGW("Asynchronous input event injection failed.");
                     break;
+                case InputEventInjectionResult::PERMISSION_DENIED:
+                    ALOGW("Asynchronous input event injection permission denied.");
+                    break;
                 case InputEventInjectionResult::TIMED_OUT:
                     ALOGW("Asynchronous input event injection timed out.");
                     break;
@@ -5146,28 +5140,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..f3dac19 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -104,7 +104,7 @@
     void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override;
 
     android::os::InputEventInjectionResult injectInputEvent(
-            const InputEvent* event, std::optional<int32_t> targetUid,
+            const InputEvent* event, int32_t injectorPid, int32_t injectorUid,
             android::os::InputEventInjectionSync syncMode, std::chrono::milliseconds timeout,
             uint32_t policyFlags) override;
 
@@ -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);
 
@@ -275,6 +278,7 @@
 
     // Event injection and synchronization.
     std::condition_variable mInjectionResultAvailable;
+    bool hasInjectionPermission(int32_t injectorPid, int32_t injectorUid);
     void setInjectionResult(EventEntry& entry,
                             android::os::InputEventInjectionResult injectionResult);
     void transformMotionEntryForInjectionLocked(MotionEntry&,
@@ -550,6 +554,8 @@
     void addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets, int32_t displayId)
             REQUIRES(mLock);
     void pokeUserActivityLocked(const EventEntry& eventEntry) REQUIRES(mLock);
+    bool checkInjectionPermission(const sp<android::gui::WindowInfoHandle>& windowHandle,
+                                  const InjectionState* injectionState);
     // Enqueue a drag event if needed, and update the touch state.
     // Uses findTouchedWindowTargetsLocked to make the decision
     void addDragEventLocked(const MotionEntry& entry) REQUIRES(mLock);
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index 100bd18..d7bc5fb 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -68,16 +68,10 @@
      * input injection to proceed.
      * Returns one of the INPUT_EVENT_INJECTION_XXX constants.
      *
-     * If a targetUid is provided, InputDispatcher will only consider injecting the input event into
-     * windows owned by the provided uid. If the input event is targeted at a window that is not
-     * owned by the provided uid, input injection will fail. If no targetUid is provided, the input
-     * event will be dispatched as-is.
-     *
-     * This method may be called on any thread (usually by the input manager). The caller must
-     * perform all necessary permission checks prior to injecting events.
+     * This method may be called on any thread (usually by the input manager).
      */
     virtual android::os::InputEventInjectionResult injectInputEvent(
-            const InputEvent* event, std::optional<int32_t> targetUid,
+            const InputEvent* event, int32_t injectorPid, int32_t injectorUid,
             android::os::InputEventInjectionSync syncMode, std::chrono::milliseconds timeout,
             uint32_t policyFlags) = 0;
 
@@ -161,7 +155,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/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
index 575b3d7..de0b6da 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -125,6 +125,15 @@
     /* Poke user activity for an event dispatched to a window. */
     virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) = 0;
 
+    /* Checks whether a given application pid/uid has permission to inject input events
+     * into other applications.
+     *
+     * This method is special in that its implementation promises to be non-reentrant and
+     * is safe to call while holding other locks.  (Most other methods make no such guarantees!)
+     */
+    virtual bool checkInjectEventsPermissionNonReentrant(int32_t injectorPid,
+                                                         int32_t injectorUid) = 0;
+
     /* Notifies the policy that a pointer down event has occurred outside the current focused
      * window.
      *
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index dc5fcec..a9a4c71 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -27,6 +27,9 @@
 
 namespace android {
 
+// The default velocity control parameters that has no effect.
+static const VelocityControlParameters FLAT_VELOCITY_CONTROL_PARAMS{};
+
 // --- CursorMotionAccumulator ---
 
 CursorMotionAccumulator::CursorMotionAccumulator() {
@@ -154,8 +157,9 @@
         mHWheelScale = 1.0f;
     }
 
-    if ((!changes && config->pointerCaptureRequest.enable) ||
-        (changes & InputReaderConfiguration::CHANGE_POINTER_CAPTURE)) {
+    const bool configurePointerCapture = (!changes && config->pointerCaptureRequest.enable) ||
+            (changes & InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
+    if (configurePointerCapture) {
         if (config->pointerCaptureRequest.enable) {
             if (mParameters.mode == Parameters::MODE_POINTER) {
                 mParameters.mode = Parameters::MODE_POINTER_RELATIVE;
@@ -180,10 +184,18 @@
         }
     }
 
-    if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED)) {
-        mPointerVelocityControl.setParameters(config->pointerVelocityControlParameters);
-        mWheelXVelocityControl.setParameters(config->wheelVelocityControlParameters);
-        mWheelYVelocityControl.setParameters(config->wheelVelocityControlParameters);
+    if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED) ||
+        configurePointerCapture) {
+        if (config->pointerCaptureRequest.enable) {
+            // Disable any acceleration or scaling when Pointer Capture is enabled.
+            mPointerVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
+            mWheelXVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
+            mWheelYVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
+        } else {
+            mPointerVelocityControl.setParameters(config->pointerVelocityControlParameters);
+            mWheelXVelocityControl.setParameters(config->wheelVelocityControlParameters);
+            mWheelYVelocityControl.setParameters(config->wheelVelocityControlParameters);
+        }
     }
 
     if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index 1d63c0e..2ac8178 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -428,6 +428,8 @@
 }
 
 void KeyboardInputMapper::updateLedState(bool reset) {
+    // Clear the local led state then union the global led state.
+    mMetaState &= ~(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON | AMETA_SCROLL_LOCK_ON);
     mMetaState |= getContext()->getLedMetaState();
 
     constexpr int32_t META_NUM = 3;
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index f15ed9e..a167271 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -45,10 +45,10 @@
 using namespace ftl::flag_operators;
 
 // An arbitrary time value.
-static constexpr nsecs_t ARBITRARY_TIME = 1234;
+static const nsecs_t ARBITRARY_TIME = 1234;
 
 // An arbitrary device id.
-static constexpr int32_t DEVICE_ID = 1;
+static const int32_t DEVICE_ID = 1;
 
 // An arbitrary display id.
 static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
@@ -61,12 +61,9 @@
 static constexpr int32_t POINTER_1_UP =
         AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 
-// The default pid and uid for windows created by the test.
-static constexpr int32_t WINDOW_PID = 999;
-static constexpr int32_t WINDOW_UID = 1001;
-
-// The default policy flags to use for event injection by tests.
-static constexpr uint32_t DEFAULT_POLICY_FLAGS = POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER;
+// An arbitrary injector pid / uid pair that has permission to inject events.
+static const int32_t INJECTOR_PID = 999;
+static const int32_t INJECTOR_UID = 1001;
 
 // An arbitrary pid of the gesture monitor window
 static constexpr int32_t MONITOR_PID = 2001;
@@ -475,6 +472,10 @@
 
     void pokeUserActivity(nsecs_t, int32_t, int32_t) override {}
 
+    bool checkInjectEventsPermissionNonReentrant(int32_t pid, int32_t uid) override {
+        return pid == INJECTOR_PID && uid == INJECTOR_UID;
+    }
+
     void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override {
         std::scoped_lock lock(mLock);
         mOnPointerDownToken = newToken;
@@ -559,8 +560,8 @@
                      /*action*/ -1, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME,
                      ARBITRARY_TIME);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
-                                            0ms, 0))
+              mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
+                                            InputEventInjectionSync::NONE, 0ms, 0))
             << "Should reject key events with undefined action.";
 
     // Rejects ACTION_MULTIPLE since it is not supported despite being defined in the API.
@@ -568,8 +569,8 @@
                      INVALID_HMAC, AKEY_EVENT_ACTION_MULTIPLE, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0,
                      ARBITRARY_TIME, ARBITRARY_TIME);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
-                                            0ms, 0))
+              mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
+                                            InputEventInjectionSync::NONE, 0ms, 0))
             << "Should reject key events with ACTION_MULTIPLE.";
 }
 
@@ -598,8 +599,8 @@
                      ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
-                                            0ms, 0))
+              mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
+                                            InputEventInjectionSync::NONE, 0ms, 0))
             << "Should reject motion events with undefined action.";
 
     // Rejects pointer down with invalid index.
@@ -610,8 +611,8 @@
                      ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
-                                            0ms, 0))
+              mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
+                                            InputEventInjectionSync::NONE, 0ms, 0))
             << "Should reject motion events with pointer down index too large.";
 
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
@@ -622,8 +623,8 @@
                      identityTransform, ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
-                                            0ms, 0))
+              mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
+                                            InputEventInjectionSync::NONE, 0ms, 0))
             << "Should reject motion events with pointer down index too small.";
 
     // Rejects pointer up with invalid index.
@@ -634,8 +635,8 @@
                      ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
-                                            0ms, 0))
+              mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
+                                            InputEventInjectionSync::NONE, 0ms, 0))
             << "Should reject motion events with pointer up index too large.";
 
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
@@ -646,8 +647,8 @@
                      identityTransform, ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
-                                            0ms, 0))
+              mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
+                                            InputEventInjectionSync::NONE, 0ms, 0))
             << "Should reject motion events with pointer up index too small.";
 
     // Rejects motion events with invalid number of pointers.
@@ -658,8 +659,8 @@
                      ARBITRARY_TIME,
                      /*pointerCount*/ 0, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
-                                            0ms, 0))
+              mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
+                                            InputEventInjectionSync::NONE, 0ms, 0))
             << "Should reject motion events with 0 pointers.";
 
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
@@ -669,8 +670,8 @@
                      ARBITRARY_TIME,
                      /*pointerCount*/ MAX_POINTERS + 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
-                                            0ms, 0))
+              mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
+                                            InputEventInjectionSync::NONE, 0ms, 0))
             << "Should reject motion events with more than MAX_POINTERS pointers.";
 
     // Rejects motion events with invalid pointer ids.
@@ -682,8 +683,8 @@
                      ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
-                                            0ms, 0))
+              mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
+                                            InputEventInjectionSync::NONE, 0ms, 0))
             << "Should reject motion events with pointer ids less than 0.";
 
     pointerProperties[0].id = MAX_POINTER_ID + 1;
@@ -694,8 +695,8 @@
                      ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
-                                            0ms, 0))
+              mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
+                                            InputEventInjectionSync::NONE, 0ms, 0))
             << "Should reject motion events with pointer ids greater than MAX_POINTER_ID.";
 
     // Rejects motion events with duplicate pointer ids.
@@ -708,8 +709,8 @@
                      ARBITRARY_TIME,
                      /*pointerCount*/ 2, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              mDispatcher->injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
-                                            0ms, 0))
+              mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
+                                            InputEventInjectionSync::NONE, 0ms, 0))
             << "Should reject motion events with duplicate pointer ids.";
 }
 
@@ -1012,8 +1013,8 @@
         mInfo.globalScaleFactor = 1.0;
         mInfo.touchableRegion.clear();
         mInfo.addTouchableRegion(Rect(0, 0, WIDTH, HEIGHT));
-        mInfo.ownerPid = WINDOW_PID;
-        mInfo.ownerUid = WINDOW_UID;
+        mInfo.ownerPid = INJECTOR_PID;
+        mInfo.ownerUid = INJECTOR_UID;
         mInfo.displayId = displayId;
         mInfo.inputConfig = WindowInfo::InputConfig::DEFAULT;
     }
@@ -1295,8 +1296,7 @@
         int32_t displayId = ADISPLAY_ID_NONE,
         InputEventInjectionSync syncMode = InputEventInjectionSync::WAIT_FOR_RESULT,
         std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT,
-        bool allowKeyRepeat = true, std::optional<int32_t> targetUid = {},
-        uint32_t policyFlags = DEFAULT_POLICY_FLAGS) {
+        bool allowKeyRepeat = true) {
     KeyEvent event;
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
 
@@ -1305,11 +1305,13 @@
                      INVALID_HMAC, action, /* flags */ 0, AKEYCODE_A, KEY_A, AMETA_NONE,
                      repeatCount, currentTime, currentTime);
 
+    int32_t policyFlags = POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER;
     if (!allowKeyRepeat) {
         policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT;
     }
     // Inject event until dispatch out.
-    return dispatcher->injectInputEvent(&event, targetUid, syncMode, injectionTimeout, policyFlags);
+    return dispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, syncMode,
+                                        injectionTimeout, policyFlags);
 }
 
 static InputEventInjectionResult injectKeyDown(const std::unique_ptr<InputDispatcher>& dispatcher,
@@ -1452,10 +1454,10 @@
 static InputEventInjectionResult injectMotionEvent(
         const std::unique_ptr<InputDispatcher>& dispatcher, const MotionEvent& event,
         std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT,
-        InputEventInjectionSync injectionMode = InputEventInjectionSync::WAIT_FOR_RESULT,
-        std::optional<int32_t> targetUid = {}, uint32_t policyFlags = DEFAULT_POLICY_FLAGS) {
-    return dispatcher->injectInputEvent(&event, targetUid, injectionMode, injectionTimeout,
-                                        policyFlags);
+        InputEventInjectionSync injectionMode = InputEventInjectionSync::WAIT_FOR_RESULT) {
+    return dispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, injectionMode,
+                                        injectionTimeout,
+                                        POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
 }
 
 static InputEventInjectionResult injectMotionEvent(
@@ -1465,8 +1467,7 @@
                                         AMOTION_EVENT_INVALID_CURSOR_POSITION},
         std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT,
         InputEventInjectionSync injectionMode = InputEventInjectionSync::WAIT_FOR_RESULT,
-        nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC),
-        std::optional<int32_t> targetUid = {}, uint32_t policyFlags = DEFAULT_POLICY_FLAGS) {
+        nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC)) {
     MotionEvent event = MotionEventBuilder(action, source)
                                 .displayId(displayId)
                                 .eventTime(eventTime)
@@ -1478,8 +1479,7 @@
                                 .build();
 
     // Inject event until dispatch out.
-    return injectMotionEvent(dispatcher, event, injectionTimeout, injectionMode, targetUid,
-                             policyFlags);
+    return injectMotionEvent(dispatcher, event, injectionTimeout, injectionMode);
 }
 
 static InputEventInjectionResult injectMotionDown(
@@ -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);
@@ -3515,8 +3574,8 @@
  * FLAG_WINDOW_IS_PARTIALLY_OBSCURED.
  */
 TEST_F(InputDispatcherTest, SlipperyWindow_SetsFlagPartiallyObscured) {
-    constexpr int32_t SLIPPERY_PID = WINDOW_PID + 1;
-    constexpr int32_t SLIPPERY_UID = WINDOW_UID + 1;
+    constexpr int32_t SLIPPERY_PID = INJECTOR_PID + 1;
+    constexpr int32_t SLIPPERY_UID = INJECTOR_UID + 1;
 
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
@@ -4043,7 +4102,7 @@
         const int32_t additionalPolicyFlags =
                 POLICY_FLAG_PASS_TO_USER | POLICY_FLAG_DISABLE_KEY_REPEAT;
         ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-                  mDispatcher->injectInputEvent(&event, {} /*targetUid*/,
+                  mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
                                                 InputEventInjectionSync::WAIT_FOR_RESULT, 10ms,
                                                 policyFlags | additionalPolicyFlags));
 
@@ -4078,7 +4137,7 @@
 
         const int32_t additionalPolicyFlags = POLICY_FLAG_PASS_TO_USER;
         ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-                  mDispatcher->injectInputEvent(&event, {} /*targetUid*/,
+                  mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
                                                 InputEventInjectionSync::WAIT_FOR_RESULT, 10ms,
                                                 policyFlags | additionalPolicyFlags));
 
@@ -4585,7 +4644,7 @@
     const int32_t policyFlags = POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER;
 
     InputEventInjectionResult result =
-            mDispatcher->injectInputEvent(&event, {} /* targetUid */,
+            mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
                                           InputEventInjectionSync::WAIT_FOR_RESULT,
                                           INJECT_EVENT_TIMEOUT, policyFlags);
     ASSERT_EQ(InputEventInjectionResult::FAILED, result)
@@ -6385,8 +6444,8 @@
         mWindow->consumeFocusEvent(true);
 
         // Set initial touch mode to InputDispatcher::kDefaultInTouchMode.
-        if (mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, WINDOW_PID,
-                                        WINDOW_UID, /* hasPermission */ true)) {
+        if (mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, INJECTOR_PID,
+                                        INJECTOR_UID, /* hasPermission */ true)) {
             mWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode);
             mSecondWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode);
         }
@@ -7017,149 +7076,4 @@
     window->assertNoEvents();
 }
 
-struct User {
-    int32_t mPid;
-    int32_t mUid;
-    uint32_t mPolicyFlags{DEFAULT_POLICY_FLAGS};
-    std::unique_ptr<InputDispatcher>& mDispatcher;
-
-    User(std::unique_ptr<InputDispatcher>& dispatcher, int32_t pid, int32_t uid)
-          : mPid(pid), mUid(uid), mDispatcher(dispatcher) {}
-
-    InputEventInjectionResult injectTargetedMotion(int32_t action) const {
-        return injectMotionEvent(mDispatcher, action, AINPUT_SOURCE_TOUCHSCREEN,
-                                 ADISPLAY_ID_DEFAULT, {100, 200},
-                                 {AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                                  AMOTION_EVENT_INVALID_CURSOR_POSITION},
-                                 INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT,
-                                 systemTime(SYSTEM_TIME_MONOTONIC), {mUid}, mPolicyFlags);
-    }
-
-    InputEventInjectionResult injectTargetedKey(int32_t action) const {
-        return inputdispatcher::injectKey(mDispatcher, action, 0 /* repeatCount*/, ADISPLAY_ID_NONE,
-                                          InputEventInjectionSync::WAIT_FOR_RESULT,
-                                          INJECT_EVENT_TIMEOUT, false /*allowKeyRepeat*/, {mUid},
-                                          mPolicyFlags);
-    }
-
-    sp<FakeWindowHandle> createWindow() const {
-        std::shared_ptr<FakeApplicationHandle> overlayApplication =
-                std::make_shared<FakeApplicationHandle>();
-        sp<FakeWindowHandle> window = new FakeWindowHandle(overlayApplication, mDispatcher,
-                                                           "Owned Window", ADISPLAY_ID_DEFAULT);
-        window->setOwnerInfo(mPid, mUid);
-        return window;
-    }
-};
-
-using InputDispatcherTargetedInjectionTest = InputDispatcherTest;
-
-TEST_F(InputDispatcherTargetedInjectionTest, CanInjectIntoOwnedWindow) {
-    auto owner = User(mDispatcher, 10, 11);
-    auto window = owner.createWindow();
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
-
-    EXPECT_EQ(InputEventInjectionResult::SUCCEEDED,
-              owner.injectTargetedMotion(AMOTION_EVENT_ACTION_DOWN));
-    window->consumeMotionDown();
-
-    setFocusedWindow(window);
-    window->consumeFocusEvent(true);
-
-    EXPECT_EQ(InputEventInjectionResult::SUCCEEDED,
-              owner.injectTargetedKey(AKEY_EVENT_ACTION_DOWN));
-    window->consumeKeyDown(ADISPLAY_ID_NONE);
-}
-
-TEST_F(InputDispatcherTargetedInjectionTest, CannotInjectIntoUnownedWindow) {
-    auto owner = User(mDispatcher, 10, 11);
-    auto window = owner.createWindow();
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
-
-    auto rando = User(mDispatcher, 20, 21);
-    EXPECT_EQ(InputEventInjectionResult::TARGET_MISMATCH,
-              rando.injectTargetedMotion(AMOTION_EVENT_ACTION_DOWN));
-
-    setFocusedWindow(window);
-    window->consumeFocusEvent(true);
-
-    EXPECT_EQ(InputEventInjectionResult::TARGET_MISMATCH,
-              rando.injectTargetedKey(AKEY_EVENT_ACTION_DOWN));
-    window->assertNoEvents();
-}
-
-TEST_F(InputDispatcherTargetedInjectionTest, CanInjectIntoOwnedSpyWindow) {
-    auto owner = User(mDispatcher, 10, 11);
-    auto window = owner.createWindow();
-    auto spy = owner.createWindow();
-    spy->setSpy(true);
-    spy->setTrustedOverlay(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
-
-    EXPECT_EQ(InputEventInjectionResult::SUCCEEDED,
-              owner.injectTargetedMotion(AMOTION_EVENT_ACTION_DOWN));
-    spy->consumeMotionDown();
-    window->consumeMotionDown();
-}
-
-TEST_F(InputDispatcherTargetedInjectionTest, CannotInjectIntoUnownedSpyWindow) {
-    auto owner = User(mDispatcher, 10, 11);
-    auto window = owner.createWindow();
-
-    auto rando = User(mDispatcher, 20, 21);
-    auto randosSpy = rando.createWindow();
-    randosSpy->setSpy(true);
-    randosSpy->setTrustedOverlay(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {randosSpy, window}}});
-
-    // The event is targeted at owner's window, so injection should succeed, but the spy should
-    // not receive the event.
-    EXPECT_EQ(InputEventInjectionResult::SUCCEEDED,
-              owner.injectTargetedMotion(AMOTION_EVENT_ACTION_DOWN));
-    randosSpy->assertNoEvents();
-    window->consumeMotionDown();
-}
-
-TEST_F(InputDispatcherTargetedInjectionTest, CanInjectIntoAnyWindowWhenNotTargeting) {
-    auto owner = User(mDispatcher, 10, 11);
-    auto window = owner.createWindow();
-
-    auto rando = User(mDispatcher, 20, 21);
-    auto randosSpy = rando.createWindow();
-    randosSpy->setSpy(true);
-    randosSpy->setTrustedOverlay(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {randosSpy, window}}});
-
-    // A user that has injection permission can inject into any window.
-    EXPECT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT));
-    randosSpy->consumeMotionDown();
-    window->consumeMotionDown();
-
-    setFocusedWindow(randosSpy);
-    randosSpy->consumeFocusEvent(true);
-
-    EXPECT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher));
-    randosSpy->consumeKeyDown(ADISPLAY_ID_NONE);
-    window->assertNoEvents();
-}
-
-TEST_F(InputDispatcherTargetedInjectionTest, CanGenerateActionOutsideToOtherUids) {
-    auto owner = User(mDispatcher, 10, 11);
-    auto window = owner.createWindow();
-
-    auto rando = User(mDispatcher, 20, 21);
-    auto randosWindow = rando.createWindow();
-    randosWindow->setFrame(Rect{-10, -10, -5, -5});
-    randosWindow->setWatchOutsideTouch(true);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {randosWindow, window}}});
-
-    // We allow generation of ACTION_OUTSIDE events into windows owned by different uids.
-    EXPECT_EQ(InputEventInjectionResult::SUCCEEDED,
-              owner.injectTargetedMotion(AMOTION_EVENT_ACTION_DOWN));
-    window->consumeMotionDown();
-    randosWindow->consumeMotionOutside();
-}
-
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 8ba501c..b29a0a2 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -375,6 +375,11 @@
 
     float getPointerGestureMovementSpeedRatio() { return mConfig.pointerGestureMovementSpeedRatio; }
 
+    void setVelocityControlParams(const VelocityControlParameters& params) {
+        mConfig.pointerVelocityControlParameters = params;
+        mConfig.wheelVelocityControlParameters = params;
+    }
+
 private:
     uint32_t mNextPointerCaptureSequenceNumber = 0;
 
@@ -2949,7 +2954,10 @@
     }
 
     void configureDevice(uint32_t changes) {
-        if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
+        if (!changes ||
+            (changes &
+             (InputReaderConfiguration::CHANGE_DISPLAY_INFO |
+              InputReaderConfiguration::CHANGE_POINTER_CAPTURE))) {
             mReader->requestRefreshConfiguration(changes);
             mReader->loopOnce();
         }
@@ -3999,6 +4007,78 @@
     ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper.getMetaState());
 }
 
+TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleInMultiDevices) {
+    // keyboard 1.
+    mFakeEventHub->addLed(EVENTHUB_ID, LED_CAPSL, true /*initially on*/);
+    mFakeEventHub->addLed(EVENTHUB_ID, LED_NUML, false /*initially off*/);
+    mFakeEventHub->addLed(EVENTHUB_ID, LED_SCROLLL, false /*initially off*/);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
+
+    KeyboardInputMapper& mapper1 =
+            addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+
+    // keyboard 2.
+    const std::string USB2 = "USB2";
+    const std::string DEVICE_NAME2 = "KEYBOARD2";
+    constexpr int32_t SECOND_DEVICE_ID = DEVICE_ID + 1;
+    constexpr int32_t SECOND_EVENTHUB_ID = EVENTHUB_ID + 1;
+    std::shared_ptr<InputDevice> device2 =
+            newDevice(SECOND_DEVICE_ID, DEVICE_NAME2, USB2, SECOND_EVENTHUB_ID,
+                      ftl::Flags<InputDeviceClass>(0));
+    mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_CAPSL, true /*initially on*/);
+    mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_NUML, false /*initially off*/);
+    mFakeEventHub->addLed(SECOND_EVENTHUB_ID, LED_SCROLLL, false /*initially off*/);
+    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0);
+    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0);
+    mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
+
+    KeyboardInputMapper& mapper2 =
+            device2->addMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD,
+                                                    AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0 /*changes*/);
+    device2->reset(ARBITRARY_TIME);
+
+    // Initial metastate is AMETA_NUM_LOCK_ON, turn it off.
+    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
+    ASSERT_EQ(AMETA_NUM_LOCK_ON, mapper1.getMetaState());
+    ASSERT_EQ(AMETA_NUM_LOCK_ON, mapper2.getMetaState());
+
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 1);
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_NUMLOCK, 0);
+    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_NUML));
+    ASSERT_EQ(AMETA_NONE, mapper1.getMetaState());
+    ASSERT_EQ(AMETA_NONE, mapper2.getMetaState());
+
+    // Toggle caps lock on and off.
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1);
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0);
+    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
+    ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper1.getMetaState());
+    ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper2.getMetaState());
+
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 1);
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_CAPSLOCK, 0);
+    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_CAPSL));
+    ASSERT_EQ(AMETA_NONE, mapper1.getMetaState());
+    ASSERT_EQ(AMETA_NONE, mapper2.getMetaState());
+
+    // Toggle scroll lock on and off.
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1);
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0);
+    ASSERT_TRUE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
+    ASSERT_EQ(AMETA_SCROLL_LOCK_ON, mapper1.getMetaState());
+    ASSERT_EQ(AMETA_SCROLL_LOCK_ON, mapper2.getMetaState());
+
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 1);
+    process(mapper1, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_SCROLLLOCK, 0);
+    ASSERT_FALSE(mFakeEventHub->getLedState(EVENTHUB_ID, LED_SCROLLL));
+    ASSERT_EQ(AMETA_NONE, mapper1.getMetaState());
+    ASSERT_EQ(AMETA_NONE, mapper2.getMetaState());
+}
+
 // --- KeyboardInputMapperTest_ExternalDevice ---
 
 class KeyboardInputMapperTest_ExternalDevice : public InputMapperTest {
@@ -4842,6 +4922,54 @@
     ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 110.0f, 220.0f));
 }
 
+/**
+ * When Pointer Capture is enabled, we expect to report unprocessed relative movements, so any
+ * pointer acceleration or speed processing should not be applied.
+ */
+TEST_F(CursorInputMapperTest, PointerCaptureDisablesVelocityProcessing) {
+    addConfigurationProperty("cursor.mode", "pointer");
+    const VelocityControlParameters testParams(5.f /*scale*/, 0.f /*low threshold*/,
+                                               100.f /*high threshold*/, 10.f /*acceleration*/);
+    mFakePolicy->setVelocityControlParams(testParams);
+    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+
+    NotifyDeviceResetArgs resetArgs;
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
+    ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime);
+    ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
+
+    NotifyMotionArgs args;
+
+    // Move and verify scale is applied.
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
+    const float relX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+    const float relY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+    ASSERT_GT(relX, 10);
+    ASSERT_GT(relY, 20);
+
+    // Enable Pointer Capture
+    mFakePolicy->setPointerCapture(true);
+    configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
+    NotifyPointerCaptureChangedArgs captureArgs;
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs));
+    ASSERT_TRUE(captureArgs.request.enable);
+
+    // Move and verify scale is not applied.
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
+    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
+    ASSERT_EQ(10, args.pointerCoords[0].getX());
+    ASSERT_EQ(20, args.pointerCoords[0].getY());
+}
+
 TEST_F(CursorInputMapperTest, Process_ShouldHandleDisplayId) {
     CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
 
diff --git a/services/sensorservice/HidlSensorHalWrapper.cpp b/services/sensorservice/HidlSensorHalWrapper.cpp
index 4c64e59..c55c9b4 100644
--- a/services/sensorservice/HidlSensorHalWrapper.cpp
+++ b/services/sensorservice/HidlSensorHalWrapper.cpp
@@ -281,7 +281,7 @@
 }
 
 status_t HidlSensorHalWrapper::registerDirectChannel(const sensors_direct_mem_t* memory,
-                                                     int32_t* /*channelHandle*/) {
+                                                     int32_t* outChannelHandle) {
     if (mSensors == nullptr) return NO_INIT;
 
     SharedMemType type;
@@ -309,14 +309,16 @@
             .memoryHandle = memory->handle,
     };
 
-    status_t ret;
-    checkReturn(mSensors->registerDirectChannel(mem, [&ret](auto result, auto channelHandle) {
-        if (result == Result::OK) {
-            ret = channelHandle;
-        } else {
-            ret = statusFromResult(result);
-        }
-    }));
+    status_t ret = OK;
+    checkReturn(mSensors->registerDirectChannel(mem,
+                                                [&ret, &outChannelHandle](auto result,
+                                                                          auto channelHandle) {
+                                                    if (result == Result::OK) {
+                                                        *outChannelHandle = channelHandle;
+                                                    } else {
+                                                        ret = statusFromResult(result);
+                                                    }
+                                                }));
     return ret;
 }
 
diff --git a/services/sensorservice/HidlSensorHalWrapper.h b/services/sensorservice/HidlSensorHalWrapper.h
index 71c3512..d6ed178 100644
--- a/services/sensorservice/HidlSensorHalWrapper.h
+++ b/services/sensorservice/HidlSensorHalWrapper.h
@@ -112,7 +112,7 @@
     virtual status_t injectSensorData(const sensors_event_t* event) override;
 
     virtual status_t registerDirectChannel(const sensors_direct_mem_t* memory,
-                                           int32_t* channelHandle) override;
+                                           int32_t* outChannelHandle) override;
 
     virtual status_t unregisterDirectChannel(int32_t channelHandle) override;
 
diff --git a/services/sensorservice/SensorDevice.cpp b/services/sensorservice/SensorDevice.cpp
index a0e30ac..53a3025 100644
--- a/services/sensorservice/SensorDevice.cpp
+++ b/services/sensorservice/SensorDevice.cpp
@@ -766,7 +766,13 @@
     if (mHalWrapper == nullptr) return NO_INIT;
     Mutex::Autolock _l(mLock);
 
-    return mHalWrapper->registerDirectChannel(memory, nullptr);
+    int32_t channelHandle;
+    status_t status = mHalWrapper->registerDirectChannel(memory, &channelHandle);
+    if (status != OK) {
+        channelHandle = -1;
+    }
+
+    return channelHandle;
 }
 
 void SensorDevice::unregisterDirectChannel(int32_t channelHandle) {
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 79e4c75..670233a 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -376,6 +376,11 @@
     }
 
     ATRACE_CALL();
+    if (displayData.powerMode == hal::PowerMode::DOZE && enabled == hal::Vsync::ENABLE) {
+        ALOGV("%s will not enable vsync for display %s due to power mode %s", __FUNCTION__,
+              to_string(displayId).c_str(), to_string(displayData.powerMode).c_str());
+        return;
+    }
     auto error = displayData.hwcDisplay->setVsyncEnabled(enabled);
     RETURN_IF_HWC_ERROR(error, displayId);
 
@@ -552,6 +557,7 @@
         setVsyncEnabled(displayId, hal::Vsync::DISABLE);
     }
 
+    mDisplayData[displayId].powerMode = mode;
     const auto& displayData = mDisplayData[displayId];
     auto& hwcDisplay = displayData.hwcDisplay;
     switch (mode) {
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 7dc10ea..8d67589 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -462,6 +462,8 @@
         std::mutex vsyncEnabledLock;
         hal::Vsync vsyncEnabled GUARDED_BY(vsyncEnabledLock) = hal::Vsync::DISABLE;
 
+        hal::PowerMode powerMode = hal::PowerMode::ON;
+
         nsecs_t lastHwVsync = 0;
     };
 
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
index 05f488b..cbafdd3 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -463,29 +463,41 @@
         ALOGV("Failed to send actual work duration, skipping");
         return;
     }
-
-    WorkDuration duration;
-    duration.durationNanos = actualDurationNanos;
-    mActualDuration = actualDurationNanos;
+    nsecs_t reportedDuration = actualDurationNanos;
 
     // normalize the sent values to a pre-set target
     if (sNormalizeTarget) {
-        duration.durationNanos += mLastTargetDurationSent - mTargetDuration;
+        reportedDuration += mLastTargetDurationSent - mTargetDuration;
+    } else {
+        // when target duration change is within deviation and not updated, adjust the actual
+        // duration proportionally based on the difference, e.g. if new target is 5ms longer than
+        // last reported but actual duration is the same as last target, we want to report a smaller
+        // actual work duration now to indicate that we are overshooting
+        if (mLastTargetDurationSent != kDefaultTarget.count() && mTargetDuration != 0) {
+            reportedDuration =
+                    static_cast<int64_t>(static_cast<long double>(mLastTargetDurationSent) /
+                                         mTargetDuration * actualDurationNanos);
+            mActualDuration = reportedDuration;
+        }
     }
+    mActualDuration = reportedDuration;
+    WorkDuration duration;
+    duration.durationNanos = reportedDuration;
     duration.timeStampNanos = timeStampNanos;
     mPowerHintQueue.push_back(duration);
 
-    nsecs_t targetNsec = mTargetDuration;
-    nsecs_t durationNsec = actualDurationNanos;
-
     if (sTraceHintSessionData) {
-        ATRACE_INT64("Measured duration", durationNsec);
-        ATRACE_INT64("Target error term", targetNsec - durationNsec);
+        ATRACE_INT64("Measured duration", actualDurationNanos);
+        ATRACE_INT64("Target error term", mTargetDuration - actualDurationNanos);
+
+        ATRACE_INT64("Reported duration", reportedDuration);
+        ATRACE_INT64("Reported target", mLastTargetDurationSent);
+        ATRACE_INT64("Reported target error term", mLastTargetDurationSent - reportedDuration);
     }
 
-    ALOGV("Sending actual work duration of: %" PRId64 " on target: %" PRId64
+    ALOGV("Sending actual work duration of: %" PRId64 " on reported target: %" PRId64
           " with error: %" PRId64,
-          durationNsec, targetNsec, targetNsec - durationNsec);
+          reportedDuration, mLastTargetDurationSent, mLastTargetDurationSent - reportedDuration);
 
     // This rate limiter queues similar duration reports to the powerhal into
     // batches to avoid excessive binder calls. The criteria to send a given batch
@@ -501,7 +513,7 @@
         }
         mPowerHintQueue.clear();
         // we save the non-normalized value here to detect % changes
-        mLastActualDurationSent = actualDurationNanos;
+        mLastActualDurationSent = reportedDuration;
     }
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
index 3f47ffd..61bb32b 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
@@ -173,8 +173,8 @@
 
     // Max percent the actual duration can vary without causing a report (eg: 0.1 = 10%)
     static constexpr double kAllowedActualDeviationPercent = 0.1;
-    // Max percent the target duration can vary without causing a report (eg: 0.05 = 5%)
-    static constexpr double kAllowedTargetDeviationPercent = 0.05;
+    // Max percent the target duration can vary without causing a report (eg: 0.1 = 10%)
+    static constexpr double kAllowedTargetDeviationPercent = 0.1;
     // Target used for init and normalization, the actual value does not really matter
     static constexpr const std::chrono::nanoseconds kDefaultTarget = 50ms;
     // Amount of time after the last message was sent before the session goes stale
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/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 997b1a1..d8a5601 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -2185,7 +2185,7 @@
 
     layerInfo->set_owner_uid(mOwnerUid);
 
-    if (traceFlags & LayerTracing::TRACE_INPUT) {
+    if ((traceFlags & LayerTracing::TRACE_INPUT) && needsInputInfo()) {
         WindowInfo info;
         if (useDrawing) {
             info = fillInputInfo(ui::Transform(), /* displayIsSecure */ true);
@@ -2596,6 +2596,8 @@
             return FrameRateCompatibility::ExactOrMultiple;
         case ANATIVEWINDOW_FRAME_RATE_EXACT:
             return FrameRateCompatibility::Exact;
+        case ANATIVEWINDOW_FRAME_RATE_NO_VOTE:
+            return FrameRateCompatibility::NoVote;
         default:
             LOG_ALWAYS_FATAL("Invalid frame rate compatibility value %d", compatibility);
             return FrameRateCompatibility::Default;
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index 80aa072..d4435c2 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -45,6 +45,15 @@
 constexpr int kBufferWidth = 4 * kDigitWidth + 3 * kDigitSpace;
 constexpr int kBufferHeight = kDigitHeight;
 
+SurfaceComposerClient::Transaction createTransaction(const sp<SurfaceControl>& surface) {
+    constexpr float kFrameRate = 0.f;
+    constexpr int8_t kCompatibility = ANATIVEWINDOW_FRAME_RATE_NO_VOTE;
+    constexpr int8_t kSeamlessness = ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS;
+
+    return SurfaceComposerClient::Transaction().setFrameRate(surface, kFrameRate, kCompatibility,
+                                                             kSeamlessness);
+}
+
 } // namespace
 
 void RefreshRateOverlay::SevenSegmentDrawer::drawSegment(Segment segment, int left, SkColor color,
@@ -213,12 +222,7 @@
         return;
     }
 
-    constexpr float kFrameRate = 0.f;
-    constexpr int8_t kCompatibility = static_cast<int8_t>(Layer::FrameRateCompatibility::NoVote);
-    constexpr int8_t kSeamlessness = ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS;
-
-    SurfaceComposerClient::Transaction()
-            .setFrameRate(mSurfaceControl, kFrameRate, kCompatibility, kSeamlessness)
+    createTransaction(mSurfaceControl)
             .setLayer(mSurfaceControl, INT32_MAX - 2)
             .setTrustedOverlay(mSurfaceControl, true)
             .apply();
@@ -243,9 +247,7 @@
         }
     }();
 
-    SurfaceComposerClient::Transaction t;
-    t.setTransform(mSurfaceControl, transform);
-    t.apply();
+    createTransaction(mSurfaceControl).setTransform(mSurfaceControl, transform).apply();
 
     BufferCache::const_iterator it = mBufferCache.find({fps.getIntValue(), transformHint});
     if (it == mBufferCache.end()) {
@@ -287,25 +289,21 @@
     Rect frame((3 * width) >> 4, height >> 5);
     frame.offsetBy(width >> 5, height >> 4);
 
-    SurfaceComposerClient::Transaction t;
-    t.setMatrix(mSurfaceControl, frame.getWidth() / static_cast<float>(kBufferWidth), 0, 0,
-                frame.getHeight() / static_cast<float>(kBufferHeight));
-    t.setPosition(mSurfaceControl, frame.left, frame.top);
-    t.apply();
+    createTransaction(mSurfaceControl)
+            .setMatrix(mSurfaceControl, frame.getWidth() / static_cast<float>(kBufferWidth), 0, 0,
+                       frame.getHeight() / static_cast<float>(kBufferHeight))
+            .setPosition(mSurfaceControl, frame.left, frame.top)
+            .apply();
 }
 
 void RefreshRateOverlay::setLayerStack(ui::LayerStack stack) {
-    SurfaceComposerClient::Transaction t;
-    t.setLayerStack(mSurfaceControl, stack);
-    t.apply();
+    createTransaction(mSurfaceControl).setLayerStack(mSurfaceControl, stack).apply();
 }
 
 void RefreshRateOverlay::changeRefreshRate(Fps fps) {
     mCurrentFps = fps;
     const auto buffer = getOrCreateBuffers(fps)[mFrame];
-    SurfaceComposerClient::Transaction t;
-    t.setBuffer(mSurfaceControl, buffer);
-    t.apply();
+    createTransaction(mSurfaceControl).setBuffer(mSurfaceControl, buffer).apply();
 }
 
 void RefreshRateOverlay::animate() {
@@ -314,9 +312,7 @@
     const auto& buffers = getOrCreateBuffers(*mCurrentFps);
     mFrame = (mFrame + 1) % buffers.size();
     const auto buffer = buffers[mFrame];
-    SurfaceComposerClient::Transaction t;
-    t.setBuffer(mSurfaceControl, buffer);
-    t.apply();
+    createTransaction(mSurfaceControl).setBuffer(mSurfaceControl, buffer).apply();
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 115dc64..3bfc2cc 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);
             }
         }
     });
@@ -1810,7 +1812,7 @@
     if (hint == FrameHint::kActive) {
         mScheduler->resetIdleTimer();
     }
-    mPowerAdvisor.notifyDisplayUpdateImminent();
+    mPowerAdvisor->notifyDisplayUpdateImminent();
     mScheduler->scheduleFrame();
 }
 
@@ -1980,7 +1982,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();
     }
@@ -1999,8 +2001,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;
@@ -2264,7 +2266,7 @@
     if (mPowerHintSessionData.sessionEnabled) {
         const nsecs_t flingerDuration =
                 (mPowerHintSessionData.presentEnd - mPowerHintSessionData.commitStart);
-        mPowerAdvisor.sendActualWorkDuration(flingerDuration, mPowerHintSessionData.presentEnd);
+        mPowerAdvisor->sendActualWorkDuration(flingerDuration, mPowerHintSessionData.presentEnd);
     }
 }
 
@@ -2728,12 +2730,12 @@
         }
 
         const auto displayId = info->id;
-        const auto it = mPhysicalDisplayTokens.find(displayId);
+        const auto token = mPhysicalDisplayTokens.get(displayId);
 
         if (event.connection == hal::Connection::CONNECTED) {
             auto [supportedModes, activeMode] = loadDisplayModes(displayId);
 
-            if (it == mPhysicalDisplayTokens.end()) {
+            if (!token) {
                 ALOGV("Creating display %s", to_string(displayId).c_str());
 
                 DisplayDeviceState state;
@@ -2748,14 +2750,13 @@
 
                 sp<IBinder> token = new BBinder();
                 mCurrentState.displays.add(token, state);
-                mPhysicalDisplayTokens.emplace(displayId, std::move(token));
+                mPhysicalDisplayTokens.try_emplace(displayId, std::move(token));
                 mInterceptor->saveDisplayCreation(state);
             } else {
                 ALOGV("Recreating display %s", to_string(displayId).c_str());
 
-                const auto token = it->second;
-                auto& state = mCurrentState.displays.editValueFor(token);
-                state.sequenceId = DisplayDeviceState{}.sequenceId; // Generate new sequenceId
+                auto& state = mCurrentState.displays.editValueFor(token->get());
+                state.sequenceId = DisplayDeviceState{}.sequenceId; // Generate new sequenceId.
                 state.physical->supportedModes = std::move(supportedModes);
                 state.physical->activeMode = std::move(activeMode);
                 if (getHwComposer().updatesDeviceProductInfoOnHotplugReconnect()) {
@@ -2765,13 +2766,13 @@
         } else {
             ALOGV("Removing display %s", to_string(displayId).c_str());
 
-            const ssize_t index = mCurrentState.displays.indexOfKey(it->second);
-            if (index >= 0) {
+            if (const ssize_t index = mCurrentState.displays.indexOfKey(token->get()); index >= 0) {
                 const DisplayDeviceState& state = mCurrentState.displays.valueAt(index);
                 mInterceptor->saveDisplayDeletion(state.sequenceId);
                 mCurrentState.displays.removeItemsAt(index);
             }
-            mPhysicalDisplayTokens.erase(it);
+
+            mPhysicalDisplayTokens.erase(displayId);
         }
 
         processDisplayChangesLocked();
@@ -2919,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);
@@ -2952,15 +2953,16 @@
     }
 
     LOG_FATAL_IF(!displaySurface);
-    const auto display = setupNewDisplayDeviceInternal(displayToken, std::move(compositionDisplay),
-                                                       state, displaySurface, producer);
-    mDisplays.emplace(displayToken, display);
+    auto display = setupNewDisplayDeviceInternal(displayToken, std::move(compositionDisplay), state,
+                                                 displaySurface, producer);
     if (display->isPrimary()) {
         initScheduler(display);
     }
     if (!state.isVirtual()) {
         dispatchDisplayHotplugEvent(display->getPhysicalId(), true);
     }
+
+    mDisplays.try_emplace(displayToken, std::move(display));
 }
 
 void SurfaceFlinger::processDisplayRemoved(const wp<IBinder>& displayToken) {
@@ -3601,6 +3603,23 @@
     if (mNumLayers >= ISurfaceComposer::MAX_LAYERS) {
         ALOGE("AddClientLayer failed, mNumLayers (%zu) >= MAX_LAYERS (%zu)", mNumLayers.load(),
               ISurfaceComposer::MAX_LAYERS);
+        static_cast<void>(mScheduler->schedule([=] {
+            ALOGE("Dumping random sampling of on-screen layers: ");
+            mDrawingState.traverse([&](Layer *layer) {
+                // Aim to dump about 200 layers to avoid totally trashing
+                // logcat. On the other hand, if there really are 4096 layers
+                // something has gone totally wrong its probably the most
+                // useful information in logcat.
+                if (rand() % 20 == 13) {
+                    ALOGE("Layer: %s", layer->getName().c_str());
+                }
+            });
+            for (Layer* offscreenLayer : mOffscreenLayers) {
+                if (rand() % 20 == 13) {
+                    ALOGE("Offscreen-layer: %s", offscreenLayer->getName().c_str());
+                }
+            }
+        }));
         return NO_MEMORY;
     }
 
@@ -3631,11 +3650,11 @@
 }
 
 void SurfaceFlinger::setTransactionFlags(uint32_t mask, TransactionSchedule schedule,
-                                         const sp<IBinder>& applyToken) {
+                                         const sp<IBinder>& applyToken, FrameHint frameHint) {
     modulateVsync(&VsyncModulator::setTransactionSchedule, schedule, applyToken);
 
     if (const bool scheduled = mTransactionFlags.fetch_or(mask) & mask; !scheduled) {
-        scheduleCommit(FrameHint::kActive);
+        scheduleCommit(frameHint);
     }
 }
 
@@ -4007,7 +4026,7 @@
 }
 
 void SurfaceFlinger::queueTransaction(TransactionState& state) {
-    Mutex::Autolock _l(mQueueLock);
+    Mutex::Autolock lock(mQueueLock);
 
     // Generate a CountDownLatch pending state if this is a synchronous transaction.
     if ((state.flags & eSynchronous) || state.inputWindowCommands.syncInputWindows) {
@@ -4026,7 +4045,9 @@
         return TransactionSchedule::Late;
     }(state.flags);
 
-    setTransactionFlags(eTransactionFlushNeeded, schedule, state.applyToken);
+    const auto frameHint = state.isFrameActive() ? FrameHint::kActive : FrameHint::kNone;
+
+    setTransactionFlags(eTransactionFlushNeeded, schedule, state.applyToken, frameHint);
 }
 
 void SurfaceFlinger::waitForSynchronousTransaction(
@@ -7164,15 +7185,6 @@
     return calculateMaxAcquiredBufferCount(refreshRate, presentLatency);
 }
 
-void TransactionState::traverseStatesWithBuffers(
-        std::function<void(const layer_state_t&)> visitor) {
-    for (const auto& state : states) {
-        if (state.state.hasBufferChanges() && state.state.hasValidBuffer() && state.state.surface) {
-            visitor(state.state);
-        }
-    }
-}
-
 void SurfaceFlinger::handleLayerCreatedLocked(const LayerCreatedState& state) {
     sp<Layer> layer = state.layer.promote();
     if (!layer) {
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index ea2e71a..011aaef 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -27,6 +27,7 @@
 #include <android/gui/DisplayState.h>
 #include <cutils/atomic.h>
 #include <cutils/compiler.h>
+#include <ftl/small_map.h>
 #include <gui/BufferQueue.h>
 #include <gui/FrameTimestamps.h>
 #include <gui/ISurfaceComposer.h>
@@ -801,7 +802,8 @@
 
     // Sets the masked bits, and schedules a commit if needed.
     void setTransactionFlags(uint32_t mask, TransactionSchedule = TransactionSchedule::Late,
-                             const sp<IBinder>& applyToken = nullptr);
+                             const sp<IBinder>& applyToken = nullptr,
+                             FrameHint = FrameHint::kActive);
 
     // Clears and returns the masked bits.
     uint32_t clearTransactionFlags(uint32_t mask);
@@ -904,8 +906,8 @@
     }
 
     sp<DisplayDevice> getDisplayDeviceLocked(const wp<IBinder>& displayToken) REQUIRES(mStateLock) {
-        const auto it = mDisplays.find(displayToken);
-        return it == mDisplays.end() ? nullptr : it->second;
+        const sp<DisplayDevice> nullDisplay;
+        return mDisplays.get(displayToken).value_or(std::cref(nullDisplay));
     }
 
     sp<const DisplayDevice> getDisplayDeviceLocked(PhysicalDisplayId id) const
@@ -1050,8 +1052,8 @@
      */
     sp<IBinder> getPhysicalDisplayTokenLocked(PhysicalDisplayId displayId) const
             REQUIRES(mStateLock) {
-        const auto it = mPhysicalDisplayTokens.find(displayId);
-        return it != mPhysicalDisplayTokens.end() ? it->second : nullptr;
+        const sp<IBinder> nullToken;
+        return mPhysicalDisplayTokens.get(displayId).value_or(std::cref(nullToken));
     }
 
     std::optional<PhysicalDisplayId> getPhysicalDisplayIdLocked(
@@ -1246,10 +1248,14 @@
 
     std::vector<HotplugEvent> mPendingHotplugEvents GUARDED_BY(mStateLock);
 
-    // this may only be written from the main thread with mStateLock held
-    // it may be read from other threads with mStateLock held
-    std::map<wp<IBinder>, sp<DisplayDevice>> mDisplays GUARDED_BY(mStateLock);
-    std::unordered_map<PhysicalDisplayId, sp<IBinder>> mPhysicalDisplayTokens
+    // Displays are composited in `mDisplays` order. Internal displays are inserted at boot and
+    // never removed, so take precedence over external and virtual displays.
+    //
+    // The static capacities were chosen to exceed a typical number of physical/virtual displays.
+    //
+    // May be read from any thread, but must only be written from the main thread.
+    ftl::SmallMap<wp<IBinder>, const sp<DisplayDevice>, 5> mDisplays GUARDED_BY(mStateLock);
+    ftl::SmallMap<PhysicalDisplayId, const sp<IBinder>, 3> mPhysicalDisplayTokens
             GUARDED_BY(mStateLock);
 
     struct {
@@ -1382,7 +1388,7 @@
     sp<os::IInputFlinger> mInputFlinger;
     InputWindowCommands mInputWindowCommands;
 
-    Hwc2::impl::PowerAdvisor mPowerAdvisor;
+    std::unique_ptr<Hwc2::PowerAdvisor> mPowerAdvisor;
 
     void enableRefreshRateOverlay(bool enable) REQUIRES(mStateLock);
 
@@ -1422,10 +1428,8 @@
     std::atomic<ui::Transform::RotationFlags> mActiveDisplayTransformHint;
 
     bool isRefreshRateOverlayEnabled() const REQUIRES(mStateLock) {
-        return std::any_of(mDisplays.begin(), mDisplays.end(),
-                           [](std::pair<wp<IBinder>, sp<DisplayDevice>> display) {
-                               return display.second->isRefreshRateOverlayEnabled();
-                           });
+        return hasDisplay(
+                [](const auto& display) { return display.isRefreshRateOverlayEnabled(); });
     }
 
     wp<IBinder> mActiveDisplayToken GUARDED_BY(mStateLock);
diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h
index 04ca347..bab5326 100644
--- a/services/surfaceflinger/TransactionState.h
+++ b/services/surfaceflinger/TransactionState.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright 2021 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.
@@ -16,12 +16,21 @@
 
 #pragma once
 
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include <vector>
+
 #include <gui/LayerState.h>
+#include <system/window.h>
 
 namespace android {
+
 class CountDownLatch;
 
 struct TransactionState {
+    TransactionState() = default;
+
     TransactionState(const FrameTimelineInfo& frameTimelineInfo,
                      const Vector<ComposerState>& composerStates,
                      const Vector<DisplayState>& displayStates, uint32_t transactionFlags,
@@ -47,9 +56,30 @@
             originUid(originUid),
             id(transactionId) {}
 
-    TransactionState() {}
+    // Invokes `void(const layer_state_t&)` visitor for matching layers.
+    template <typename Visitor>
+    void traverseStatesWithBuffers(Visitor&& visitor) const {
+        for (const auto& [state] : states) {
+            if (state.hasBufferChanges() && state.hasValidBuffer() && state.surface) {
+                visitor(state);
+            }
+        }
+    }
 
-    void traverseStatesWithBuffers(std::function<void(const layer_state_t&)> visitor);
+    // TODO(b/185535769): Remove FrameHint. Instead, reset the idle timer (of the relevant physical
+    // display) on the main thread if commit leads to composite. Then, RefreshRateOverlay should be
+    // able to setFrameRate once, rather than for each transaction.
+    bool isFrameActive() const {
+        if (!displays.empty()) return true;
+
+        for (const auto& [state] : states) {
+            if (state.frameRateCompatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE) {
+                return true;
+            }
+        }
+
+        return false;
+    }
 
     FrameTimelineInfo frameTimelineInfo;
     Vector<ComposerState> states;
diff --git a/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp b/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp
index e25a0ae..9ab35d7 100644
--- a/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp
+++ b/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp
@@ -17,6 +17,7 @@
 #undef LOG_TAG
 #define LOG_TAG "AidlPowerHalWrapperTest"
 
+#include <android-base/stringprintf.h>
 #include <android/hardware/power/IPower.h>
 #include <android/hardware/power/IPowerHintSession.h>
 #include <gmock/gmock.h>
@@ -82,6 +83,15 @@
     return duration;
 }
 
+std::string printWorkDurations(const ::std::vector<WorkDuration>& durations) {
+    std::ostringstream os;
+    for (auto duration : durations) {
+        os << duration.toString();
+        os << "\n";
+    }
+    return os.str();
+}
+
 namespace {
 TEST_F(AidlPowerHalWrapperTest, supportsPowerHintSession) {
     ASSERT_TRUE(mWrapper->supportsPowerHintSession());
@@ -143,8 +153,8 @@
                                                                               {-1ms, false},
                                                                               {200ms, true},
                                                                               {2ms, true},
-                                                                              {96ms, false},
-                                                                              {104ms, false}};
+                                                                              {91ms, false},
+                                                                              {109ms, false}};
 
     for (const auto& test : testCases) {
         // reset to 100ms baseline
@@ -212,6 +222,40 @@
     }
 }
 
+TEST_F(AidlPowerHalWrapperTest, sendAdjustedActualWorkDuration) {
+    ASSERT_TRUE(mWrapper->supportsPowerHintSession());
+
+    std::vector<int32_t> threadIds = {1, 2};
+    mWrapper->setPowerHintSessionThreadIds(threadIds);
+    EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
+            .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
+    ASSERT_TRUE(mWrapper->startPowerHintSession());
+    verifyAndClearExpectations();
+
+    std::chrono::nanoseconds lastTarget = 100ms;
+    EXPECT_CALL(*mMockSession.get(), updateTargetWorkDuration(lastTarget.count())).Times(1);
+    mWrapper->setTargetWorkDuration(lastTarget.count());
+    std::chrono::nanoseconds newTarget = 105ms;
+    mWrapper->setTargetWorkDuration(newTarget.count());
+    EXPECT_CALL(*mMockSession.get(), updateTargetWorkDuration(newTarget.count())).Times(0);
+    std::chrono::nanoseconds actual = 21ms;
+    // 100 / 105 * 21ms = 20ms
+    std::chrono::nanoseconds expectedActualSent = 20ms;
+    std::vector<WorkDuration> expectedDurations = {toWorkDuration(expectedActualSent, 1)};
+
+    EXPECT_CALL(*mMockSession.get(), reportActualWorkDuration(_))
+            .WillOnce(DoAll(
+                    [expectedDurations](const ::std::vector<WorkDuration>& durationsSent) {
+                        EXPECT_EQ(expectedDurations, durationsSent)
+                                << base::StringPrintf("actual sent: %s vs expected: %s",
+                                                      printWorkDurations(durationsSent).c_str(),
+                                                      printWorkDurations(expectedDurations)
+                                                              .c_str());
+                    },
+                    Return(Status::ok())));
+    mWrapper->sendActualWorkDuration(actual.count(), 1);
+}
+
 TEST_F(AidlPowerHalWrapperTest, sendActualWorkDuration_exceedsStaleTime) {
     ASSERT_TRUE(mWrapper->supportsPowerHintSession());
 
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/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index 2425862..f04221c 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -165,36 +165,39 @@
     return displayDevice;
 }
 
-bool DisplayTransactionTest::hasPhysicalHwcDisplay(HWDisplayId hwcDisplayId) {
-    return mFlinger.mutableHwcPhysicalDisplayIdMap().count(hwcDisplayId) == 1;
+bool DisplayTransactionTest::hasPhysicalHwcDisplay(HWDisplayId hwcDisplayId) const {
+    return mFlinger.hwcPhysicalDisplayIdMap().count(hwcDisplayId) == 1;
 }
 
-bool DisplayTransactionTest::hasTransactionFlagSet(int flag) {
-    return mFlinger.mutableTransactionFlags() & flag;
+bool DisplayTransactionTest::hasTransactionFlagSet(int32_t flag) const {
+    return mFlinger.transactionFlags() & flag;
 }
 
-bool DisplayTransactionTest::hasDisplayDevice(sp<IBinder> displayToken) {
-    return mFlinger.mutableDisplays().count(displayToken) == 1;
+bool DisplayTransactionTest::hasDisplayDevice(const sp<IBinder>& displayToken) const {
+    return mFlinger.displays().contains(displayToken);
 }
 
-sp<DisplayDevice> DisplayTransactionTest::getDisplayDevice(sp<IBinder> displayToken) {
-    return mFlinger.mutableDisplays()[displayToken];
+const DisplayDevice& DisplayTransactionTest::getDisplayDevice(
+        const sp<IBinder>& displayToken) const {
+    return *mFlinger.displays().get(displayToken)->get();
 }
 
-bool DisplayTransactionTest::hasCurrentDisplayState(sp<IBinder> displayToken) {
-    return mFlinger.mutableCurrentState().displays.indexOfKey(displayToken) >= 0;
+bool DisplayTransactionTest::hasCurrentDisplayState(const sp<IBinder>& displayToken) const {
+    return mFlinger.currentState().displays.indexOfKey(displayToken) >= 0;
 }
 
-const DisplayDeviceState& DisplayTransactionTest::getCurrentDisplayState(sp<IBinder> displayToken) {
-    return mFlinger.mutableCurrentState().displays.valueFor(displayToken);
+const DisplayDeviceState& DisplayTransactionTest::getCurrentDisplayState(
+        const sp<IBinder>& displayToken) const {
+    return mFlinger.currentState().displays.valueFor(displayToken);
 }
 
-bool DisplayTransactionTest::hasDrawingDisplayState(sp<IBinder> displayToken) {
-    return mFlinger.mutableDrawingState().displays.indexOfKey(displayToken) >= 0;
+bool DisplayTransactionTest::hasDrawingDisplayState(const sp<IBinder>& displayToken) const {
+    return mFlinger.drawingState().displays.indexOfKey(displayToken) >= 0;
 }
 
-const DisplayDeviceState& DisplayTransactionTest::getDrawingDisplayState(sp<IBinder> displayToken) {
-    return mFlinger.mutableDrawingState().displays.valueFor(displayToken);
+const DisplayDeviceState& DisplayTransactionTest::getDrawingDisplayState(
+        const sp<IBinder>& displayToken) const {
+    return mFlinger.drawingState().displays.valueFor(displayToken);
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
index 565c244..f5235ce 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
@@ -95,14 +95,17 @@
     // --------------------------------------------------------------------
     // Postcondition helpers
 
-    bool hasPhysicalHwcDisplay(hal::HWDisplayId hwcDisplayId);
-    bool hasTransactionFlagSet(int flag);
-    bool hasDisplayDevice(sp<IBinder> displayToken);
-    sp<DisplayDevice> getDisplayDevice(sp<IBinder> displayToken);
-    bool hasCurrentDisplayState(sp<IBinder> displayToken);
-    const DisplayDeviceState& getCurrentDisplayState(sp<IBinder> displayToken);
-    bool hasDrawingDisplayState(sp<IBinder> displayToken);
-    const DisplayDeviceState& getDrawingDisplayState(sp<IBinder> displayToken);
+    bool hasPhysicalHwcDisplay(hal::HWDisplayId) const;
+    bool hasTransactionFlagSet(int32_t flag) const;
+
+    bool hasDisplayDevice(const sp<IBinder>& displayToken) const;
+    const DisplayDevice& getDisplayDevice(const sp<IBinder>& displayToken) const;
+
+    bool hasCurrentDisplayState(const sp<IBinder>& displayToken) const;
+    const DisplayDeviceState& getCurrentDisplayState(const sp<IBinder>& displayToken) const;
+
+    bool hasDrawingDisplayState(const sp<IBinder>& displayToken) const;
+    const DisplayDeviceState& getDrawingDisplayState(const sp<IBinder>& displayToken) const;
 
     // --------------------------------------------------------------------
     // Test instances
diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
index 825f145..b9a5f36 100644
--- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
@@ -337,10 +337,22 @@
                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS, ""));
     EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+
+    // Privileged APIs.
+    EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT,
+                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+    EXPECT_FALSE(ValidateFrameRate(0.0f, ANATIVEWINDOW_FRAME_RATE_NO_VOTE,
+                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+
+    constexpr bool kPrivileged = true;
     EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT,
                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "",
-                                  /*privileged=*/true));
+                                  kPrivileged));
+    EXPECT_TRUE(ValidateFrameRate(0.0f, ANATIVEWINDOW_FRAME_RATE_NO_VOTE,
+                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "",
+                                  kPrivileged));
 
+    // Invalid frame rate.
     EXPECT_FALSE(ValidateFrameRate(-1, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
                                    ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
     EXPECT_FALSE(ValidateFrameRate(1.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
@@ -348,15 +360,12 @@
     EXPECT_FALSE(ValidateFrameRate(0.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
                                    ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
 
-    EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT,
-                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-
-    // Invalid compatibility
+    // Invalid compatibility.
     EXPECT_FALSE(
             ValidateFrameRate(60.0f, -1, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
     EXPECT_FALSE(ValidateFrameRate(60.0f, 2, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
 
-    // Invalid change frame rate strategy
+    // Invalid change frame rate strategy.
     EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT, -1, ""));
     EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT, 2, ""));
 }
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index 32d57b5..5872a47 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -61,7 +61,6 @@
 
 protected:
     void setupScheduler(std::shared_ptr<scheduler::RefreshRateConfigs>);
-    void testChangeRefreshRate(bool isDisplayActive, bool isRefreshRequired);
 
     sp<DisplayDevice> mDisplay;
     mock::EventThread* mAppEventThread;
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
index 6959ee3..9ac2907 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
@@ -107,9 +107,10 @@
 void DisplayTransactionCommitTest::verifyDisplayIsConnected(const sp<IBinder>& displayToken) {
     // The display device should have been set up in the list of displays.
     ASSERT_TRUE(hasDisplayDevice(displayToken));
-    const auto& device = getDisplayDevice(displayToken);
-    EXPECT_EQ(static_cast<bool>(Case::Display::SECURE), device->isSecure());
-    EXPECT_EQ(static_cast<bool>(Case::Display::PRIMARY), device->isPrimary());
+    const auto& display = getDisplayDevice(displayToken);
+
+    EXPECT_EQ(static_cast<bool>(Case::Display::SECURE), display.isSecure());
+    EXPECT_EQ(static_cast<bool>(Case::Display::PRIMARY), display.isPrimary());
 
     std::optional<DisplayDeviceState::Physical> expectedPhysical;
     if (const auto connectionType = Case::Display::CONNECTION_TYPE::value) {
@@ -143,10 +144,11 @@
     // SF should have a display token.
     const auto displayId = Case::Display::DISPLAY_ID::get();
     ASSERT_TRUE(PhysicalDisplayId::tryCast(displayId));
-    ASSERT_EQ(mFlinger.mutablePhysicalDisplayTokens().count(displayId), 1);
-    auto& displayToken = mFlinger.mutablePhysicalDisplayTokens()[displayId];
 
-    verifyDisplayIsConnected<Case>(displayToken);
+    const auto displayTokenOpt = mFlinger.mutablePhysicalDisplayTokens().get(displayId);
+    ASSERT_TRUE(displayTokenOpt);
+
+    verifyDisplayIsConnected<Case>(displayTokenOpt->get());
 }
 
 void DisplayTransactionCommitTest::verifyDisplayIsNotConnected(const sp<IBinder>& displayToken) {
@@ -248,9 +250,9 @@
     // SF should not have a display token.
     const auto displayId = Case::Display::DISPLAY_ID::get();
     ASSERT_TRUE(PhysicalDisplayId::tryCast(displayId));
-    ASSERT_TRUE(mFlinger.mutablePhysicalDisplayTokens().count(displayId) == 0);
+    ASSERT_FALSE(mFlinger.mutablePhysicalDisplayTokens().contains(displayId));
 
-    // The existing token should have been removed
+    // The existing token should have been removed.
     verifyDisplayIsNotConnected(existing.token());
 }
 
@@ -330,7 +332,7 @@
                 // SF should not have a display token.
                 const auto displayId = Case::Display::DISPLAY_ID::get();
                 ASSERT_TRUE(PhysicalDisplayId::tryCast(displayId));
-                ASSERT_TRUE(mFlinger.mutablePhysicalDisplayTokens().count(displayId) == 0);
+                ASSERT_FALSE(mFlinger.mutablePhysicalDisplayTokens().contains(displayId));
             }(),
             testing::KilledBySignal(SIGABRT), "Primary display cannot be disconnected.");
 }
@@ -369,15 +371,16 @@
                 // --------------------------------------------------------------------
                 // Postconditions
 
-                // The existing token should have been removed
+                // The existing token should have been removed.
                 verifyDisplayIsNotConnected(existing.token());
                 const auto displayId = Case::Display::DISPLAY_ID::get();
                 ASSERT_TRUE(PhysicalDisplayId::tryCast(displayId));
-                ASSERT_TRUE(mFlinger.mutablePhysicalDisplayTokens().count(displayId) == 1);
-                EXPECT_NE(existing.token(), mFlinger.mutablePhysicalDisplayTokens()[displayId]);
 
-                // A new display should be connected in its place
+                const auto displayTokenOpt = mFlinger.mutablePhysicalDisplayTokens().get(displayId);
+                ASSERT_TRUE(displayTokenOpt);
+                EXPECT_NE(existing.token(), displayTokenOpt->get());
 
+                // A new display should be connected in its place.
                 verifyPhysicalDisplayIsConnected<Case>();
 
                 // --------------------------------------------------------------------
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/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
index 7948e60..583cf5f 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
@@ -406,7 +406,7 @@
     display.inject();
 
     // The display is set to PowerMode::ON
-    getDisplayDevice(display.token())->setPowerMode(PowerMode::ON);
+    display.mutableDisplayDevice()->setPowerMode(PowerMode::ON);
 
     // --------------------------------------------------------------------
     // Invocation
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index bf2465f..f1a69fb 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) {
@@ -502,6 +517,12 @@
      * post-conditions.
      */
 
+    const auto& displays() const { return mFlinger->mDisplays; }
+    const auto& currentState() const { return mFlinger->mCurrentState; }
+    const auto& drawingState() const { return mFlinger->mDrawingState; }
+    const auto& transactionFlags() const { return mFlinger->mTransactionFlags; }
+    const auto& hwcPhysicalDisplayIdMap() const { return getHwComposer().mPhysicalDisplayIdMap; }
+
     auto& mutableHasWideColorDisplay() { return SurfaceFlinger::hasWideColorDisplay; }
 
     auto& mutableCurrentState() { return mFlinger->mCurrentState; }
@@ -515,7 +536,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; }
 
@@ -741,7 +761,9 @@
             return mFlinger.mutableCurrentState().displays.valueFor(mDisplayToken);
         }
 
-        auto& mutableDisplayDevice() { return mFlinger.mutableDisplays()[mDisplayToken]; }
+        const sp<DisplayDevice>& mutableDisplayDevice() {
+            return mFlinger.mutableDisplays().get(mDisplayToken)->get();
+        }
 
         // If `configs` is nullptr, the injector creates RefreshRateConfigs from the `modes`.
         // Otherwise, it uses `configs`, which the caller must create using the same `modes`.
@@ -848,12 +870,14 @@
             if (!display->isVirtual()) {
                 display->setActiveMode(activeModeId);
             }
-            mFlinger.mutableDisplays().emplace(mDisplayToken, display);
+            mFlinger.mutableDisplays().emplace_or_replace(mDisplayToken, display);
+
             mFlinger.mutableCurrentState().displays.add(mDisplayToken, state);
             mFlinger.mutableDrawingState().displays.add(mDisplayToken, state);
 
             if (const auto& physical = state.physical) {
-                mFlinger.mutablePhysicalDisplayTokens()[physical->id] = mDisplayToken;
+                mFlinger.mutablePhysicalDisplayTokens().emplace_or_replace(physical->id,
+                                                                           mDisplayToken);
             }
 
             return display;
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 45bc4c9..c4b1487 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -299,6 +299,7 @@
 }
 
 void ReleaseSwapchainImage(VkDevice device,
+                           bool shared_present,
                            ANativeWindow* window,
                            int release_fence,
                            Swapchain::Image& image,
@@ -330,7 +331,8 @@
         }
         image.dequeue_fence = -1;
 
-        if (window) {
+        // It's invalid to call cancelBuffer on a shared buffer
+        if (window && !shared_present) {
             window->cancelBuffer(window, image.buffer.get(), release_fence);
         } else {
             if (release_fence >= 0) {
@@ -364,9 +366,10 @@
     if (swapchain->surface.swapchain_handle != HandleFromSwapchain(swapchain))
         return;
     for (uint32_t i = 0; i < swapchain->num_images; i++) {
-        if (!swapchain->images[i].dequeued)
-            ReleaseSwapchainImage(device, nullptr, -1, swapchain->images[i],
-                                  true);
+        if (!swapchain->images[i].dequeued) {
+            ReleaseSwapchainImage(device, swapchain->shared, nullptr, -1,
+                                  swapchain->images[i], true);
+        }
     }
     swapchain->surface.swapchain_handle = VK_NULL_HANDLE;
     swapchain->timing.clear();
@@ -1084,7 +1087,8 @@
     }
 
     for (uint32_t i = 0; i < swapchain->num_images; i++) {
-        ReleaseSwapchainImage(device, window, -1, swapchain->images[i], false);
+        ReleaseSwapchainImage(device, swapchain->shared, window, -1,
+                              swapchain->images[i], false);
     }
 
     if (active) {
@@ -1854,7 +1858,8 @@
                     WorstPresentResult(swapchain_result, VK_SUBOPTIMAL_KHR);
             }
         } else {
-            ReleaseSwapchainImage(device, nullptr, fence, img, true);
+            ReleaseSwapchainImage(device, swapchain.shared, nullptr, fence,
+                                  img, true);
             swapchain_result = VK_ERROR_OUT_OF_DATE_KHR;
         }