Merge changes I8929d11e,Ib737e81f into main

* changes:
  Parcel: free objects before realloc
  binder_parcel_fuzz: add setData
diff --git a/cmds/atrace/atrace.cpp b/cmds/atrace/atrace.cpp
index 5719a09..cd4926a 100644
--- a/cmds/atrace/atrace.cpp
+++ b/cmds/atrace/atrace.cpp
@@ -313,12 +313,6 @@
 static const char* k_traceBufferSizePath =
     "buffer_size_kb";
 
-#if 0
-// TODO: Re-enable after stabilization
-static const char* k_traceCmdlineSizePath =
-    "saved_cmdlines_size";
-#endif
-
 static const char* k_tracingOverwriteEnablePath =
     "options/overwrite";
 
@@ -545,18 +539,6 @@
     return writeStr(k_traceBufferSizePath, str);
 }
 
-#if 0
-// TODO: Re-enable after stabilization
-// Set the default size of cmdline hashtable
-static bool setCmdlineSize()
-{
-    if (fileExists(k_traceCmdlineSizePath)) {
-        return writeStr(k_traceCmdlineSizePath, "8192");
-    }
-    return true;
-}
-#endif
-
 // Set the clock to the best available option while tracing. Use 'boot' if it's
 // available; otherwise, use 'mono'. If neither are available use 'global'.
 // Any write to the trace_clock sysfs file will reset the buffer, so only
@@ -870,8 +852,6 @@
     ok &= setCategoriesEnableFromFile(g_categoriesFile);
     ok &= setTraceOverwriteEnable(g_traceOverwrite);
     ok &= setTraceBufferSizeKB(g_traceBufferSizeKB);
-    // TODO: Re-enable after stabilization
-    //ok &= setCmdlineSize();
     ok &= setClock();
     ok &= setPrintTgidEnableIfPresent(true);
     ok &= setKernelTraceFuncs(g_kernelTraceFuncs);
diff --git a/cmds/atrace/atrace.rc b/cmds/atrace/atrace.rc
index a3e29a8..fdac5db 100644
--- a/cmds/atrace/atrace.rc
+++ b/cmds/atrace/atrace.rc
@@ -13,6 +13,8 @@
 # Access control to these files is now entirely in selinux policy.
     chmod 0666 /sys/kernel/debug/tracing/trace_clock
     chmod 0666 /sys/kernel/tracing/trace_clock
+    chmod 0666 /sys/kernel/debug/tracing/buffer_percent
+    chmod 0666 /sys/kernel/tracing/buffer_percent
     chmod 0666 /sys/kernel/debug/tracing/buffer_size_kb
     chmod 0666 /sys/kernel/tracing/buffer_size_kb
     chmod 0666 /sys/kernel/debug/tracing/options/overwrite
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 5f109fb..8d37aac 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -3468,7 +3468,7 @@
                                                   "tracing", "save-for-bugreport"});
     }
 
-    if (!android_tracing_perfetto_protolog()) {
+    if (!android_tracing_perfetto_protolog_tracing()) {
         dumpTracesForBugReportCommands.push_back({"dumpsys", "activity", "service",
                                                   "SystemUIService", "WMShell", "protolog",
                                                   "save-for-bugreport"});
diff --git a/cmds/installd/otapreopt_script.sh b/cmds/installd/otapreopt_script.sh
index 28bd793..ae7d8e0 100644
--- a/cmds/installd/otapreopt_script.sh
+++ b/cmds/installd/otapreopt_script.sh
@@ -50,6 +50,12 @@
   exit 1
 fi
 
+if pm art on-ota-staged --slot "$TARGET_SLOT_SUFFIX"; then
+  # Handled by Pre-reboot Dexopt.
+  exit 0
+fi
+echo "Pre-reboot Dexopt not enabled. Fall back to otapreopt."
+
 if [ "$(/system/bin/otapreopt_chroot --version)" != 2 ]; then
   # We require an updated chroot wrapper that reads dexopt commands from stdin.
   # Even if we kept compat with the old binary, the OTA preopt wouldn't work due
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 8b2842a..eedf141 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -353,6 +353,12 @@
 }
 
 prebuilt_etc {
+    name: "android.software.contextualsearch.prebuilt.xml",
+    src: "android.software.contextualsearch.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "android.software.device_id_attestation.prebuilt.xml",
     src: "android.software.device_id_attestation.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
diff --git a/include/android/input.h b/include/android/input.h
index b5c1e5c..fec56f0 100644
--- a/include/android/input.h
+++ b/include/android/input.h
@@ -1492,7 +1492,10 @@
 
 /**
  * Creates a java android.view.InputEvent object that is a copy of the specified native
- * {@link AInputEvent}. Returns null on error
+ * {@link AInputEvent}.
+ *
+ * Specified {@link AInputEvent} is require to be a valid {@link MotionEvent} or {@link KeyEvent}
+ * object.
  *
  *  Available since API level 35.
  */
diff --git a/include/input/Input.h b/include/input/Input.h
index a84dcfc..374254f 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -870,6 +870,10 @@
 
     void copyFrom(const MotionEvent* other, bool keepHistory);
 
+    // Initialize this event by keeping only the pointers from "other" that are in splitPointerIds.
+    void splitFrom(const MotionEvent& other, std::bitset<MAX_POINTER_ID + 1> splitPointerIds,
+                   int32_t newEventId);
+
     void addSample(
             nsecs_t eventTime,
             const PointerCoords* pointerCoords);
@@ -910,6 +914,11 @@
 
     static std::string actionToString(int32_t action);
 
+    static std::tuple<int32_t /*action*/, std::vector<PointerProperties>,
+                      std::vector<PointerCoords>>
+    split(int32_t action, int32_t flags, int32_t historySize, const std::vector<PointerProperties>&,
+          const std::vector<PointerCoords>&, std::bitset<MAX_POINTER_ID + 1> splitPointerIds);
+
     // MotionEvent will transform various axes in different ways, based on the source. For
     // example, the x and y axes will not have any offsets/translations applied if it comes from a
     // relative mouse device (since SOURCE_RELATIVE_MOUSE is a non-pointer source). These methods
diff --git a/include/input/InputEventBuilders.h b/include/input/InputEventBuilders.h
index 2d23b97..c0c5e24 100644
--- a/include/input/InputEventBuilders.h
+++ b/include/input/InputEventBuilders.h
@@ -118,6 +118,16 @@
         return *this;
     }
 
+    MotionEventBuilder& transform(ui::Transform t) {
+        mTransform = t;
+        return *this;
+    }
+
+    MotionEventBuilder& rawTransform(ui::Transform t) {
+        mRawTransform = t;
+        return *this;
+    }
+
     MotionEvent build() {
         std::vector<PointerProperties> pointerProperties;
         std::vector<PointerCoords> pointerCoords;
@@ -134,12 +144,11 @@
         }
 
         MotionEvent event;
-        static const ui::Transform kIdentityTransform;
         event.initialize(InputEvent::nextId(), mDeviceId, mSource, mDisplayId, INVALID_HMAC,
                          mAction, mActionButton, mFlags, /*edgeFlags=*/0, AMETA_NONE, mButtonState,
-                         MotionClassification::NONE, kIdentityTransform,
+                         MotionClassification::NONE, mTransform,
                          /*xPrecision=*/0, /*yPrecision=*/0, mRawXCursorPosition,
-                         mRawYCursorPosition, kIdentityTransform, mDownTime, mEventTime,
+                         mRawYCursorPosition, mRawTransform, mDownTime, mEventTime,
                          mPointers.size(), pointerProperties.data(), pointerCoords.data());
         return event;
     }
@@ -156,6 +165,8 @@
     int32_t mFlags{0};
     float mRawXCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
     float mRawYCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
+    ui::Transform mTransform;
+    ui::Transform mRawTransform;
 
     std::vector<PointerBuilder> mPointers;
 };
diff --git a/include/powermanager/HalResult.h b/include/powermanager/HalResult.h
new file mode 100644
index 0000000..7fe3822
--- /dev/null
+++ b/include/powermanager/HalResult.h
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *                        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android/binder_auto_utils.h>
+#include <android/binder_status.h>
+#include <android/hardware/power/1.0/IPower.h>
+#include <binder/Status.h>
+#include <hidl/HidlSupport.h>
+#include <string>
+
+namespace android::power {
+
+static bool checkUnsupported(const ndk::ScopedAStatus& ndkStatus) {
+    return ndkStatus.getExceptionCode() == EX_UNSUPPORTED_OPERATION ||
+            ndkStatus.getStatus() == STATUS_UNKNOWN_TRANSACTION;
+}
+
+static bool checkUnsupported(const binder::Status& status) {
+    return status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION ||
+            status.transactionError() == UNKNOWN_TRANSACTION;
+}
+
+// Result of a call to the Power HAL wrapper, holding data if successful.
+template <typename T>
+class HalResult {
+public:
+    static HalResult<T> ok(T&& value) { return HalResult(std::forward<T>(value)); }
+    static HalResult<T> ok(T& value) { return HalResult<T>::ok(T{value}); }
+    static HalResult<T> failed(std::string msg) { return HalResult(msg, /* unsupported= */ false); }
+    static HalResult<T> unsupported() { return HalResult("", /* unsupported= */ true); }
+
+    static HalResult<T> fromStatus(const binder::Status& status, T&& data) {
+        if (checkUnsupported(status)) {
+            return HalResult<T>::unsupported();
+        }
+        if (status.isOk()) {
+            return HalResult<T>::ok(std::forward<T>(data));
+        }
+        return HalResult<T>::failed(std::string(status.toString8().c_str()));
+    }
+
+    static HalResult<T> fromStatus(const binder::Status& status, T& data) {
+        return HalResult<T>::fromStatus(status, T{data});
+    }
+
+    static HalResult<T> fromStatus(const ndk::ScopedAStatus& ndkStatus, T&& data) {
+        if (checkUnsupported(ndkStatus)) {
+            return HalResult<T>::unsupported();
+        }
+        if (ndkStatus.isOk()) {
+            return HalResult<T>::ok(std::forward<T>(data));
+        }
+        return HalResult<T>::failed(std::string(ndkStatus.getDescription()));
+    }
+
+    static HalResult<T> fromStatus(const ndk::ScopedAStatus& ndkStatus, T& data) {
+        return HalResult<T>::fromStatus(ndkStatus, T{data});
+    }
+
+    template <typename R>
+    static HalResult<T> fromReturn(hardware::Return<R>& ret, T&& data) {
+        return ret.isOk() ? HalResult<T>::ok(std::forward<T>(data))
+                          : HalResult<T>::failed(ret.description());
+    }
+
+    template <typename R>
+    static HalResult<T> fromReturn(hardware::Return<R>& ret, T& data) {
+        return HalResult<T>::fromReturn(ret, T{data});
+    }
+
+    template <typename R>
+    static HalResult<T> fromReturn(hardware::Return<R>& ret, hardware::power::V1_0::Status status,
+                                   T&& data) {
+        return ret.isOk() ? HalResult<T>::fromStatus(status, std::forward<T>(data))
+                          : HalResult<T>::failed(ret.description());
+    }
+
+    template <typename R>
+    static HalResult<T> fromReturn(hardware::Return<R>& ret, hardware::power::V1_0::Status status,
+                                   T& data) {
+        return HalResult<T>::fromReturn(ret, status, T{data});
+    }
+
+    // This will throw std::bad_optional_access if this result is not ok.
+    const T& value() const { return mValue.value(); }
+    bool isOk() const { return !mUnsupported && mValue.has_value(); }
+    bool isFailed() const { return !mUnsupported && !mValue.has_value(); }
+    bool isUnsupported() const { return mUnsupported; }
+    const char* errorMessage() const { return mErrorMessage.c_str(); }
+
+private:
+    std::optional<T> mValue;
+    std::string mErrorMessage;
+    bool mUnsupported;
+
+    explicit HalResult(T&& value)
+          : mValue{std::move(value)}, mErrorMessage(), mUnsupported(false) {}
+    explicit HalResult(std::string errorMessage, bool unsupported)
+          : mValue(), mErrorMessage(std::move(errorMessage)), mUnsupported(unsupported) {}
+};
+
+// Empty result
+template <>
+class HalResult<void> {
+public:
+    static HalResult<void> ok() { return HalResult(); }
+    static HalResult<void> failed(std::string msg) { return HalResult(std::move(msg)); }
+    static HalResult<void> unsupported() { return HalResult(/* unsupported= */ true); }
+
+    static HalResult<void> fromStatus(const binder::Status& status) {
+        if (checkUnsupported(status)) {
+            return HalResult<void>::unsupported();
+        }
+        if (status.isOk()) {
+            return HalResult<void>::ok();
+        }
+        return HalResult<void>::failed(std::string(status.toString8().c_str()));
+    }
+
+    static HalResult<void> fromStatus(const ndk::ScopedAStatus& ndkStatus) {
+        if (ndkStatus.isOk()) {
+            return HalResult<void>::ok();
+        }
+        if (checkUnsupported(ndkStatus)) {
+            return HalResult<void>::unsupported();
+        }
+        return HalResult<void>::failed(ndkStatus.getDescription());
+    }
+
+    template <typename R>
+    static HalResult<void> fromReturn(hardware::Return<R>& ret) {
+        return ret.isOk() ? HalResult<void>::ok() : HalResult<void>::failed(ret.description());
+    }
+
+    bool isOk() const { return !mUnsupported && !mFailed; }
+    bool isFailed() const { return !mUnsupported && mFailed; }
+    bool isUnsupported() const { return mUnsupported; }
+    const char* errorMessage() const { return mErrorMessage.c_str(); }
+
+private:
+    std::string mErrorMessage;
+    bool mFailed;
+    bool mUnsupported;
+
+    explicit HalResult(bool unsupported = false)
+          : mErrorMessage(), mFailed(false), mUnsupported(unsupported) {}
+    explicit HalResult(std::string errorMessage)
+          : mErrorMessage(std::move(errorMessage)), mFailed(true), mUnsupported(false) {}
+};
+} // namespace android::power
diff --git a/include/powermanager/PowerHalController.h b/include/powermanager/PowerHalController.h
index c50bc4a..7e0bd5b 100644
--- a/include/powermanager/PowerHalController.h
+++ b/include/powermanager/PowerHalController.h
@@ -23,6 +23,7 @@
 #include <aidl/android/hardware/power/Mode.h>
 #include <android-base/thread_annotations.h>
 #include <powermanager/PowerHalWrapper.h>
+#include <powermanager/PowerHintSessionWrapper.h>
 
 namespace android {
 
@@ -38,6 +39,7 @@
 
     virtual std::unique_ptr<HalWrapper> connect();
     virtual void reset();
+    virtual int32_t getAidlVersion();
 };
 
 // -------------------------------------------------------------------------------------------------
@@ -59,14 +61,13 @@
                                      int32_t durationMs) override;
     virtual HalResult<void> setMode(aidl::android::hardware::power::Mode mode,
                                     bool enabled) override;
-    virtual HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
-    createHintSession(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
-                      int64_t durationNanos) override;
-    virtual HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
-    createHintSessionWithConfig(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
-                                int64_t durationNanos,
-                                aidl::android::hardware::power::SessionTag tag,
-                                aidl::android::hardware::power::SessionConfig* config) override;
+    virtual HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSession(
+            int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
+            int64_t durationNanos) override;
+    virtual HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSessionWithConfig(
+            int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos,
+            aidl::android::hardware::power::SessionTag tag,
+            aidl::android::hardware::power::SessionConfig* config) override;
     virtual HalResult<int64_t> getHintSessionPreferredRate() override;
     virtual HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(
             int tgid, int uid) override;
diff --git a/include/powermanager/PowerHalLoader.h b/include/powermanager/PowerHalLoader.h
index cbbfa59..ab66336 100644
--- a/include/powermanager/PowerHalLoader.h
+++ b/include/powermanager/PowerHalLoader.h
@@ -36,6 +36,8 @@
     static sp<hardware::power::V1_1::IPower> loadHidlV1_1();
     static sp<hardware::power::V1_2::IPower> loadHidlV1_2();
     static sp<hardware::power::V1_3::IPower> loadHidlV1_3();
+    // Returns aidl interface version, or 0 if AIDL is not used
+    static int32_t getAidlVersion();
 
 private:
     static std::mutex gHalMutex;
@@ -48,6 +50,8 @@
     static sp<hardware::power::V1_0::IPower> loadHidlV1_0Locked()
             EXCLUSIVE_LOCKS_REQUIRED(gHalMutex);
 
+    static int32_t gAidlInterfaceVersion;
+
     PowerHalLoader() = delete;
     ~PowerHalLoader() = delete;
 };
diff --git a/include/powermanager/PowerHalWrapper.h b/include/powermanager/PowerHalWrapper.h
index e2da014..6e347a9 100644
--- a/include/powermanager/PowerHalWrapper.h
+++ b/include/powermanager/PowerHalWrapper.h
@@ -26,6 +26,9 @@
 #include <android/hardware/power/1.1/IPower.h>
 #include <android/hardware/power/1.2/IPower.h>
 #include <android/hardware/power/1.3/IPower.h>
+#include <powermanager/HalResult.h>
+#include <powermanager/PowerHintSessionWrapper.h>
+
 #include <binder/Status.h>
 
 #include <utility>
@@ -41,134 +44,6 @@
     OFF = 2,
 };
 
-// Result of a call to the Power HAL wrapper, holding data if successful.
-template <typename T>
-class HalResult {
-public:
-    static HalResult<T> ok(T&& value) { return HalResult(std::forward<T>(value)); }
-    static HalResult<T> ok(T& value) { return HalResult<T>::ok(T{value}); }
-    static HalResult<T> failed(std::string msg) { return HalResult(msg, /* unsupported= */ false); }
-    static HalResult<T> unsupported() { return HalResult("", /* unsupported= */ true); }
-
-    static HalResult<T> fromStatus(const binder::Status& status, T&& data) {
-        if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
-            return HalResult<T>::unsupported();
-        }
-        if (status.isOk()) {
-            return HalResult<T>::ok(std::forward<T>(data));
-        }
-        return HalResult<T>::failed(std::string(status.toString8().c_str()));
-    }
-
-    static HalResult<T> fromStatus(const binder::Status& status, T& data) {
-        return HalResult<T>::fromStatus(status, T{data});
-    }
-
-    static HalResult<T> fromStatus(const ndk::ScopedAStatus& status, T&& data) {
-        if (status.getExceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
-            return HalResult<T>::unsupported();
-        }
-        if (status.isOk()) {
-            return HalResult<T>::ok(std::forward<T>(data));
-        }
-        return HalResult<T>::failed(std::string(status.getDescription()));
-    }
-
-    static HalResult<T> fromStatus(const ndk::ScopedAStatus& status, T& data) {
-        return HalResult<T>::fromStatus(status, T{data});
-    }
-
-    template <typename R>
-    static HalResult<T> fromReturn(hardware::Return<R>& ret, T&& data) {
-        return ret.isOk() ? HalResult<T>::ok(std::forward<T>(data))
-                          : HalResult<T>::failed(ret.description());
-    }
-
-    template <typename R>
-    static HalResult<T> fromReturn(hardware::Return<R>& ret, T& data) {
-        return HalResult<T>::fromReturn(ret, T{data});
-    }
-
-    template <typename R>
-    static HalResult<T> fromReturn(hardware::Return<R>& ret, hardware::power::V1_0::Status status,
-                                   T&& data) {
-        return ret.isOk() ? HalResult<T>::fromStatus(status, std::forward<T>(data))
-                          : HalResult<T>::failed(ret.description());
-    }
-
-    template <typename R>
-    static HalResult<T> fromReturn(hardware::Return<R>& ret, hardware::power::V1_0::Status status,
-                                   T& data) {
-        return HalResult<T>::fromReturn(ret, status, T{data});
-    }
-
-    // This will throw std::bad_optional_access if this result is not ok.
-    const T& value() const { return mValue.value(); }
-    bool isOk() const { return !mUnsupported && mValue.has_value(); }
-    bool isFailed() const { return !mUnsupported && !mValue.has_value(); }
-    bool isUnsupported() const { return mUnsupported; }
-    const char* errorMessage() const { return mErrorMessage.c_str(); }
-
-private:
-    std::optional<T> mValue;
-    std::string mErrorMessage;
-    bool mUnsupported;
-
-    explicit HalResult(T&& value)
-          : mValue{std::move(value)}, mErrorMessage(), mUnsupported(false) {}
-    explicit HalResult(std::string errorMessage, bool unsupported)
-          : mValue(), mErrorMessage(std::move(errorMessage)), mUnsupported(unsupported) {}
-};
-
-// Empty result of a call to the Power HAL wrapper.
-template <>
-class HalResult<void> {
-public:
-    static HalResult<void> ok() { return HalResult(); }
-    static HalResult<void> failed(std::string msg) { return HalResult(std::move(msg)); }
-    static HalResult<void> unsupported() { return HalResult(/* unsupported= */ true); }
-
-    static HalResult<void> fromStatus(const binder::Status& status) {
-        if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
-            return HalResult<void>::unsupported();
-        }
-        if (status.isOk()) {
-            return HalResult<void>::ok();
-        }
-        return HalResult<void>::failed(std::string(status.toString8().c_str()));
-    }
-
-    static HalResult<void> fromStatus(const ndk::ScopedAStatus& status) {
-        if (status.getExceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
-            return HalResult<void>::unsupported();
-        }
-        if (status.isOk()) {
-            return HalResult<void>::ok();
-        }
-        return HalResult<void>::failed(std::string(status.getDescription()));
-    }
-
-    template <typename R>
-    static HalResult<void> fromReturn(hardware::Return<R>& ret) {
-        return ret.isOk() ? HalResult<void>::ok() : HalResult<void>::failed(ret.description());
-    }
-
-    bool isOk() const { return !mUnsupported && !mFailed; }
-    bool isFailed() const { return !mUnsupported && mFailed; }
-    bool isUnsupported() const { return mUnsupported; }
-    const char* errorMessage() const { return mErrorMessage.c_str(); }
-
-private:
-    std::string mErrorMessage;
-    bool mFailed;
-    bool mUnsupported;
-
-    explicit HalResult(bool unsupported = false)
-          : mErrorMessage(), mFailed(false), mUnsupported(unsupported) {}
-    explicit HalResult(std::string errorMessage)
-          : mErrorMessage(std::move(errorMessage)), mFailed(true), mUnsupported(false) {}
-};
-
 // Wrapper for Power HAL handlers.
 class HalWrapper {
 public:
@@ -177,14 +52,13 @@
     virtual HalResult<void> setBoost(aidl::android::hardware::power::Boost boost,
                                      int32_t durationMs) = 0;
     virtual HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) = 0;
-    virtual HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
-    createHintSession(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
-                      int64_t durationNanos) = 0;
-    virtual HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
-    createHintSessionWithConfig(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
-                                int64_t durationNanos,
-                                aidl::android::hardware::power::SessionTag tag,
-                                aidl::android::hardware::power::SessionConfig* config) = 0;
+    virtual HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSession(
+            int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
+            int64_t durationNanos) = 0;
+    virtual HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSessionWithConfig(
+            int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos,
+            aidl::android::hardware::power::SessionTag tag,
+            aidl::android::hardware::power::SessionConfig* config) = 0;
     virtual HalResult<int64_t> getHintSessionPreferredRate() = 0;
     virtual HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(int tgid,
                                                                                        int uid) = 0;
@@ -200,14 +74,13 @@
     HalResult<void> setBoost(aidl::android::hardware::power::Boost boost,
                              int32_t durationMs) override;
     HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) override;
-    HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> createHintSession(
+    HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSession(
             int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
             int64_t durationNanos) override;
-    HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
-    createHintSessionWithConfig(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
-                                int64_t durationNanos,
-                                aidl::android::hardware::power::SessionTag tag,
-                                aidl::android::hardware::power::SessionConfig* config) override;
+    HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSessionWithConfig(
+            int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos,
+            aidl::android::hardware::power::SessionTag tag,
+            aidl::android::hardware::power::SessionConfig* config) override;
     HalResult<int64_t> getHintSessionPreferredRate() override;
     HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(int tgid,
                                                                                int uid) override;
@@ -285,14 +158,13 @@
     HalResult<void> setBoost(aidl::android::hardware::power::Boost boost,
                              int32_t durationMs) override;
     HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) override;
-    HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> createHintSession(
+    HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSession(
             int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
             int64_t durationNanos) override;
-    HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
-    createHintSessionWithConfig(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
-                                int64_t durationNanos,
-                                aidl::android::hardware::power::SessionTag tag,
-                                aidl::android::hardware::power::SessionConfig* config) override;
+    HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSessionWithConfig(
+            int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos,
+            aidl::android::hardware::power::SessionTag tag,
+            aidl::android::hardware::power::SessionConfig* config) override;
 
     HalResult<int64_t> getHintSessionPreferredRate() override;
     HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(int tgid,
@@ -307,14 +179,12 @@
     std::mutex mBoostMutex;
     std::mutex mModeMutex;
     std::shared_ptr<aidl::android::hardware::power::IPower> mHandle;
-    // Android framework only sends boost upto DISPLAY_UPDATE_IMMINENT.
-    // Need to increase the array size if more boost supported.
-    std::array<
-            std::atomic<HalSupport>,
-            static_cast<int32_t>(aidl::android::hardware::power::Boost::DISPLAY_UPDATE_IMMINENT) +
-                    1>
+    std::array<HalSupport,
+               static_cast<int32_t>(
+                       *(ndk::enum_range<aidl::android::hardware::power::Boost>().end() - 1)) +
+                       1>
             mBoostSupportedArray GUARDED_BY(mBoostMutex) = {HalSupport::UNKNOWN};
-    std::array<std::atomic<HalSupport>,
+    std::array<HalSupport,
                static_cast<int32_t>(
                        *(ndk::enum_range<aidl::android::hardware::power::Mode>().end() - 1)) +
                        1>
diff --git a/include/powermanager/PowerHintSessionWrapper.h b/include/powermanager/PowerHintSessionWrapper.h
new file mode 100644
index 0000000..ba6fe77
--- /dev/null
+++ b/include/powermanager/PowerHintSessionWrapper.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *            http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/ChannelConfig.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/IPowerHintSession.h>
+#include <aidl/android/hardware/power/Mode.h>
+#include <aidl/android/hardware/power/SessionConfig.h>
+#include <android-base/thread_annotations.h>
+#include "HalResult.h"
+
+namespace android::power {
+
+// Wrapper for power hint sessions, which allows for better mocking,
+// support checking, and failure handling than using hint sessions directly
+class PowerHintSessionWrapper {
+public:
+    virtual ~PowerHintSessionWrapper() = default;
+    PowerHintSessionWrapper(
+            std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>&& session);
+    virtual HalResult<void> updateTargetWorkDuration(int64_t in_targetDurationNanos);
+    virtual HalResult<void> reportActualWorkDuration(
+            const std::vector<::aidl::android::hardware::power::WorkDuration>& in_durations);
+    virtual HalResult<void> pause();
+    virtual HalResult<void> resume();
+    virtual HalResult<void> close();
+    virtual HalResult<void> sendHint(::aidl::android::hardware::power::SessionHint in_hint);
+    virtual HalResult<void> setThreads(const std::vector<int32_t>& in_threadIds);
+    virtual HalResult<void> setMode(::aidl::android::hardware::power::SessionMode in_type,
+                                    bool in_enabled);
+    virtual HalResult<aidl::android::hardware::power::SessionConfig> getSessionConfig();
+
+private:
+    std::shared_ptr<aidl::android::hardware::power::IPowerHintSession> mSession;
+    int32_t mInterfaceVersion;
+};
+
+} // namespace android::power
\ No newline at end of file
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 4d1463c..35cea81 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -256,7 +256,7 @@
 
     if (const auto* rpcFields = maybeRpcFields()) {
         if (binder) {
-            status_t status = writeInt32(1); // non-null
+            status_t status = writeInt32(RpcFields::TYPE_BINDER); // non-null
             if (status != OK) return status;
             uint64_t address;
             // TODO(b/167966510): need to undo this if the Parcel is not sent
@@ -266,7 +266,7 @@
             status = writeUint64(address);
             if (status != OK) return status;
         } else {
-            status_t status = writeInt32(0); // null
+            status_t status = writeInt32(RpcFields::TYPE_BINDER_NULL); // null
             if (status != OK) return status;
         }
         return finishFlattenBinder(binder);
@@ -740,6 +740,12 @@
     return kernelFields->mHasFds;
 }
 
+status_t Parcel::hasBinders(bool* result) const {
+    status_t status = hasBindersInRange(0, dataSize(), result);
+    ALOGE_IF(status != NO_ERROR, "Error %d calling hasBindersInRange()", status);
+    return status;
+}
+
 std::vector<sp<IBinder>> Parcel::debugReadAllStrongBinders() const {
     std::vector<sp<IBinder>> ret;
 
@@ -799,6 +805,46 @@
     return ret;
 }
 
+status_t Parcel::hasBindersInRange(size_t offset, size_t len, bool* result) const {
+    if (len > INT32_MAX || offset > INT32_MAX) {
+        // Don't accept size_t values which may have come from an inadvertent conversion from a
+        // negative int.
+        return BAD_VALUE;
+    }
+    size_t limit;
+    if (__builtin_add_overflow(offset, len, &limit) || limit > mDataSize) {
+        return BAD_VALUE;
+    }
+    *result = false;
+    if (const auto* kernelFields = maybeKernelFields()) {
+#ifdef BINDER_WITH_KERNEL_IPC
+        for (size_t i = 0; i < kernelFields->mObjectsSize; i++) {
+            size_t pos = kernelFields->mObjects[i];
+            if (pos < offset) continue;
+            if (pos + sizeof(flat_binder_object) > offset + len) {
+                if (kernelFields->mObjectsSorted) {
+                    break;
+                } else {
+                    continue;
+                }
+            }
+            const flat_binder_object* flat =
+                    reinterpret_cast<const flat_binder_object*>(mData + pos);
+            if (flat->hdr.type == BINDER_TYPE_BINDER || flat->hdr.type == BINDER_TYPE_HANDLE) {
+                *result = true;
+                break;
+            }
+        }
+#else
+        LOG_ALWAYS_FATAL("Binder kernel driver disabled at build time");
+        return INVALID_OPERATION;
+#endif // BINDER_WITH_KERNEL_IPC
+    } else if (const auto* rpcFields = maybeRpcFields()) {
+        return INVALID_OPERATION;
+    }
+    return NO_ERROR;
+}
+
 status_t Parcel::hasFileDescriptorsInRange(size_t offset, size_t len, bool* result) const {
     if (len > INT32_MAX || offset > INT32_MAX) {
         // Don't accept size_t values which may have come from an inadvertent conversion from a
diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h
index d7096d8..5e18b91 100644
--- a/libs/binder/include/binder/Parcel.h
+++ b/libs/binder/include/binder/Parcel.h
@@ -101,7 +101,9 @@
     void                restoreAllowFds(bool lastValue);
 
     bool                hasFileDescriptors() const;
+    status_t hasBinders(bool* result) const;
     status_t hasFileDescriptorsInRange(size_t offset, size_t length, bool* result) const;
+    status_t hasBindersInRange(size_t offset, size_t length, bool* result) const;
 
     // returns all binder objects in the Parcel
     std::vector<sp<IBinder>> debugReadAllStrongBinders() const;
@@ -647,6 +649,8 @@
     void                freeDataNoInit();
     void                initState();
     void                scanForFds() const;
+    status_t scanForBinders(bool* result) const;
+
     status_t            validateReadData(size_t len) const;
 
     void                updateWorkSourceRequestHeaderPosition() const;
diff --git a/libs/binder/tests/binderParcelUnitTest.cpp b/libs/binder/tests/binderParcelUnitTest.cpp
index 34fc43f..32a70e5 100644
--- a/libs/binder/tests/binderParcelUnitTest.cpp
+++ b/libs/binder/tests/binderParcelUnitTest.cpp
@@ -23,6 +23,7 @@
 using android::BBinder;
 using android::IBinder;
 using android::IPCThreadState;
+using android::NO_ERROR;
 using android::OK;
 using android::Parcel;
 using android::sp;
@@ -164,6 +165,45 @@
     ASSERT_EQ(2, p2.readInt32());
 }
 
+TEST(Parcel, HasBinders) {
+    sp<IBinder> b1 = sp<BBinder>::make();
+
+    Parcel p1;
+    p1.writeInt32(1);
+    p1.writeStrongBinder(b1);
+
+    bool result = false;
+    ASSERT_EQ(NO_ERROR, p1.hasBinders(&result));
+    ASSERT_EQ(true, result);
+
+    p1.setDataSize(0); // clear data
+    result = false;
+    ASSERT_EQ(NO_ERROR, p1.hasBinders(&result));
+    ASSERT_EQ(false, result);
+    p1.writeStrongBinder(b1); // reset with binder data
+    result = false;
+    ASSERT_EQ(NO_ERROR, p1.hasBinders(&result));
+    ASSERT_EQ(true, result);
+
+    Parcel p3;
+    p3.appendFrom(&p1, 0, p1.dataSize());
+    result = false;
+    ASSERT_EQ(NO_ERROR, p1.hasBinders(&result));
+    ASSERT_EQ(true, result);
+}
+
+TEST(Parcel, HasBindersInRange) {
+    sp<IBinder> b1 = sp<BBinder>::make();
+    Parcel p1;
+    p1.writeStrongBinder(b1);
+    bool result = false;
+    ASSERT_EQ(NO_ERROR, p1.hasBindersInRange(0, p1.dataSize(), &result));
+    ASSERT_EQ(true, result);
+    result = false;
+    ASSERT_EQ(NO_ERROR, p1.hasBinders(&result));
+    ASSERT_EQ(true, result);
+}
+
 TEST(Parcel, AppendWithBinder) {
     sp<IBinder> b1 = sp<BBinder>::make();
     sp<IBinder> b2 = sp<BBinder>::make();
diff --git a/libs/binder/tests/parcel_fuzzer/binder.cpp b/libs/binder/tests/parcel_fuzzer/binder.cpp
index d9f1d75..e378b86 100644
--- a/libs/binder/tests/parcel_fuzzer/binder.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder.cpp
@@ -361,6 +361,20 @@
         FUZZ_LOG() << " status: " << status  << " result: " << result;
     },
     [] (const ::android::Parcel& p, FuzzedDataProvider& /*provider*/) {
+        FUZZ_LOG() << "about to call hasBinders() with status";
+        bool result;
+        status_t status = p.hasBinders(&result);
+        FUZZ_LOG() << " status: " << status  << " result: " << result;
+    },
+    [] (const ::android::Parcel& p, FuzzedDataProvider& /*provider*/) {
+        FUZZ_LOG() << "about to call hasBindersInRange() with status";
+        size_t offset = p.readUint32();
+        size_t length = p.readUint32();
+        bool result;
+        status_t status = p.hasBindersInRange(offset, length, &result);
+        FUZZ_LOG() << " status: " << status  << " result: " << result;
+    },
+    [] (const ::android::Parcel& p, FuzzedDataProvider& /*provider*/) {
         FUZZ_LOG() << "about to call compareDataInRange() with status";
         size_t thisOffset = p.readUint32();
         size_t otherOffset = p.readUint32();
diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp
index 394a000..50c05f4 100644
--- a/libs/graphicsenv/GraphicsEnv.cpp
+++ b/libs/graphicsenv/GraphicsEnv.cpp
@@ -265,7 +265,8 @@
 
     auto vndkNamespace = android_get_exported_namespace("vndk");
     if (!vndkNamespace) {
-        return nullptr;
+        mDriverNamespace = nullptr;
+        return mDriverNamespace;
     }
 
     mDriverNamespace = android_create_namespace("updatable gfx driver",
@@ -617,7 +618,8 @@
 
     auto vndkNamespace = android_get_exported_namespace("vndk");
     if (!vndkNamespace) {
-        return nullptr;
+        mAngleNamespace = nullptr;
+        return mAngleNamespace;
     }
 
     if (!linkDriverNamespaceLocked(mAngleNamespace, vndkNamespace, "")) {
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index 9e0ce1d..d58fb42 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -60,6 +60,45 @@
     return !isFromSource(source, AINPUT_SOURCE_CLASS_POINTER);
 }
 
+int32_t resolveActionForSplitMotionEvent(
+        int32_t action, int32_t flags, const std::vector<PointerProperties>& pointerProperties,
+        const std::vector<PointerProperties>& splitPointerProperties) {
+    LOG_ALWAYS_FATAL_IF(splitPointerProperties.empty());
+    const auto maskedAction = MotionEvent::getActionMasked(action);
+    if (maskedAction != AMOTION_EVENT_ACTION_POINTER_DOWN &&
+        maskedAction != AMOTION_EVENT_ACTION_POINTER_UP) {
+        // The action is unaffected by splitting this motion event.
+        return action;
+    }
+    const auto actionIndex = MotionEvent::getActionIndex(action);
+    if (CC_UNLIKELY(actionIndex >= pointerProperties.size())) {
+        LOG(FATAL) << "Action index is out of bounds, index: " << actionIndex;
+    }
+
+    const auto affectedPointerId = pointerProperties[actionIndex].id;
+    std::optional<uint32_t> splitActionIndex;
+    for (uint32_t i = 0; i < splitPointerProperties.size(); i++) {
+        if (affectedPointerId == splitPointerProperties[i].id) {
+            splitActionIndex = i;
+            break;
+        }
+    }
+    if (!splitActionIndex.has_value()) {
+        // The affected pointer is not part of the split motion event.
+        return AMOTION_EVENT_ACTION_MOVE;
+    }
+
+    if (splitPointerProperties.size() > 1) {
+        return maskedAction | (*splitActionIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+    }
+
+    if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
+        return ((flags & AMOTION_EVENT_FLAG_CANCELED) != 0) ? AMOTION_EVENT_ACTION_CANCEL
+                                                            : AMOTION_EVENT_ACTION_UP;
+    }
+    return AMOTION_EVENT_ACTION_DOWN;
+}
+
 } // namespace
 
 const char* motionClassificationToString(MotionClassification classification) {
@@ -584,6 +623,28 @@
     }
 }
 
+void MotionEvent::splitFrom(const android::MotionEvent& other,
+                            std::bitset<MAX_POINTER_ID + 1> splitPointerIds, int32_t newEventId) {
+    // TODO(b/327503168): The down time should be a parameter to the split function, because only
+    //   the caller can know when the first event went down on the target.
+    const nsecs_t splitDownTime = other.mDownTime;
+
+    auto [action, pointerProperties, pointerCoords] =
+            split(other.getAction(), other.getFlags(), other.getHistorySize(),
+                  other.mPointerProperties, other.mSamplePointerCoords, splitPointerIds);
+
+    // Initialize the event with zero pointers, and manually set the split pointers.
+    initialize(newEventId, other.mDeviceId, other.mSource, other.mDisplayId, /*hmac=*/{}, action,
+               other.mActionButton, other.mFlags, other.mEdgeFlags, other.mMetaState,
+               other.mButtonState, other.mClassification, other.mTransform, other.mXPrecision,
+               other.mYPrecision, other.mRawXCursorPosition, other.mRawYCursorPosition,
+               other.mRawTransform, splitDownTime, other.getEventTime(), /*pointerCount=*/0,
+               pointerProperties.data(), pointerCoords.data());
+    mPointerProperties = std::move(pointerProperties);
+    mSamplePointerCoords = std::move(pointerCoords);
+    mSampleEventTimes = other.mSampleEventTimes;
+}
+
 void MotionEvent::addSample(
         int64_t eventTime,
         const PointerCoords* pointerCoords) {
@@ -934,6 +995,45 @@
     return android::base::StringPrintf("%" PRId32, action);
 }
 
+std::tuple<int32_t, std::vector<PointerProperties>, std::vector<PointerCoords>> MotionEvent::split(
+        int32_t action, int32_t flags, int32_t historySize,
+        const std::vector<PointerProperties>& pointerProperties,
+        const std::vector<PointerCoords>& pointerCoords,
+        std::bitset<MAX_POINTER_ID + 1> splitPointerIds) {
+    LOG_ALWAYS_FATAL_IF(!splitPointerIds.any());
+    const auto pointerCount = pointerProperties.size();
+    LOG_ALWAYS_FATAL_IF(pointerCoords.size() != (pointerCount * (historySize + 1)));
+    const auto splitCount = splitPointerIds.count();
+
+    std::vector<PointerProperties> splitPointerProperties;
+    std::vector<PointerCoords> splitPointerCoords;
+
+    for (uint32_t i = 0; i < pointerCount; i++) {
+        if (splitPointerIds.test(pointerProperties[i].id)) {
+            splitPointerProperties.emplace_back(pointerProperties[i]);
+        }
+    }
+    for (uint32_t i = 0; i < pointerCoords.size(); i++) {
+        if (splitPointerIds.test(pointerProperties[i % pointerCount].id)) {
+            splitPointerCoords.emplace_back(pointerCoords[i]);
+        }
+    }
+    LOG_ALWAYS_FATAL_IF(splitPointerCoords.size() !=
+                        (splitPointerProperties.size() * (historySize + 1)));
+
+    if (CC_UNLIKELY(splitPointerProperties.size() != splitCount)) {
+        LOG(FATAL) << "Cannot split MotionEvent: Requested splitting " << splitCount
+                   << " pointers from the original event, but the original event only contained "
+                   << splitPointerProperties.size() << " of those pointers.";
+    }
+
+    // TODO(b/327503168): Verify the splitDownTime here once it is used correctly.
+
+    const auto splitAction = resolveActionForSplitMotionEvent(action, flags, pointerProperties,
+                                                              splitPointerProperties);
+    return {splitAction, splitPointerProperties, splitPointerCoords};
+}
+
 // Apply the given transformation to the point without checking whether the entire transform
 // should be disregarded altogether for the provided source.
 static inline vec2 calculateTransformedXYUnchecked(uint32_t source, const ui::Transform& transform,
diff --git a/libs/input/MotionPredictorMetricsManager.cpp b/libs/input/MotionPredictorMetricsManager.cpp
index 0412d08..6872af2 100644
--- a/libs/input/MotionPredictorMetricsManager.cpp
+++ b/libs/input/MotionPredictorMetricsManager.cpp
@@ -113,7 +113,12 @@
 // Adds new predictions to mRecentPredictions and maintains the invariant that elements are
 // sorted in ascending order of targetTimestamp.
 void MotionPredictorMetricsManager::onPredict(const MotionEvent& predictionEvent) {
-    for (size_t i = 0; i < predictionEvent.getHistorySize() + 1; ++i) {
+    const size_t numPredictions = predictionEvent.getHistorySize() + 1;
+    if (numPredictions > mMaxNumPredictions) {
+        LOG(WARNING) << "numPredictions (" << numPredictions << ") > mMaxNumPredictions ("
+                     << mMaxNumPredictions << "). Ignoring extra predictions in metrics.";
+    }
+    for (size_t i = 0; (i < numPredictions) && (i < mMaxNumPredictions); ++i) {
         // Convert MotionEvent to PredictionPoint.
         const PointerCoords* coords =
                 predictionEvent.getHistoricalRawPointerCoords(/*pointerIndex=*/0, i);
@@ -325,42 +330,44 @@
             mAtomFields[i].highVelocityOffTrajectoryRmse =
                     static_cast<int>(offTrajectoryRmse * 1000);
         }
+    }
 
-        // Scale-invariant errors: reported only for the last time bucket, where the values
-        // represent an average across all time buckets.
-        if (i + 1 == mMaxNumPredictions) {
-            // Compute error averages.
-            float alongTrajectoryRmseSum = 0;
-            float offTrajectoryRmseSum = 0;
-            for (size_t j = 0; j < mAggregatedMetrics.size(); ++j) {
-                // If we have general errors (checked above), we should always also have
-                // scale-invariant errors.
-                LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantErrorsCount == 0,
-                                    "mAggregatedMetrics[%zu].scaleInvariantErrorsCount is 0", j);
-
-                LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse < 0,
-                                    "mAggregatedMetrics[%zu].scaleInvariantAlongTrajectorySse = %f "
-                                    "should not be negative",
-                                    j, mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse);
-                alongTrajectoryRmseSum +=
-                        std::sqrt(mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse /
-                                  mAggregatedMetrics[j].scaleInvariantErrorsCount);
-
-                LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantOffTrajectorySse < 0,
-                                    "mAggregatedMetrics[%zu].scaleInvariantOffTrajectorySse = %f "
-                                    "should not be negative",
-                                    j, mAggregatedMetrics[j].scaleInvariantOffTrajectorySse);
-                offTrajectoryRmseSum +=
-                        std::sqrt(mAggregatedMetrics[j].scaleInvariantOffTrajectorySse /
-                                  mAggregatedMetrics[j].scaleInvariantErrorsCount);
+    // Scale-invariant errors: the average scale-invariant error across all time buckets
+    // is reported in the last time bucket.
+    {
+        // Compute error averages.
+        float alongTrajectoryRmseSum = 0;
+        float offTrajectoryRmseSum = 0;
+        int bucket_count = 0;
+        for (size_t j = 0; j < mAggregatedMetrics.size(); ++j) {
+            if (mAggregatedMetrics[j].scaleInvariantErrorsCount == 0) {
+                continue;
             }
 
-            const float averageAlongTrajectoryRmse =
-                    alongTrajectoryRmseSum / mAggregatedMetrics.size();
+            LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse < 0,
+                                "mAggregatedMetrics[%zu].scaleInvariantAlongTrajectorySse = %f "
+                                "should not be negative",
+                                j, mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse);
+            alongTrajectoryRmseSum +=
+                    std::sqrt(mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse /
+                              mAggregatedMetrics[j].scaleInvariantErrorsCount);
+
+            LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantOffTrajectorySse < 0,
+                                "mAggregatedMetrics[%zu].scaleInvariantOffTrajectorySse = %f "
+                                "should not be negative",
+                                j, mAggregatedMetrics[j].scaleInvariantOffTrajectorySse);
+            offTrajectoryRmseSum += std::sqrt(mAggregatedMetrics[j].scaleInvariantOffTrajectorySse /
+                                              mAggregatedMetrics[j].scaleInvariantErrorsCount);
+
+            ++bucket_count;
+        }
+
+        if (bucket_count > 0) {
+            const float averageAlongTrajectoryRmse = alongTrajectoryRmseSum / bucket_count;
             mAtomFields.back().scaleInvariantAlongTrajectoryRmse =
                     static_cast<int>(averageAlongTrajectoryRmse * 1000);
 
-            const float averageOffTrajectoryRmse = offTrajectoryRmseSum / mAggregatedMetrics.size();
+            const float averageOffTrajectoryRmse = offTrajectoryRmseSum / bucket_count;
             mAtomFields.back().scaleInvariantOffTrajectoryRmse =
                     static_cast<int>(averageOffTrajectoryRmse * 1000);
         }
diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp
index a965573..540766d 100644
--- a/libs/input/tests/InputEvent_test.cpp
+++ b/libs/input/tests/InputEvent_test.cpp
@@ -23,6 +23,7 @@
 #include <gtest/gtest.h>
 #include <gui/constants.h>
 #include <input/Input.h>
+#include <input/InputEventBuilders.h>
 
 namespace android {
 
@@ -31,6 +32,18 @@
 
 static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
 
+static constexpr auto POINTER_0_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+static constexpr auto POINTER_1_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+static constexpr auto POINTER_0_UP =
+        AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+static constexpr auto POINTER_1_UP =
+        AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
 class BaseTest : public testing::Test {
 protected:
     static constexpr std::array<uint8_t, 32> HMAC = {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10,
@@ -554,6 +567,145 @@
     ASSERT_EQ(event.getX(0), copy.getX(0));
 }
 
+TEST_F(MotionEventTest, SplitPointerDown) {
+    MotionEvent event = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                .downTime(ARBITRARY_DOWN_TIME)
+                                .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER).x(4).y(4))
+                                .pointer(PointerBuilder(/*id=*/6, ToolType::FINGER).x(6).y(6))
+                                .pointer(PointerBuilder(/*id=*/8, ToolType::FINGER).x(8).y(8))
+                                .build();
+
+    MotionEvent splitDown;
+    std::bitset<MAX_POINTER_ID + 1> splitDownIds{};
+    splitDownIds.set(6, true);
+    splitDown.splitFrom(event, splitDownIds, /*eventId=*/42);
+    ASSERT_EQ(splitDown.getAction(), AMOTION_EVENT_ACTION_DOWN);
+    ASSERT_EQ(splitDown.getPointerCount(), 1u);
+    ASSERT_EQ(splitDown.getPointerId(0), 6);
+    ASSERT_EQ(splitDown.getX(0), 6);
+    ASSERT_EQ(splitDown.getY(0), 6);
+
+    MotionEvent splitPointerDown;
+    std::bitset<MAX_POINTER_ID + 1> splitPointerDownIds{};
+    splitPointerDownIds.set(6, true);
+    splitPointerDownIds.set(8, true);
+    splitPointerDown.splitFrom(event, splitPointerDownIds, /*eventId=*/42);
+    ASSERT_EQ(splitPointerDown.getAction(), POINTER_0_DOWN);
+    ASSERT_EQ(splitPointerDown.getPointerCount(), 2u);
+    ASSERT_EQ(splitPointerDown.getPointerId(0), 6);
+    ASSERT_EQ(splitPointerDown.getX(0), 6);
+    ASSERT_EQ(splitPointerDown.getY(0), 6);
+    ASSERT_EQ(splitPointerDown.getPointerId(1), 8);
+    ASSERT_EQ(splitPointerDown.getX(1), 8);
+    ASSERT_EQ(splitPointerDown.getY(1), 8);
+
+    MotionEvent splitMove;
+    std::bitset<MAX_POINTER_ID + 1> splitMoveIds{};
+    splitMoveIds.set(4, true);
+    splitMove.splitFrom(event, splitMoveIds, /*eventId=*/43);
+    ASSERT_EQ(splitMove.getAction(), AMOTION_EVENT_ACTION_MOVE);
+    ASSERT_EQ(splitMove.getPointerCount(), 1u);
+    ASSERT_EQ(splitMove.getPointerId(0), 4);
+    ASSERT_EQ(splitMove.getX(0), 4);
+    ASSERT_EQ(splitMove.getY(0), 4);
+}
+
+TEST_F(MotionEventTest, SplitPointerUp) {
+    MotionEvent event = MotionEventBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                .downTime(ARBITRARY_DOWN_TIME)
+                                .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER).x(4).y(4))
+                                .pointer(PointerBuilder(/*id=*/6, ToolType::FINGER).x(6).y(6))
+                                .pointer(PointerBuilder(/*id=*/8, ToolType::FINGER).x(8).y(8))
+                                .build();
+
+    MotionEvent splitUp;
+    std::bitset<MAX_POINTER_ID + 1> splitUpIds{};
+    splitUpIds.set(4, true);
+    splitUp.splitFrom(event, splitUpIds, /*eventId=*/42);
+    ASSERT_EQ(splitUp.getAction(), AMOTION_EVENT_ACTION_UP);
+    ASSERT_EQ(splitUp.getPointerCount(), 1u);
+    ASSERT_EQ(splitUp.getPointerId(0), 4);
+    ASSERT_EQ(splitUp.getX(0), 4);
+    ASSERT_EQ(splitUp.getY(0), 4);
+
+    MotionEvent splitPointerUp;
+    std::bitset<MAX_POINTER_ID + 1> splitPointerUpIds{};
+    splitPointerUpIds.set(4, true);
+    splitPointerUpIds.set(8, true);
+    splitPointerUp.splitFrom(event, splitPointerUpIds, /*eventId=*/42);
+    ASSERT_EQ(splitPointerUp.getAction(), POINTER_0_UP);
+    ASSERT_EQ(splitPointerUp.getPointerCount(), 2u);
+    ASSERT_EQ(splitPointerUp.getPointerId(0), 4);
+    ASSERT_EQ(splitPointerUp.getX(0), 4);
+    ASSERT_EQ(splitPointerUp.getY(0), 4);
+    ASSERT_EQ(splitPointerUp.getPointerId(1), 8);
+    ASSERT_EQ(splitPointerUp.getX(1), 8);
+    ASSERT_EQ(splitPointerUp.getY(1), 8);
+
+    MotionEvent splitMove;
+    std::bitset<MAX_POINTER_ID + 1> splitMoveIds{};
+    splitMoveIds.set(6, true);
+    splitMoveIds.set(8, true);
+    splitMove.splitFrom(event, splitMoveIds, /*eventId=*/43);
+    ASSERT_EQ(splitMove.getAction(), AMOTION_EVENT_ACTION_MOVE);
+    ASSERT_EQ(splitMove.getPointerCount(), 2u);
+    ASSERT_EQ(splitMove.getPointerId(0), 6);
+    ASSERT_EQ(splitMove.getX(0), 6);
+    ASSERT_EQ(splitMove.getY(0), 6);
+    ASSERT_EQ(splitMove.getPointerId(1), 8);
+    ASSERT_EQ(splitMove.getX(1), 8);
+    ASSERT_EQ(splitMove.getY(1), 8);
+}
+
+TEST_F(MotionEventTest, SplitPointerUpCancel) {
+    MotionEvent event = MotionEventBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                .downTime(ARBITRARY_DOWN_TIME)
+                                .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER).x(4).y(4))
+                                .pointer(PointerBuilder(/*id=*/6, ToolType::FINGER).x(6).y(6))
+                                .pointer(PointerBuilder(/*id=*/8, ToolType::FINGER).x(8).y(8))
+                                .addFlag(AMOTION_EVENT_FLAG_CANCELED)
+                                .build();
+
+    MotionEvent splitUp;
+    std::bitset<MAX_POINTER_ID + 1> splitUpIds{};
+    splitUpIds.set(6, true);
+    splitUp.splitFrom(event, splitUpIds, /*eventId=*/42);
+    ASSERT_EQ(splitUp.getAction(), AMOTION_EVENT_ACTION_CANCEL);
+    ASSERT_EQ(splitUp.getPointerCount(), 1u);
+    ASSERT_EQ(splitUp.getPointerId(0), 6);
+    ASSERT_EQ(splitUp.getX(0), 6);
+    ASSERT_EQ(splitUp.getY(0), 6);
+}
+
+TEST_F(MotionEventTest, SplitPointerMove) {
+    MotionEvent event = MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                .downTime(ARBITRARY_DOWN_TIME)
+                                .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER).x(4).y(4))
+                                .pointer(PointerBuilder(/*id=*/6, ToolType::FINGER).x(6).y(6))
+                                .pointer(PointerBuilder(/*id=*/8, ToolType::FINGER).x(8).y(8))
+                                .transform(ui::Transform(ui::Transform::ROT_90, 100, 100))
+                                .rawTransform(ui::Transform(ui::Transform::FLIP_H, 50, 50))
+                                .build();
+
+    MotionEvent splitMove;
+    std::bitset<MAX_POINTER_ID + 1> splitMoveIds{};
+    splitMoveIds.set(4, true);
+    splitMoveIds.set(8, true);
+    splitMove.splitFrom(event, splitMoveIds, /*eventId=*/42);
+    ASSERT_EQ(splitMove.getAction(), AMOTION_EVENT_ACTION_MOVE);
+    ASSERT_EQ(splitMove.getPointerCount(), 2u);
+    ASSERT_EQ(splitMove.getPointerId(0), 4);
+    ASSERT_EQ(splitMove.getX(0), event.getX(0));
+    ASSERT_EQ(splitMove.getY(0), event.getY(0));
+    ASSERT_EQ(splitMove.getRawX(0), event.getRawX(0));
+    ASSERT_EQ(splitMove.getRawY(0), event.getRawY(0));
+    ASSERT_EQ(splitMove.getPointerId(1), 8);
+    ASSERT_EQ(splitMove.getX(1), event.getX(2));
+    ASSERT_EQ(splitMove.getY(1), event.getY(2));
+    ASSERT_EQ(splitMove.getRawX(1), event.getRawX(2));
+    ASSERT_EQ(splitMove.getRawY(1), event.getRawY(2));
+}
+
 TEST_F(MotionEventTest, OffsetLocation) {
     MotionEvent event;
     initializeEventWithHistory(&event);
diff --git a/libs/input/tests/MotionPredictorMetricsManager_test.cpp b/libs/input/tests/MotionPredictorMetricsManager_test.cpp
index 31cc145..cc41eeb 100644
--- a/libs/input/tests/MotionPredictorMetricsManager_test.cpp
+++ b/libs/input/tests/MotionPredictorMetricsManager_test.cpp
@@ -238,14 +238,17 @@
 
 // --- Ground-truth-generation helper functions. ---
 
+// Generates numPoints ground truth points with values equal to those of the given
+// GroundTruthPoint, and with consecutive timestamps separated by the given inputInterval.
 std::vector<GroundTruthPoint> generateConstantGroundTruthPoints(
-        const GroundTruthPoint& groundTruthPoint, size_t numPoints) {
+        const GroundTruthPoint& groundTruthPoint, size_t numPoints,
+        nsecs_t inputInterval = TEST_PREDICTION_INTERVAL_NANOS) {
     std::vector<GroundTruthPoint> groundTruthPoints;
     nsecs_t timestamp = groundTruthPoint.timestamp;
     for (size_t i = 0; i < numPoints; ++i) {
         groundTruthPoints.emplace_back(groundTruthPoint);
         groundTruthPoints.back().timestamp = timestamp;
-        timestamp += TEST_PREDICTION_INTERVAL_NANOS;
+        timestamp += inputInterval;
     }
     return groundTruthPoints;
 }
@@ -280,7 +283,8 @@
     const GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(10, 20), .pressure = 0.3f},
                                             .timestamp = TEST_INITIAL_TIMESTAMP};
     const std::vector<GroundTruthPoint> groundTruthPoints =
-            generateConstantGroundTruthPoints(groundTruthPoint, /*numPoints=*/3);
+            generateConstantGroundTruthPoints(groundTruthPoint, /*numPoints=*/3,
+                                              /*inputInterval=*/10);
 
     ASSERT_EQ(3u, groundTruthPoints.size());
     // First point.
@@ -290,11 +294,11 @@
     // Second point.
     EXPECT_EQ(groundTruthPoints[1].position, groundTruthPoint.position);
     EXPECT_EQ(groundTruthPoints[1].pressure, groundTruthPoint.pressure);
-    EXPECT_GT(groundTruthPoints[1].timestamp, groundTruthPoints[0].timestamp);
+    EXPECT_EQ(groundTruthPoints[1].timestamp, groundTruthPoint.timestamp + 10);
     // Third point.
     EXPECT_EQ(groundTruthPoints[2].position, groundTruthPoint.position);
     EXPECT_EQ(groundTruthPoints[2].pressure, groundTruthPoint.pressure);
-    EXPECT_GT(groundTruthPoints[2].timestamp, groundTruthPoints[1].timestamp);
+    EXPECT_EQ(groundTruthPoints[2].timestamp, groundTruthPoint.timestamp + 20);
 }
 
 TEST(GenerateCircularArcGroundTruthTest, StraightLineUpwards) {
@@ -333,16 +337,19 @@
 
 // --- Prediction-generation helper functions. ---
 
-// Creates a sequence of predictions with values equal to those of the given GroundTruthPoint.
-std::vector<PredictionPoint> generateConstantPredictions(const GroundTruthPoint& groundTruthPoint) {
+// Generates TEST_MAX_NUM_PREDICTIONS predictions with values equal to those of the given
+// GroundTruthPoint, and with consecutive timestamps separated by the given predictionInterval.
+std::vector<PredictionPoint> generateConstantPredictions(
+        const GroundTruthPoint& groundTruthPoint,
+        nsecs_t predictionInterval = TEST_PREDICTION_INTERVAL_NANOS) {
     std::vector<PredictionPoint> predictions;
-    nsecs_t predictionTimestamp = groundTruthPoint.timestamp + TEST_PREDICTION_INTERVAL_NANOS;
+    nsecs_t predictionTimestamp = groundTruthPoint.timestamp + predictionInterval;
     for (size_t j = 0; j < TEST_MAX_NUM_PREDICTIONS; ++j) {
         predictions.push_back(PredictionPoint{{.position = groundTruthPoint.position,
                                                .pressure = groundTruthPoint.pressure},
                                               .originTimestamp = groundTruthPoint.timestamp,
                                               .targetTimestamp = predictionTimestamp});
-        predictionTimestamp += TEST_PREDICTION_INTERVAL_NANOS;
+        predictionTimestamp += predictionInterval;
     }
     return predictions;
 }
@@ -375,8 +382,9 @@
 TEST(GeneratePredictionsTest, GenerateConstantPredictions) {
     const GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(10, 20), .pressure = 0.3f},
                                             .timestamp = TEST_INITIAL_TIMESTAMP};
+    const nsecs_t predictionInterval = 10;
     const std::vector<PredictionPoint> predictionPoints =
-            generateConstantPredictions(groundTruthPoint);
+            generateConstantPredictions(groundTruthPoint, predictionInterval);
 
     ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, predictionPoints.size());
     for (size_t i = 0; i < predictionPoints.size(); ++i) {
@@ -385,8 +393,7 @@
         EXPECT_THAT(predictionPoints[i].pressure, FloatNear(groundTruthPoint.pressure, 1e-6));
         EXPECT_EQ(predictionPoints[i].originTimestamp, groundTruthPoint.timestamp);
         EXPECT_EQ(predictionPoints[i].targetTimestamp,
-                  groundTruthPoint.timestamp +
-                          static_cast<nsecs_t>(i + 1) * TEST_PREDICTION_INTERVAL_NANOS);
+                  TEST_INITIAL_TIMESTAMP + static_cast<nsecs_t>(i + 1) * predictionInterval);
     }
 }
 
@@ -678,12 +685,9 @@
 //  • groundTruthPoints: chronologically-ordered ground truth points, with at least 2 elements.
 //  • predictionPoints: the first index points to a vector of predictions corresponding to the
 //    source ground truth point with the same index.
-//     - The first element should be empty, because there are not expected to be predictions until
-//       we have received 2 ground truth points.
-//     - The last element may be empty, because there will be no future ground truth points to
-//       associate with those predictions (if not empty, it will be ignored).
+//     - For empty prediction vectors, MetricsManager::onPredict will not be called.
 //     - To test all prediction buckets, there should be at least TEST_MAX_NUM_PREDICTIONS non-empty
-//       prediction sets (that is, excluding the first and last). Thus, groundTruthPoints and
+//       prediction vectors (that is, excluding the first and last). Thus, groundTruthPoints and
 //       predictionPoints should have size at least TEST_MAX_NUM_PREDICTIONS + 2.
 //
 // When the function returns, outReportedAtomFields will contain the reported AtomFields.
@@ -697,19 +701,12 @@
                                                  createMockReportAtomFunction(
                                                          outReportedAtomFields));
 
-    // Validate structure of groundTruthPoints and predictionPoints.
-    ASSERT_EQ(predictionPoints.size(), groundTruthPoints.size());
     ASSERT_GE(groundTruthPoints.size(), 2u);
-    ASSERT_EQ(predictionPoints[0].size(), 0u);
-    for (size_t i = 1; i + 1 < predictionPoints.size(); ++i) {
-        SCOPED_TRACE(testing::Message() << "i = " << i);
-        ASSERT_EQ(predictionPoints[i].size(), TEST_MAX_NUM_PREDICTIONS);
-    }
+    ASSERT_EQ(predictionPoints.size(), groundTruthPoints.size());
 
-    // Pass ground truth points and predictions (for all except first and last ground truth).
     for (size_t i = 0; i < groundTruthPoints.size(); ++i) {
         metricsManager.onRecord(makeMotionEvent(groundTruthPoints[i]));
-        if ((i > 0) && (i + 1 < predictionPoints.size())) {
+        if (!predictionPoints[i].empty()) {
             metricsManager.onPredict(makeMotionEvent(predictionPoints[i]));
         }
     }
@@ -738,7 +735,7 @@
 // Perfect predictions test:
 //  • Input: constant input events, perfect predictions matching the input events.
 //  • Expectation: all error metrics should be zero, or NO_DATA_SENTINEL for "unreported" metrics.
-//    (For example, scale-invariant errors are only reported for the final time bucket.)
+//    (For example, scale-invariant errors are only reported for the last time bucket.)
 TEST(MotionPredictorMetricsManagerTest, ConstantGroundTruthPerfectPredictions) {
     GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(10.0f, 20.0f), .pressure = 0.6f},
                                       .timestamp = TEST_INITIAL_TIMESTAMP};
@@ -977,5 +974,35 @@
     }
 }
 
+// Robustness test:
+//  • Input: input events separated by a significantly greater time interval than the interval
+//    between predictions.
+//  • Expectation: the MetricsManager should not crash in this case. (No assertions are made about
+//    the resulting metrics.)
+//
+// In practice, this scenario could arise either if the input and prediction intervals are
+// mismatched, or if input events are missing (dropped or skipped for some reason).
+TEST(MotionPredictorMetricsManagerTest, MismatchedInputAndPredictionInterval) {
+    // Create two ground truth points separated by MAX_NUM_PREDICTIONS * PREDICTION_INTERVAL,
+    // so that the second ground truth point corresponds to the last prediction bucket. This
+    // ensures that the scale-invariant error codepath will be run, giving full code coverage.
+    GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(0.0f, 0.0f), .pressure = 0.5f},
+                                      .timestamp = TEST_INITIAL_TIMESTAMP};
+    const nsecs_t inputInterval = TEST_MAX_NUM_PREDICTIONS * TEST_PREDICTION_INTERVAL_NANOS;
+    const std::vector<GroundTruthPoint> groundTruthPoints =
+            generateConstantGroundTruthPoints(groundTruthPoint, /*numPoints=*/2, inputInterval);
+
+    // Create predictions separated by the prediction interval.
+    std::vector<std::vector<PredictionPoint>> predictionPoints;
+    for (size_t i = 0; i < groundTruthPoints.size(); ++i) {
+        predictionPoints.push_back(
+                generateConstantPredictions(groundTruthPoints[i], TEST_PREDICTION_INTERVAL_NANOS));
+    }
+
+    // Test that we can run the MetricsManager without crashing.
+    std::vector<AtomFields> reportedAtomFields;
+    runMetricsManager(groundTruthPoints, predictionPoints, reportedAtomFields);
+}
+
 } // namespace
 } // namespace android
diff --git a/libs/math/include/math/mat4.h b/libs/math/include/math/mat4.h
index 6119ba7..c630d97 100644
--- a/libs/math/include/math/mat4.h
+++ b/libs/math/include/math/mat4.h
@@ -34,6 +34,14 @@
 #define CONSTEXPR
 #endif
 
+#ifdef _WIN32
+// windows.h contains obsolete defines of 'near' and 'far' for systems using
+// legacy 16 bit pointers. Undefine them to avoid conflicting with the usage of
+// 'near' and 'far' in this file.
+#undef near
+#undef far
+#endif
+
 namespace android {
 // -------------------------------------------------------------------------------------
 namespace details {
diff --git a/libs/nativedisplay/Android.bp b/libs/nativedisplay/Android.bp
index f7f20b4..03f4f39 100644
--- a/libs/nativedisplay/Android.bp
+++ b/libs/nativedisplay/Android.bp
@@ -34,7 +34,13 @@
 
 cc_library_headers {
     name: "libnativedisplay_headers",
+    host_supported: true,
     export_include_dirs: ["include"],
+    target: {
+        windows: {
+            enabled: true,
+        },
+    },
 }
 
 cc_library_shared {
diff --git a/libs/nativewindow/Android.bp b/libs/nativewindow/Android.bp
index 0e90327..8558074 100644
--- a/libs/nativewindow/Android.bp
+++ b/libs/nativewindow/Android.bp
@@ -54,6 +54,11 @@
         "test_com.android.media.swcodec",
     ],
     host_supported: true,
+    target: {
+        windows: {
+            enabled: true,
+        },
+    },
 }
 
 ndk_library {
diff --git a/libs/nativewindow/include/android/hardware_buffer.h b/libs/nativewindow/include/android/hardware_buffer.h
index e0e30c3..6fcb3a4 100644
--- a/libs/nativewindow/include/android/hardware_buffer.h
+++ b/libs/nativewindow/include/android/hardware_buffer.h
@@ -52,6 +52,10 @@
 #include <inttypes.h>
 #include <sys/cdefs.h>
 
+#if !defined(__INTRODUCED_IN)
+#define __INTRODUCED_IN(__api_level) /* nothing */
+#endif
+
 __BEGIN_DECLS
 
 // clang-format off
diff --git a/libs/sensorprivacy/Android.bp b/libs/sensorprivacy/Android.bp
index 1e7e707..00514c4 100644
--- a/libs/sensorprivacy/Android.bp
+++ b/libs/sensorprivacy/Android.bp
@@ -57,7 +57,6 @@
 filegroup {
     name: "libsensorprivacy_aidl",
     srcs: [
-        "aidl/android/hardware/CameraPrivacyAllowlistEntry.aidl",
         "aidl/android/hardware/ISensorPrivacyListener.aidl",
         "aidl/android/hardware/ISensorPrivacyManager.aidl",
     ],
diff --git a/libs/sensorprivacy/SensorPrivacyManager.cpp b/libs/sensorprivacy/SensorPrivacyManager.cpp
index fe93786..3f3ad93 100644
--- a/libs/sensorprivacy/SensorPrivacyManager.cpp
+++ b/libs/sensorprivacy/SensorPrivacyManager.cpp
@@ -155,10 +155,9 @@
     return DISABLED;
 }
 
-std::vector<hardware::CameraPrivacyAllowlistEntry>
-        SensorPrivacyManager::getCameraPrivacyAllowlist(){
+std::vector<String16> SensorPrivacyManager::getCameraPrivacyAllowlist(){
     sp<hardware::ISensorPrivacyManager> service = getService();
-    std::vector<hardware::CameraPrivacyAllowlistEntry> result;
+    std::vector<String16> result;
     if (service != nullptr) {
         service->getCameraPrivacyAllowlist(&result);
         return result;
diff --git a/libs/sensorprivacy/aidl/android/hardware/CameraPrivacyAllowlistEntry.aidl b/libs/sensorprivacy/aidl/android/hardware/CameraPrivacyAllowlistEntry.aidl
deleted file mode 100644
index 03e1537..0000000
--- a/libs/sensorprivacy/aidl/android/hardware/CameraPrivacyAllowlistEntry.aidl
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware;
-
-parcelable CameraPrivacyAllowlistEntry {
-    String packageName;
-    boolean isMandatory;
-}
diff --git a/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl b/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl
index b6bd39e..f707187 100644
--- a/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl
+++ b/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl
@@ -16,7 +16,6 @@
 
 package android.hardware;
 
-import android.hardware.CameraPrivacyAllowlistEntry;
 import android.hardware.ISensorPrivacyListener;
 
 /** @hide */
@@ -43,7 +42,7 @@
 
     void setToggleSensorPrivacyForProfileGroup(int userId, int source, int sensor, boolean enable);
 
-    List<CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist();
+    List<String> getCameraPrivacyAllowlist();
 
     int getToggleSensorPrivacyState(int toggleType, int sensor);
 
diff --git a/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h b/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h
index 9e97e16..8935b76 100644
--- a/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h
+++ b/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h
@@ -45,9 +45,7 @@
     enum {
         ENABLED = 1,
         DISABLED = 2,
-        AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS = 3,
-        AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS = 4,
-        AUTOMOTIVE_DRIVER_ASSISTANCE_APPS = 5
+        ENABLED_EXCEPT_ALLOWLISTED_APPS = 3
     };
 
     SensorPrivacyManager();
@@ -62,7 +60,7 @@
     bool isToggleSensorPrivacyEnabled(int toggleType, int sensor);
     status_t isToggleSensorPrivacyEnabled(int toggleType, int sensor, bool &result);
     int getToggleSensorPrivacyState(int toggleType, int sensor);
-    std::vector<hardware::CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist();
+    std::vector<String16> getCameraPrivacyAllowlist();
     bool isCameraPrivacyEnabled(String16 packageName);
 
     status_t linkToDeath(const sp<IBinder::DeathRecipient>& recipient);
diff --git a/services/batteryservice/include/batteryservice/BatteryService.h b/services/batteryservice/include/batteryservice/BatteryService.h
index 654c903..f697f94 100644
--- a/services/batteryservice/include/batteryservice/BatteryService.h
+++ b/services/batteryservice/include/batteryservice/BatteryService.h
@@ -34,9 +34,9 @@
     BATTERY_PROP_CAPACITY = 4, // equals BATTERY_PROPERTY_CAPACITY
     BATTERY_PROP_ENERGY_COUNTER = 5, // equals BATTERY_PROPERTY_ENERGY_COUNTER
     BATTERY_PROP_BATTERY_STATUS = 6, // equals BATTERY_PROPERTY_BATTERY_STATUS
-    BATTERY_PROP_CHARGING_POLICY = 7, // equals BATTERY_PROPERTY_CHARGING_POLICY
-    BATTERY_PROP_MANUFACTURING_DATE = 8, // equals BATTERY_PROPERTY_MANUFACTURING_DATE
-    BATTERY_PROP_FIRST_USAGE_DATE = 9, // equals BATTERY_PROPERTY_FIRST_USAGE_DATE
+    BATTERY_PROP_MANUFACTURING_DATE = 7, // equals BATTERY_PROPERTY_MANUFACTURING_DATE
+    BATTERY_PROP_FIRST_USAGE_DATE = 8, // equals BATTERY_PROPERTY_FIRST_USAGE_DATE
+    BATTERY_PROP_CHARGING_POLICY = 9, // equals BATTERY_PROPERTY_CHARGING_POLICY
     BATTERY_PROP_STATE_OF_HEALTH = 10, // equals BATTERY_PROPERTY_STATE_OF_HEALTH
     BATTERY_PROP_PART_STATUS = 12, // equals BATTERY_PROPERTY_PART_STATUS
 };
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 8858f0c..057b996 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -103,6 +103,26 @@
     }
 }
 
+// Helper to get a trace tracker from a traced key or motion entry.
+const std::unique_ptr<trace::EventTrackerInterface>& getTraceTracker(const EventEntry& entry) {
+    switch (entry.type) {
+        case EventEntry::Type::MOTION: {
+            const auto& motion = static_cast<const MotionEntry&>(entry);
+            ensureEventTraced(motion);
+            return motion.traceTracker;
+        }
+        case EventEntry::Type::KEY: {
+            const auto& key = static_cast<const KeyEntry&>(entry);
+            ensureEventTraced(key);
+            return key.traceTracker;
+        }
+        default: {
+            const static std::unique_ptr<trace::EventTrackerInterface> kNullTracker;
+            return kNullTracker;
+        }
+    }
+}
+
 // Temporarily releases a held mutex for the lifetime of the instance.
 // Named to match std::scoped_lock
 class scoped_unlock {
@@ -1147,10 +1167,6 @@
                 dropReason = DropReason::BLOCKED;
             }
             done = dispatchKeyLocked(currentTime, keyEntry, &dropReason, nextWakeupTime);
-            if (done && mTracer) {
-                ensureEventTraced(*keyEntry);
-                mTracer->eventProcessingComplete(*keyEntry->traceTracker);
-            }
             break;
         }
 
@@ -1176,10 +1192,6 @@
                 }
             }
             done = dispatchMotionLocked(currentTime, motionEntry, &dropReason, nextWakeupTime);
-            if (done && mTracer) {
-                ensureEventTraced(*motionEntry);
-                mTracer->eventProcessingComplete(*motionEntry->traceTracker);
-            }
             break;
         }
 
@@ -1205,6 +1217,12 @@
         }
         mLastDropReason = dropReason;
 
+        if (mTracer) {
+            if (auto& traceTracker = getTraceTracker(*mPendingEvent); traceTracker != nullptr) {
+                mTracer->eventProcessingComplete(*traceTracker);
+            }
+        }
+
         releasePendingEventLocked();
         nextWakeupTime = LLONG_MIN; // force next poll to wake up immediately
     }
@@ -2558,9 +2576,9 @@
                 return {};
             }
 
-            // Drop touch events if requested by input feature
+            // Do not slide events to the window which can not receive motion event
             if (newTouchedWindowHandle != nullptr &&
-                shouldDropInput(entry, newTouchedWindowHandle)) {
+                !canWindowReceiveMotionLocked(newTouchedWindowHandle, entry)) {
                 newTouchedWindowHandle = nullptr;
             }
 
@@ -4268,72 +4286,13 @@
 std::unique_ptr<MotionEntry> InputDispatcher::splitMotionEvent(
         const MotionEntry& originalMotionEntry, std::bitset<MAX_POINTER_ID + 1> pointerIds,
         nsecs_t splitDownTime) {
-    ALOG_ASSERT(pointerIds.any());
+    const auto& [action, pointerProperties, pointerCoords] =
+            MotionEvent::split(originalMotionEntry.action, originalMotionEntry.flags,
+                               /*historySize=*/0, originalMotionEntry.pointerProperties,
+                               originalMotionEntry.pointerCoords, pointerIds);
 
-    uint32_t splitPointerIndexMap[MAX_POINTERS];
-    std::vector<PointerProperties> splitPointerProperties;
-    std::vector<PointerCoords> splitPointerCoords;
-
-    uint32_t originalPointerCount = originalMotionEntry.getPointerCount();
-    uint32_t splitPointerCount = 0;
-
-    for (uint32_t originalPointerIndex = 0; originalPointerIndex < originalPointerCount;
-         originalPointerIndex++) {
-        const PointerProperties& pointerProperties =
-                originalMotionEntry.pointerProperties[originalPointerIndex];
-        uint32_t pointerId = uint32_t(pointerProperties.id);
-        if (pointerIds.test(pointerId)) {
-            splitPointerIndexMap[splitPointerCount] = originalPointerIndex;
-            splitPointerProperties.push_back(pointerProperties);
-            splitPointerCoords.push_back(originalMotionEntry.pointerCoords[originalPointerIndex]);
-            splitPointerCount += 1;
-        }
-    }
-
-    if (splitPointerCount != pointerIds.count()) {
-        // This is bad.  We are missing some of the pointers that we expected to deliver.
-        // Most likely this indicates that we received an ACTION_MOVE events that has
-        // different pointer ids than we expected based on the previous ACTION_DOWN
-        // or ACTION_POINTER_DOWN events that caused us to decide to split the pointers
-        // in this way.
-        ALOGW("Dropping split motion event because the pointer count is %d but "
-              "we expected there to be %zu pointers.  This probably means we received "
-              "a broken sequence of pointer ids from the input device: %s",
-              splitPointerCount, pointerIds.count(), originalMotionEntry.getDescription().c_str());
-        return nullptr;
-    }
-
-    int32_t action = originalMotionEntry.action;
-    int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
-    if (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN ||
-        maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
-        int32_t originalPointerIndex = MotionEvent::getActionIndex(action);
-        const PointerProperties& pointerProperties =
-                originalMotionEntry.pointerProperties[originalPointerIndex];
-        uint32_t pointerId = uint32_t(pointerProperties.id);
-        if (pointerIds.test(pointerId)) {
-            if (pointerIds.count() == 1) {
-                // The first/last pointer went down/up.
-                action = maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN
-                        ? AMOTION_EVENT_ACTION_DOWN
-                        : (originalMotionEntry.flags & AMOTION_EVENT_FLAG_CANCELED) != 0
-                                ? AMOTION_EVENT_ACTION_CANCEL
-                                : AMOTION_EVENT_ACTION_UP;
-            } else {
-                // A secondary pointer went down/up.
-                uint32_t splitPointerIndex = 0;
-                while (pointerId != uint32_t(splitPointerProperties[splitPointerIndex].id)) {
-                    splitPointerIndex += 1;
-                }
-                action = maskedAction |
-                        (splitPointerIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-            }
-        } else {
-            // An unrelated pointer changed.
-            action = AMOTION_EVENT_ACTION_MOVE;
-        }
-    }
-
+    // TODO(b/327503168): Move this check inside MotionEvent::split once all callers handle it
+    //   correctly.
     if (action == AMOTION_EVENT_ACTION_DOWN && splitDownTime != originalMotionEntry.eventTime) {
         logDispatchStateLocked();
         LOG_ALWAYS_FATAL("Split motion event has mismatching downTime and eventTime for "
@@ -4361,7 +4320,7 @@
                                           originalMotionEntry.yPrecision,
                                           originalMotionEntry.xCursorPosition,
                                           originalMotionEntry.yCursorPosition, splitDownTime,
-                                          splitPointerProperties, splitPointerCoords);
+                                          pointerProperties, pointerCoords);
 
     return splitMotionEntry;
 }
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp
index be09013..0be64e6 100644
--- a/services/inputflinger/dispatcher/trace/InputTracer.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp
@@ -84,24 +84,24 @@
 
 void InputTracer::dispatchToTargetHint(const EventTrackerInterface& cookie,
                                        const InputTarget& target) {
-    auto& cookieState = getState(cookie);
-    if (!cookieState) {
+    auto& eventState = getState(cookie);
+    if (eventState.isEventProcessingComplete) {
         LOG(FATAL) << "dispatchToTargetHint() should not be called after eventProcessingComplete()";
     }
     // TODO(b/210460522): Determine if the event is sensitive based on the target.
 }
 
 void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie) {
-    auto& cookieState = getState(cookie);
-    if (!cookieState) {
+    auto& eventState = getState(cookie);
+    if (eventState.isEventProcessingComplete) {
         LOG(FATAL) << "Traced event was already logged. "
                       "eventProcessingComplete() was likely called more than once.";
     }
 
     std::visit(Visitor{[&](const TracedMotionEvent& e) { mBackend->traceMotionEvent(e); },
                        [&](const TracedKeyEvent& e) { mBackend->traceKeyEvent(e); }},
-               cookieState->event);
-    cookieState.reset();
+               eventState.event);
+    eventState.isEventProcessingComplete = true;
 }
 
 void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry,
@@ -136,7 +136,7 @@
                                    /*hmac=*/{}});
 }
 
-std::optional<InputTracer::EventState>& InputTracer::getState(const EventTrackerInterface& cookie) {
+InputTracer::EventState& InputTracer::getState(const EventTrackerInterface& cookie) {
     return static_cast<const EventTrackerImpl&>(cookie).mState;
 }
 
@@ -146,18 +146,17 @@
       : mTracer(tracer), mState(event) {}
 
 InputTracer::EventTrackerImpl::~EventTrackerImpl() {
-    if (!mState) {
+    if (mState.isEventProcessingComplete) {
         // This event has already been written to the trace as expected.
         return;
     }
-    // We're still holding on to the state, which means it hasn't yet been written to the trace.
-    // Write it to the trace now.
+    // The event processing was never marked as complete, so do it now.
     // TODO(b/210460522): Determine why/where the event is being destroyed before
     //   eventProcessingComplete() is called.
     std::visit(Visitor{[&](const TracedMotionEvent& e) { mTracer.mBackend->traceMotionEvent(e); },
                        [&](const TracedKeyEvent& e) { mTracer.mBackend->traceKeyEvent(e); }},
-               mState->event);
-    mState.reset();
+               mState.event);
+    mState.isEventProcessingComplete = true;
 }
 
 } // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h
index c8b25c9..1acac6d 100644
--- a/services/inputflinger/dispatcher/trace/InputTracer.h
+++ b/services/inputflinger/dispatcher/trace/InputTracer.h
@@ -51,13 +51,16 @@
 
     // The state of a tracked event.
     struct EventState {
+        explicit inline EventState(TracedEvent event) : event(std::move(event)){};
+
         const TracedEvent event;
+        bool isEventProcessingComplete{false};
         // TODO(b/210460522): Add additional args for tracking event sensitivity and
         //  dispatch target UIDs.
     };
 
     // Get the event state associated with a tracking cookie.
-    std::optional<EventState>& getState(const EventTrackerInterface&);
+    EventState& getState(const EventTrackerInterface&);
 
     // Implementation of the event tracker cookie. The cookie holds the event state directly for
     // convenience to avoid the overhead of tracking the state separately in InputTracer.
@@ -68,11 +71,9 @@
 
     private:
         InputTracer& mTracer;
-        // This event tracker cookie will only hold the state as long as it has not been written
-        // to the trace. The state is released when the event is written to the trace.
-        mutable std::optional<EventState> mState;
+        mutable EventState mState;
 
-        friend std::optional<EventState>& InputTracer::getState(const EventTrackerInterface&);
+        friend EventState& InputTracer::getState(const EventTrackerInterface&);
     };
 };
 
diff --git a/services/powermanager/Android.bp b/services/powermanager/Android.bp
index 1f72e8b..3ea08fe 100644
--- a/services/powermanager/Android.bp
+++ b/services/powermanager/Android.bp
@@ -17,6 +17,7 @@
         "PowerHalController.cpp",
         "PowerHalLoader.cpp",
         "PowerHalWrapper.cpp",
+        "PowerHintSessionWrapper.cpp",
         "PowerSaveState.cpp",
         "Temperature.cpp",
         "WorkDuration.cpp",
diff --git a/services/powermanager/PowerHalController.cpp b/services/powermanager/PowerHalController.cpp
index bc178bc..40fd097 100644
--- a/services/powermanager/PowerHalController.cpp
+++ b/services/powermanager/PowerHalController.cpp
@@ -57,6 +57,10 @@
     PowerHalLoader::unloadAll();
 }
 
+int32_t HalConnector::getAidlVersion() {
+    return PowerHalLoader::getAidlVersion();
+}
+
 // -------------------------------------------------------------------------------------------------
 
 void PowerHalController::init() {
@@ -77,6 +81,22 @@
     return mConnectedHal;
 }
 
+// Using statement expression macro instead of a method lets the static be
+// scoped to the outer method while dodging the need for a support lookup table
+// This only works for AIDL methods that do not vary supported/unsupported depending
+// on their arguments (not setBoost, setMode) which do their own support checks
+#define CACHE_SUPPORT(version, method)                                    \
+    ({                                                                    \
+        static bool support = mHalConnector->getAidlVersion() >= version; \
+        !support ? decltype(method)::unsupported() : ({                   \
+            auto result = method;                                         \
+            if (result.isUnsupported()) {                                 \
+                support = false;                                          \
+            }                                                             \
+            std::move(result);                                            \
+        });                                                               \
+    })
+
 // Check if a call to Power HAL function failed; if so, log the failure and
 // invalidate the current Power HAL handle.
 template <typename T>
@@ -103,40 +123,49 @@
     return processHalResult(handle->setMode(mode, enabled), "setMode");
 }
 
-HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
-PowerHalController::createHintSession(int32_t tgid, int32_t uid,
-                                      const std::vector<int32_t>& threadIds,
-                                      int64_t durationNanos) {
+// Aidl-only methods
+
+HalResult<std::shared_ptr<PowerHintSessionWrapper>> PowerHalController::createHintSession(
+        int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos) {
     std::shared_ptr<HalWrapper> handle = initHal();
-    return processHalResult(handle->createHintSession(tgid, uid, threadIds, durationNanos),
-                            "createHintSession");
+    return CACHE_SUPPORT(2,
+                         processHalResult(handle->createHintSession(tgid, uid, threadIds,
+                                                                    durationNanos),
+                                          "createHintSession"));
 }
 
-HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
-PowerHalController::createHintSessionWithConfig(
+HalResult<std::shared_ptr<PowerHintSessionWrapper>> PowerHalController::createHintSessionWithConfig(
         int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos,
         aidl::android::hardware::power::SessionTag tag,
         aidl::android::hardware::power::SessionConfig* config) {
     std::shared_ptr<HalWrapper> handle = initHal();
-    return processHalResult(handle->createHintSessionWithConfig(tgid, uid, threadIds, durationNanos,
-                                                                tag, config),
-                            "createHintSessionWithConfig");
+    return CACHE_SUPPORT(5,
+                         processHalResult(handle->createHintSessionWithConfig(tgid, uid, threadIds,
+                                                                              durationNanos, tag,
+                                                                              config),
+                                          "createHintSessionWithConfig"));
 }
 
 HalResult<int64_t> PowerHalController::getHintSessionPreferredRate() {
     std::shared_ptr<HalWrapper> handle = initHal();
-    return processHalResult(handle->getHintSessionPreferredRate(), "getHintSessionPreferredRate");
+    return CACHE_SUPPORT(2,
+                         processHalResult(handle->getHintSessionPreferredRate(),
+                                          "getHintSessionPreferredRate"));
 }
 
 HalResult<aidl::android::hardware::power::ChannelConfig> PowerHalController::getSessionChannel(
         int tgid, int uid) {
     std::shared_ptr<HalWrapper> handle = initHal();
-    return processHalResult(handle->getSessionChannel(tgid, uid), "getSessionChannel");
+    return CACHE_SUPPORT(5,
+                         processHalResult(handle->getSessionChannel(tgid, uid),
+                                          "getSessionChannel"));
 }
 
 HalResult<void> PowerHalController::closeSessionChannel(int tgid, int uid) {
     std::shared_ptr<HalWrapper> handle = initHal();
-    return processHalResult(handle->closeSessionChannel(tgid, uid), "closeSessionChannel");
+    return CACHE_SUPPORT(5,
+                         processHalResult(handle->closeSessionChannel(tgid, uid),
+                                          "closeSessionChannel"));
 }
 
 } // namespace power
diff --git a/services/powermanager/PowerHalLoader.cpp b/services/powermanager/PowerHalLoader.cpp
index 2214461..ea284c3 100644
--- a/services/powermanager/PowerHalLoader.cpp
+++ b/services/powermanager/PowerHalLoader.cpp
@@ -60,6 +60,7 @@
 sp<V1_1::IPower> PowerHalLoader::gHalHidlV1_1 = nullptr;
 sp<V1_2::IPower> PowerHalLoader::gHalHidlV1_2 = nullptr;
 sp<V1_3::IPower> PowerHalLoader::gHalHidlV1_3 = nullptr;
+int32_t PowerHalLoader::gAidlInterfaceVersion = 0;
 
 void PowerHalLoader::unloadAll() {
     std::lock_guard<std::mutex> lock(gHalMutex);
@@ -89,6 +90,8 @@
             ndk::SpAIBinder(AServiceManager_waitForService(aidlServiceName.c_str())));
     if (gHalAidl) {
         ALOGI("Successfully connected to Power HAL AIDL service.");
+        gHalAidl->getInterfaceVersion(&gAidlInterfaceVersion);
+
     } else {
         ALOGI("Power HAL AIDL service not available.");
         gHalExists = false;
@@ -128,6 +131,10 @@
     return loadHal<V1_0::IPower>(gHalExists, gHalHidlV1_0, loadFn, "HIDL v1.0");
 }
 
+int32_t PowerHalLoader::getAidlVersion() {
+    return gAidlInterfaceVersion;
+}
+
 // -------------------------------------------------------------------------------------------------
 
 } // namespace power
diff --git a/services/powermanager/PowerHalWrapper.cpp b/services/powermanager/PowerHalWrapper.cpp
index 1009100..bd6685c 100644
--- a/services/powermanager/PowerHalWrapper.cpp
+++ b/services/powermanager/PowerHalWrapper.cpp
@@ -18,11 +18,10 @@
 #include <aidl/android/hardware/power/Boost.h>
 #include <aidl/android/hardware/power/IPowerHintSession.h>
 #include <aidl/android/hardware/power/Mode.h>
+#include <powermanager/HalResult.h>
 #include <powermanager/PowerHalWrapper.h>
 #include <utils/Log.h>
 
-#include <cinttypes>
-
 using namespace android::hardware::power;
 namespace Aidl = aidl::android::hardware::power;
 
@@ -31,15 +30,6 @@
 namespace power {
 
 // -------------------------------------------------------------------------------------------------
-inline HalResult<void> toHalResult(const ndk::ScopedAStatus& result) {
-    if (result.isOk()) {
-        return HalResult<void>::ok();
-    }
-    ALOGE("Power HAL request failed: %s", result.getDescription().c_str());
-    return HalResult<void>::failed(result.getDescription());
-}
-
-// -------------------------------------------------------------------------------------------------
 
 HalResult<void> EmptyHalWrapper::setBoost(Aidl::Boost boost, int32_t durationMs) {
     ALOGV("Skipped setBoost %s with duration %dms because %s", toString(boost).c_str(), durationMs,
@@ -53,19 +43,19 @@
     return HalResult<void>::unsupported();
 }
 
-HalResult<std::shared_ptr<Aidl::IPowerHintSession>> EmptyHalWrapper::createHintSession(
+HalResult<std::shared_ptr<PowerHintSessionWrapper>> EmptyHalWrapper::createHintSession(
         int32_t, int32_t, const std::vector<int32_t>& threadIds, int64_t) {
     ALOGV("Skipped createHintSession(task num=%zu) because %s", threadIds.size(),
           getUnsupportedMessage());
-    return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::unsupported();
+    return HalResult<std::shared_ptr<PowerHintSessionWrapper>>::unsupported();
 }
 
-HalResult<std::shared_ptr<Aidl::IPowerHintSession>> EmptyHalWrapper::createHintSessionWithConfig(
+HalResult<std::shared_ptr<PowerHintSessionWrapper>> EmptyHalWrapper::createHintSessionWithConfig(
         int32_t, int32_t, const std::vector<int32_t>& threadIds, int64_t, Aidl::SessionTag,
         Aidl::SessionConfig*) {
     ALOGV("Skipped createHintSessionWithConfig(task num=%zu) because %s", threadIds.size(),
           getUnsupportedMessage());
-    return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::unsupported();
+    return HalResult<std::shared_ptr<PowerHintSessionWrapper>>::unsupported();
 }
 
 HalResult<int64_t> EmptyHalWrapper::getHintSessionPreferredRate() {
@@ -225,7 +215,7 @@
     }
     lock.unlock();
 
-    return toHalResult(mHandle->setBoost(boost, durationMs));
+    return HalResult<void>::fromStatus(mHandle->setBoost(boost, durationMs));
 }
 
 HalResult<void> AidlHalWrapper::setMode(Aidl::Mode mode, bool enabled) {
@@ -253,25 +243,25 @@
     }
     lock.unlock();
 
-    return toHalResult(mHandle->setMode(mode, enabled));
+    return HalResult<void>::fromStatus(mHandle->setMode(mode, enabled));
 }
 
-HalResult<std::shared_ptr<Aidl::IPowerHintSession>> AidlHalWrapper::createHintSession(
+HalResult<std::shared_ptr<PowerHintSessionWrapper>> AidlHalWrapper::createHintSession(
         int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos) {
     std::shared_ptr<Aidl::IPowerHintSession> appSession;
-    return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::
+    return HalResult<std::shared_ptr<PowerHintSessionWrapper>>::
             fromStatus(mHandle->createHintSession(tgid, uid, threadIds, durationNanos, &appSession),
-                       std::move(appSession));
+                       std::make_shared<PowerHintSessionWrapper>(std::move(appSession)));
 }
 
-HalResult<std::shared_ptr<Aidl::IPowerHintSession>> AidlHalWrapper::createHintSessionWithConfig(
+HalResult<std::shared_ptr<PowerHintSessionWrapper>> AidlHalWrapper::createHintSessionWithConfig(
         int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos,
         Aidl::SessionTag tag, Aidl::SessionConfig* config) {
     std::shared_ptr<Aidl::IPowerHintSession> appSession;
-    return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::
+    return HalResult<std::shared_ptr<PowerHintSessionWrapper>>::
             fromStatus(mHandle->createHintSessionWithConfig(tgid, uid, threadIds, durationNanos,
                                                             tag, config, &appSession),
-                       std::move(appSession));
+                       std::make_shared<PowerHintSessionWrapper>(std::move(appSession)));
 }
 
 HalResult<int64_t> AidlHalWrapper::getHintSessionPreferredRate() {
@@ -287,7 +277,7 @@
 }
 
 HalResult<void> AidlHalWrapper::closeSessionChannel(int tgid, int uid) {
-    return toHalResult(mHandle->closeSessionChannel(tgid, uid));
+    return HalResult<void>::fromStatus(mHandle->closeSessionChannel(tgid, uid));
 }
 
 const char* AidlHalWrapper::getUnsupportedMessage() {
diff --git a/services/powermanager/PowerHintSessionWrapper.cpp b/services/powermanager/PowerHintSessionWrapper.cpp
new file mode 100644
index 0000000..930c7fa
--- /dev/null
+++ b/services/powermanager/PowerHintSessionWrapper.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *            http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <powermanager/PowerHintSessionWrapper.h>
+
+using namespace aidl::android::hardware::power;
+
+namespace android::power {
+
+// Caches support for a given call in a static variable, checking both
+// the return value and interface version.
+#define CACHE_SUPPORT(version, method)                      \
+    ({                                                      \
+        static bool support = mInterfaceVersion >= version; \
+        !support ? decltype(method)::unsupported() : ({     \
+            auto result = method;                           \
+            if (result.isUnsupported()) {                   \
+                support = false;                            \
+            }                                               \
+            std::move(result);                              \
+        });                                                 \
+    })
+
+#define CHECK_SESSION(resultType)                                    \
+    if (mSession == nullptr) {                                       \
+        return HalResult<resultType>::failed("Session not running"); \
+    }
+
+// FWD_CALL just forwards calls from the wrapper to the session object.
+// It only works if the call has no return object, as is the case with all calls
+// except getSessionConfig.
+#define FWD_CALL(version, name, args, untypedArgs)                                              \
+    HalResult<void> PowerHintSessionWrapper::name args {                                        \
+        CHECK_SESSION(void)                                                                     \
+        return CACHE_SUPPORT(version, HalResult<void>::fromStatus(mSession->name untypedArgs)); \
+    }
+
+PowerHintSessionWrapper::PowerHintSessionWrapper(std::shared_ptr<IPowerHintSession>&& session)
+      : mSession(session) {
+    if (mSession != nullptr) {
+        mSession->getInterfaceVersion(&mInterfaceVersion);
+    }
+}
+
+// Support for individual hints/modes is not really handled here since there
+// is no way to check for it, so in the future if a way to check that is added,
+// this will need to be updated.
+
+FWD_CALL(2, updateTargetWorkDuration, (int64_t in_targetDurationNanos), (in_targetDurationNanos));
+FWD_CALL(2, reportActualWorkDuration, (const std::vector<WorkDuration>& in_durations),
+         (in_durations));
+FWD_CALL(2, pause, (), ());
+FWD_CALL(2, resume, (), ());
+FWD_CALL(2, close, (), ());
+FWD_CALL(4, sendHint, (SessionHint in_hint), (in_hint));
+FWD_CALL(4, setThreads, (const std::vector<int32_t>& in_threadIds), (in_threadIds));
+FWD_CALL(5, setMode, (SessionMode in_type, bool in_enabled), (in_type, in_enabled));
+
+HalResult<SessionConfig> PowerHintSessionWrapper::getSessionConfig() {
+    CHECK_SESSION(SessionConfig);
+    SessionConfig config;
+    return CACHE_SUPPORT(5,
+                         HalResult<SessionConfig>::fromStatus(mSession->getSessionConfig(&config),
+                                                              std::move(config)));
+}
+
+} // namespace android::power
diff --git a/services/powermanager/tests/Android.bp b/services/powermanager/tests/Android.bp
index 6fc96c0..a05ce2b 100644
--- a/services/powermanager/tests/Android.bp
+++ b/services/powermanager/tests/Android.bp
@@ -37,6 +37,7 @@
         "PowerHalWrapperHidlV1_1Test.cpp",
         "PowerHalWrapperHidlV1_2Test.cpp",
         "PowerHalWrapperHidlV1_3Test.cpp",
+        "PowerHintSessionWrapperTest.cpp",
         "WorkSourceTest.cpp",
     ],
     cflags: [
diff --git a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
index a720296..1589c99 100644
--- a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
+++ b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
@@ -86,6 +86,10 @@
 
 void PowerHalWrapperAidlTest::SetUp() {
     mMockHal = ndk::SharedRefBase::make<StrictMock<MockIPower>>();
+    EXPECT_CALL(*mMockHal, getInterfaceVersion(_)).WillRepeatedly(([](int32_t* ret) {
+        *ret = 5;
+        return ndk::ScopedAStatus::ok();
+    }));
     mWrapper = std::make_unique<AidlHalWrapper>(mMockHal);
     ASSERT_NE(nullptr, mWrapper);
 }
@@ -130,10 +134,12 @@
 }
 
 TEST_F(PowerHalWrapperAidlTest, TestSetBoostUnsupported) {
-    EXPECT_CALL(*mMockHal.get(), isBoostSupported(Eq(Boost::INTERACTION), _))
-            .Times(Exactly(1))
-            .WillOnce(DoAll(SetArgPointee<1>(false),
-                            Return(testing::ByMove(ndk::ScopedAStatus::ok()))));
+    EXPECT_CALL(*mMockHal.get(), isBoostSupported(_, _))
+            .Times(Exactly(2))
+            .WillRepeatedly([](Boost, bool* ret) {
+                *ret = false;
+                return ndk::ScopedAStatus::ok();
+            });
 
     auto result = mWrapper->setBoost(Boost::INTERACTION, 1000);
     ASSERT_TRUE(result.isUnsupported());
@@ -311,3 +317,29 @@
     auto closeResult = mWrapper->closeSessionChannel(tgid, uid);
     ASSERT_TRUE(closeResult.isOk());
 }
+
+TEST_F(PowerHalWrapperAidlTest, TestCreateHintSessionWithConfigUnsupported) {
+    std::vector<int> threadIds{gettid()};
+    int32_t tgid = 999;
+    int32_t uid = 1001;
+    int64_t durationNanos = 16666666L;
+    SessionTag tag = SessionTag::OTHER;
+    SessionConfig out;
+    EXPECT_CALL(*mMockHal.get(),
+                createHintSessionWithConfig(Eq(tgid), Eq(uid), Eq(threadIds), Eq(durationNanos),
+                                            Eq(tag), _, _))
+            .Times(1)
+            .WillOnce(Return(testing::ByMove(
+                    ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION))));
+    auto result =
+            mWrapper->createHintSessionWithConfig(tgid, uid, threadIds, durationNanos, tag, &out);
+    ASSERT_TRUE(result.isUnsupported());
+    Mock::VerifyAndClearExpectations(mMockHal.get());
+    EXPECT_CALL(*mMockHal.get(),
+                createHintSessionWithConfig(Eq(tgid), Eq(uid), Eq(threadIds), Eq(durationNanos),
+                                            Eq(tag), _, _))
+            .WillOnce(Return(
+                    testing::ByMove(ndk::ScopedAStatus::fromStatus(STATUS_UNKNOWN_TRANSACTION))));
+    result = mWrapper->createHintSessionWithConfig(tgid, uid, threadIds, durationNanos, tag, &out);
+    ASSERT_TRUE(result.isUnsupported());
+}
diff --git a/services/powermanager/tests/PowerHintSessionWrapperTest.cpp b/services/powermanager/tests/PowerHintSessionWrapperTest.cpp
new file mode 100644
index 0000000..7743fa4
--- /dev/null
+++ b/services/powermanager/tests/PowerHintSessionWrapperTest.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *            http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <aidl/android/hardware/power/IPowerHintSession.h>
+#include <powermanager/PowerHintSessionWrapper.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using aidl::android::hardware::power::IPowerHintSession;
+using android::power::PowerHintSessionWrapper;
+
+using namespace android;
+using namespace std::chrono_literals;
+using namespace testing;
+
+class MockIPowerHintSession : public IPowerHintSession {
+public:
+    MockIPowerHintSession() = default;
+    MOCK_METHOD(::ndk::ScopedAStatus, updateTargetWorkDuration, (int64_t in_targetDurationNanos),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, reportActualWorkDuration,
+                (const std::vector<::aidl::android::hardware::power::WorkDuration>& in_durations),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, pause, (), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, resume, (), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, close, (), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, sendHint,
+                (::aidl::android::hardware::power::SessionHint in_hint), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, setThreads, (const std::vector<int32_t>& in_threadIds),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, setMode,
+                (::aidl::android::hardware::power::SessionMode in_type, bool in_enabled),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, getSessionConfig,
+                (::aidl::android::hardware::power::SessionConfig * _aidl_return), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, getInterfaceVersion, (int32_t * _aidl_return), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, getInterfaceHash, (std::string * _aidl_return), (override));
+    MOCK_METHOD(::ndk::SpAIBinder, asBinder, (), (override));
+    MOCK_METHOD(bool, isRemote, (), (override));
+};
+
+class PowerHintSessionWrapperTest : public Test {
+public:
+    void SetUp() override;
+
+protected:
+    std::shared_ptr<NiceMock<MockIPowerHintSession>> mMockSession = nullptr;
+    std::unique_ptr<PowerHintSessionWrapper> mSession = nullptr;
+};
+
+void PowerHintSessionWrapperTest::SetUp() {
+    mMockSession = ndk::SharedRefBase::make<NiceMock<MockIPowerHintSession>>();
+    EXPECT_CALL(*mMockSession, getInterfaceVersion(_)).WillRepeatedly(([](int32_t* ret) {
+        *ret = 5;
+        return ndk::ScopedAStatus::ok();
+    }));
+    mSession = std::make_unique<PowerHintSessionWrapper>(mMockSession);
+    ASSERT_NE(nullptr, mSession);
+}
+
+TEST_F(PowerHintSessionWrapperTest, updateTargetWorkDuration) {
+    EXPECT_CALL(*mMockSession.get(), updateTargetWorkDuration(1000000000))
+            .WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->updateTargetWorkDuration(1000000000);
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, reportActualWorkDuration) {
+    EXPECT_CALL(*mMockSession.get(),
+                reportActualWorkDuration(
+                        std::vector<::aidl::android::hardware::power::WorkDuration>()))
+            .WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->reportActualWorkDuration(
+            std::vector<::aidl::android::hardware::power::WorkDuration>());
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, pause) {
+    EXPECT_CALL(*mMockSession.get(), pause()).WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->pause();
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, resume) {
+    EXPECT_CALL(*mMockSession.get(), resume()).WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->resume();
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, close) {
+    EXPECT_CALL(*mMockSession.get(), close()).WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->close();
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, sendHint) {
+    EXPECT_CALL(*mMockSession.get(),
+                sendHint(::aidl::android::hardware::power::SessionHint::CPU_LOAD_UP))
+            .WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->sendHint(::aidl::android::hardware::power::SessionHint::CPU_LOAD_UP);
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, setThreads) {
+    EXPECT_CALL(*mMockSession.get(), setThreads(_)).WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->setThreads(std::vector<int32_t>{gettid()});
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, setMode) {
+    EXPECT_CALL(*mMockSession.get(),
+                setMode(::aidl::android::hardware::power::SessionMode::POWER_EFFICIENCY, true))
+            .WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->setMode(::aidl::android::hardware::power::SessionMode::POWER_EFFICIENCY,
+                                    true);
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, getSessionConfig) {
+    EXPECT_CALL(*mMockSession.get(), getSessionConfig(_))
+            .WillOnce(DoAll(SetArgPointee<0>(
+                                    aidl::android::hardware::power::SessionConfig{.id = 12L}),
+                            Return(ndk::ScopedAStatus::ok())));
+    auto status = mSession->getSessionConfig();
+    ASSERT_TRUE(status.isOk());
+}
diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h
index 118d928..bd54d24 100644
--- a/services/sensorservice/SensorService.h
+++ b/services/sensorservice/SensorService.h
@@ -340,8 +340,8 @@
             binder::Status onSensorPrivacyChanged(int toggleType, int sensor,
                                                   bool enabled);
 
-            // This callback is used for additional automotive-specific states for sensor privacy
-            // such as AUTO_DRIVER_ASSISTANCE_APPS. The newly defined states will only be valid
+            // This callback is used for additional automotive-specific state for sensor privacy
+            // such as ENABLED_EXCEPT_ALLOWLISTED_APPS. The newly defined states will only be valid
             // for camera privacy on automotive devices. onSensorPrivacyChanged() will still be
             // invoked whenever the enabled status of a toggle changes.
             binder::Status onSensorPrivacyStateChanged(int, int, int) {return binder::Status::ok();}
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 6a0ea22..d771803 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -12,7 +12,10 @@
     name: "surfaceflinger_flags",
     package: "com.android.graphics.surfaceflinger.flags",
     container: "system",
-    srcs: ["surfaceflinger_flags.aconfig"],
+    srcs: [
+        "surfaceflinger_flags.aconfig",
+        "surfaceflinger_flags_new.aconfig",
+    ],
 }
 
 cc_aconfig_library {
@@ -42,6 +45,7 @@
         "android.hardware.power-ndk_shared",
         "librenderengine_deps",
         "libtimestats_deps",
+        "libsurfaceflinger_common_deps",
         "surfaceflinger_defaults",
     ],
     cflags: [
@@ -82,7 +86,6 @@
         "libinput",
         "libutils",
         "libSurfaceFlingerProp",
-        "server_configurable_flags",
     ],
     static_libs: [
         "libaidlcommonsupport",
@@ -95,10 +98,8 @@
         "libscheduler",
         "libserviceutils",
         "libshaders",
-        "libsurfaceflinger_common",
         "libtimestats",
         "libtonemap",
-        "libsurfaceflingerflags",
     ],
     header_libs: [
         "android.hardware.graphics.composer@2.1-command-buffer",
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index 0b01c66..a52cc87 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -38,7 +38,6 @@
         "libSurfaceFlingerProp",
         "libui",
         "libutils",
-        "server_configurable_flags",
     ],
     static_libs: [
         "liblayers_proto",
@@ -90,24 +89,23 @@
 
 cc_library {
     name: "libcompositionengine",
-    defaults: ["libcompositionengine_defaults"],
-    static_libs: [
-        "libsurfaceflinger_common",
-        "libsurfaceflingerflags",
+    defaults: [
+        "libcompositionengine_defaults",
+        "libsurfaceflinger_common_deps",
     ],
     srcs: [
         ":libcompositionengine_sources",
     ],
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
-    shared_libs: [
-        "server_configurable_flags",
-    ],
 }
 
 cc_library {
     name: "libcompositionengine_mocks",
-    defaults: ["libcompositionengine_defaults"],
+    defaults: [
+        "libcompositionengine_defaults",
+        "libsurfaceflinger_common_test_deps",
+    ],
     srcs: [
         "mock/CompositionEngine.cpp",
         "mock/Display.cpp",
@@ -123,11 +121,6 @@
         "libgtest",
         "libgmock",
         "libcompositionengine",
-        "libsurfaceflinger_common_test",
-        "libsurfaceflingerflags_test",
-    ],
-    shared_libs: [
-        "server_configurable_flags",
     ],
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
@@ -140,7 +133,10 @@
         "frameworks/native/services/surfaceflinger/common/include",
         "frameworks/native/services/surfaceflinger/tests/unittests",
     ],
-    defaults: ["libcompositionengine_defaults"],
+    defaults: [
+        "libcompositionengine_defaults",
+        "libsurfaceflinger_common_test_deps",
+    ],
     srcs: [
         ":libcompositionengine_sources",
         "tests/planner/CachedSetTest.cpp",
@@ -166,14 +162,11 @@
         "librenderengine_mocks",
         "libgmock",
         "libgtest",
-        "libsurfaceflinger_common_test",
-        "libsurfaceflingerflags_test",
     ],
     shared_libs: [
         // For some reason, libvulkan isn't picked up from librenderengine
         // Probably ASAN related?
         "libvulkan",
-        "server_configurable_flags",
     ],
     sanitize: {
         hwaddress: true,
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
index a0c943b..8dfbeb8 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -233,7 +233,7 @@
             auto ret = mHintSession->updateTargetWorkDuration(targetDuration.ns());
             if (!ret.isOk()) {
                 ALOGW("Failed to set power hint target work duration with error: %s",
-                      ret.getDescription().c_str());
+                      ret.errorMessage());
                 mHintSession = nullptr;
             }
         }
@@ -293,8 +293,7 @@
 
         auto ret = mHintSession->reportActualWorkDuration(mHintSessionQueue);
         if (!ret.isOk()) {
-            ALOGW("Failed to report actual work durations with error: %s",
-                  ret.getDescription().c_str());
+            ALOGW("Failed to report actual work durations with error: %s", ret.errorMessage());
             mHintSession = nullptr;
             return;
         }
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
index bbe51cc0..1040048 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
@@ -259,8 +259,8 @@
     std::optional<bool> mSupportsHintSession;
 
     std::mutex mHintSessionMutex;
-    std::shared_ptr<aidl::android::hardware::power::IPowerHintSession> mHintSession
-            GUARDED_BY(mHintSessionMutex) = nullptr;
+    std::shared_ptr<power::PowerHintSessionWrapper> mHintSession GUARDED_BY(mHintSessionMutex) =
+            nullptr;
 
     // Initialize to true so we try to call, to check if it's supported
     bool mHasExpensiveRendering = true;
diff --git a/services/surfaceflinger/Scheduler/Android.bp b/services/surfaceflinger/Scheduler/Android.bp
index 16776cf..5455fdc 100644
--- a/services/surfaceflinger/Scheduler/Android.bp
+++ b/services/surfaceflinger/Scheduler/Android.bp
@@ -53,7 +53,10 @@
 cc_test {
     name: "libscheduler_test",
     test_suites: ["device-tests"],
-    defaults: ["libscheduler_defaults"],
+    defaults: [
+        "libscheduler_defaults",
+        "libsurfaceflinger_common_test_deps",
+    ],
     srcs: [
         "tests/FrameTargeterTest.cpp",
         "tests/PresentLatencyTrackerTest.cpp",
@@ -63,9 +66,5 @@
         "libgmock",
         "libgtest",
         "libscheduler",
-        "libsurfaceflingerflags_test",
-    ],
-    shared_libs: [
-        "server_configurable_flags",
     ],
 }
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index dcb6254..b8d5e76 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -300,12 +300,26 @@
 
                 if (gameModeFrameRateOverride.isValid()) {
                     info->setLayerVote({gameFrameRateOverrideVoteType, gameModeFrameRateOverride});
+                    ATRACE_FORMAT_INSTANT("GameModeFrameRateOverride");
+                    if (CC_UNLIKELY(mTraceEnabled)) {
+                        trace(*info, gameFrameRateOverrideVoteType,
+                              gameModeFrameRateOverride.getIntValue());
+                    }
                 } else if (frameRate.isValid()) {
                     info->setLayerVote({setFrameRateVoteType, frameRate.vote.rate,
                                         frameRate.vote.seamlessness, frameRate.category});
+                    if (CC_UNLIKELY(mTraceEnabled)) {
+                        trace(*info, gameFrameRateOverrideVoteType,
+                              frameRate.vote.rate.getIntValue());
+                    }
                 } else if (gameDefaultFrameRateOverride.isValid()) {
                     info->setLayerVote(
                             {gameFrameRateOverrideVoteType, gameDefaultFrameRateOverride});
+                    ATRACE_FORMAT_INSTANT("GameDefaultFrameRateOverride");
+                    if (CC_UNLIKELY(mTraceEnabled)) {
+                        trace(*info, gameFrameRateOverrideVoteType,
+                              gameDefaultFrameRateOverride.getIntValue());
+                    }
                 } else {
                     info->resetLayerVote();
                 }
@@ -341,9 +355,18 @@
 
 std::string LayerHistory::dump() const {
     std::lock_guard lock(mLock);
-    return base::StringPrintf("{size=%zu, active=%zu}",
+    return base::StringPrintf("{size=%zu, active=%zu}\n\tGameFrameRateOverrides=\n\t\t%s",
                               mActiveLayerInfos.size() + mInactiveLayerInfos.size(),
-                              mActiveLayerInfos.size());
+                              mActiveLayerInfos.size(), dumpGameFrameRateOverridesLocked().c_str());
+}
+
+std::string LayerHistory::dumpGameFrameRateOverridesLocked() const {
+    std::string overridesString = "(uid, gameModeOverride, gameDefaultOverride)=";
+    for (auto it = mGameFrameRateOverride.begin(); it != mGameFrameRateOverride.end(); ++it) {
+        base::StringAppendF(&overridesString, "{%u, %d %d} ", it->first,
+                            it->second.first.getIntValue(), it->second.second.getIntValue());
+    }
+    return overridesString;
 }
 
 float LayerHistory::getLayerFramerate(nsecs_t now, int32_t id) const {
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index 930d06c..a6f1b56 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -108,6 +108,8 @@
     // keyed by id as returned from Layer::getSequence()
     using LayerInfos = std::unordered_map<int32_t, LayerPair>;
 
+    std::string dumpGameFrameRateOverridesLocked() const REQUIRES(mLock);
+
     // Iterates over layers maps moving all active layers to mActiveLayerInfos and all inactive
     // layers to mInactiveLayerInfos.
     // worst case time complexity is O(2 * inactive + active)
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 93350b5..ffd3463 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -948,17 +948,43 @@
     const auto layersByUid = groupLayersByUid(layers);
     UidToFrameRateOverride frameRateOverrides;
     for (const auto& [uid, layersWithSameUid] : layersByUid) {
-        // Layers with ExplicitExactOrMultiple expect touch boost
-        const bool hasExplicitExactOrMultiple =
-                std::any_of(layersWithSameUid.cbegin(), layersWithSameUid.cend(),
-                            [](const auto& layer) {
-                                return layer->vote == LayerVoteType::ExplicitExactOrMultiple;
-                            });
+        // Look for cases that should not have frame rate overrides.
+        bool hasExplicitExactOrMultiple = false;
+        bool hasExplicitDefault = false;
+        bool hasHighHint = false;
+        for (const auto& layer : layersWithSameUid) {
+            switch (layer->vote) {
+                case LayerVoteType::ExplicitExactOrMultiple:
+                    hasExplicitExactOrMultiple = true;
+                    break;
+                case LayerVoteType::ExplicitDefault:
+                    hasExplicitDefault = true;
+                    break;
+                case LayerVoteType::ExplicitCategory:
+                    if (layer->frameRateCategory == FrameRateCategory::HighHint) {
+                        hasHighHint = true;
+                    }
+                    break;
+                default:
+                    // No action
+                    break;
+            }
+            if (hasExplicitExactOrMultiple && hasExplicitDefault && hasHighHint) {
+                break;
+            }
+        }
 
+        // Layers with ExplicitExactOrMultiple expect touch boost
         if (globalSignals.touch && hasExplicitExactOrMultiple) {
             continue;
         }
 
+        // Mirrors getRankedFrameRates. If there is no ExplicitDefault, expect touch boost and
+        // skip frame rate override.
+        if (hasHighHint && !hasExplicitDefault) {
+            continue;
+        }
+
         for (auto& [_, score] : scoredFrameRates) {
             score = 0;
         }
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
index 6d6b70d..84ccf8e 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
@@ -20,7 +20,7 @@
 
 #include <android-base/stringprintf.h>
 #include <ftl/concat.h>
-#include <gui/TraceUtils.h>
+#include <utils/Trace.h>
 #include <log/log_main.h>
 
 #include <scheduler/TimeKeeper.h>
@@ -44,17 +44,6 @@
             TimePoint::fromNs(nextVsyncTime)};
 }
 
-void traceEntry(const VSyncDispatchTimerQueueEntry& entry, nsecs_t now) {
-    if (!ATRACE_ENABLED() || !entry.wakeupTime().has_value() || !entry.targetVsync().has_value()) {
-        return;
-    }
-
-    ftl::Concat trace(ftl::truncated<5>(entry.name()), " alarm in ",
-                      ns2us(*entry.wakeupTime() - now), "us; VSYNC in ",
-                      ns2us(*entry.targetVsync() - now), "us");
-    ATRACE_FORMAT_INSTANT(trace.c_str());
-}
-
 } // namespace
 
 VSyncDispatch::~VSyncDispatch() = default;
@@ -98,7 +87,6 @@
 
 ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTiming timing,
                                                       VSyncTracker& tracker, nsecs_t now) {
-    ATRACE_NAME("VSyncDispatchTimerQueueEntry::schedule");
     auto nextVsyncTime =
             tracker.nextAnticipatedVSyncTimeFrom(std::max(timing.lastVsync,
                                                           now + timing.workDuration +
@@ -110,8 +98,6 @@
             mArmedInfo && (nextVsyncTime > (mArmedInfo->mActualVsyncTime + mMinVsyncDistance));
     bool const wouldSkipAWakeup =
             mArmedInfo && ((nextWakeupTime > (mArmedInfo->mActualWakeupTime + mMinVsyncDistance)));
-    ATRACE_FORMAT_INSTANT("%s: wouldSkipAVsyncTarget=%d wouldSkipAWakeup=%d", mName.c_str(),
-                          wouldSkipAVsyncTarget, wouldSkipAWakeup);
     if (FlagManager::getInstance().dont_skip_on_early_ro()) {
         if (wouldSkipAVsyncTarget || wouldSkipAWakeup) {
             nextVsyncTime = mArmedInfo->mActualVsyncTime;
@@ -136,7 +122,7 @@
 ScheduleResult VSyncDispatchTimerQueueEntry::addPendingWorkloadUpdate(
         VSyncTracker& tracker, nsecs_t now, VSyncDispatch::ScheduleTiming timing) {
     mWorkloadUpdateInfo = timing;
-    const auto armedInfo = getArmedInfo(tracker, now, timing, mArmedInfo);
+    const auto armedInfo = update(tracker, now, timing, mArmedInfo);
     return {TimePoint::fromNs(armedInfo.mActualWakeupTime),
             TimePoint::fromNs(armedInfo.mActualVsyncTime)};
 }
@@ -154,13 +140,11 @@
     bool const nextVsyncTooClose = mLastDispatchTime &&
             (nextVsyncTime - *mLastDispatchTime + mMinVsyncDistance) <= currentPeriod;
     if (alreadyDispatchedForVsync) {
-        ATRACE_FORMAT_INSTANT("alreadyDispatchedForVsync");
         return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + mMinVsyncDistance,
                                                     *mLastDispatchTime);
     }
 
     if (nextVsyncTooClose) {
-        ATRACE_FORMAT_INSTANT("nextVsyncTooClose");
         return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + currentPeriod,
                                                     *mLastDispatchTime + currentPeriod);
     }
@@ -168,11 +152,9 @@
     return nextVsyncTime;
 }
 
-auto VSyncDispatchTimerQueueEntry::getArmedInfo(VSyncTracker& tracker, nsecs_t now,
-                                                VSyncDispatch::ScheduleTiming timing,
-                                                std::optional<ArmingInfo> armedInfo) const
-        -> ArmingInfo {
-    ATRACE_NAME("VSyncDispatchTimerQueueEntry::getArmedInfo");
+auto VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now,
+                                          VSyncDispatch::ScheduleTiming timing,
+                                          std::optional<ArmingInfo> armedInfo) const -> ArmingInfo {
     const auto earliestReadyBy = now + timing.workDuration + timing.readyDuration;
     const auto earliestVsync = std::max(earliestReadyBy, timing.lastVsync);
 
@@ -183,39 +165,29 @@
     const auto nextReadyTime = nextVsyncTime - timing.readyDuration;
     const auto nextWakeupTime = nextReadyTime - timing.workDuration;
 
-    if (FlagManager::getInstance().dont_skip_on_early_ro()) {
-        bool const wouldSkipAVsyncTarget =
-                armedInfo && (nextVsyncTime > (armedInfo->mActualVsyncTime + mMinVsyncDistance));
-        bool const wouldSkipAWakeup =
-                armedInfo && (nextWakeupTime > (armedInfo->mActualWakeupTime + mMinVsyncDistance));
-        ATRACE_FORMAT_INSTANT("%s: wouldSkipAVsyncTarget=%d wouldSkipAWakeup=%d", mName.c_str(),
-                              wouldSkipAVsyncTarget, wouldSkipAWakeup);
-        if (wouldSkipAVsyncTarget || wouldSkipAWakeup) {
-            return *armedInfo;
-        }
+    bool const wouldSkipAVsyncTarget =
+            armedInfo && (nextVsyncTime > (armedInfo->mActualVsyncTime + mMinVsyncDistance));
+    bool const wouldSkipAWakeup =
+            armedInfo && (nextWakeupTime > (armedInfo->mActualWakeupTime + mMinVsyncDistance));
+    if (FlagManager::getInstance().dont_skip_on_early_ro() &&
+        (wouldSkipAVsyncTarget || wouldSkipAWakeup)) {
+        return *armedInfo;
     }
 
     return ArmingInfo{nextWakeupTime, nextVsyncTime, nextReadyTime};
 }
 
 void VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now) {
-    ATRACE_NAME("VSyncDispatchTimerQueueEntry::update");
     if (!mArmedInfo && !mWorkloadUpdateInfo) {
         return;
     }
 
     if (mWorkloadUpdateInfo) {
-        const auto workDelta = mWorkloadUpdateInfo->workDuration - mScheduleTiming.workDuration;
-        const auto readyDelta = mWorkloadUpdateInfo->readyDuration - mScheduleTiming.readyDuration;
-        const auto lastVsyncDelta = mWorkloadUpdateInfo->lastVsync - mScheduleTiming.lastVsync;
-        ATRACE_FORMAT_INSTANT("Workload updated workDelta=%" PRId64 " readyDelta=%" PRId64
-                              " lastVsyncDelta=%" PRId64,
-                              workDelta, readyDelta, lastVsyncDelta);
         mScheduleTiming = *mWorkloadUpdateInfo;
         mWorkloadUpdateInfo.reset();
     }
 
-    mArmedInfo = getArmedInfo(tracker, now, mScheduleTiming, mArmedInfo);
+    mArmedInfo = update(tracker, now, mScheduleTiming, mArmedInfo);
 }
 
 void VSyncDispatchTimerQueueEntry::disarm() {
@@ -310,7 +282,6 @@
 
 void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor(
         nsecs_t now, CallbackMap::const_iterator skipUpdateIt) {
-    ATRACE_CALL();
     std::optional<nsecs_t> min;
     std::optional<nsecs_t> targetVsync;
     std::optional<std::string_view> nextWakeupName;
@@ -323,10 +294,7 @@
         if (it != skipUpdateIt) {
             callback->update(*mTracker, now);
         }
-
-        traceEntry(*callback, now);
-
-        const auto wakeupTime = *callback->wakeupTime();
+        auto const wakeupTime = *callback->wakeupTime();
         if (!min || *min > wakeupTime) {
             nextWakeupName = callback->name();
             min = wakeupTime;
@@ -335,6 +303,11 @@
     }
 
     if (min && min < mIntendedWakeupTime) {
+        if (ATRACE_ENABLED() && nextWakeupName && targetVsync) {
+            ftl::Concat trace(ftl::truncated<5>(*nextWakeupName), " alarm in ", ns2us(*min - now),
+                              "us; VSYNC in ", ns2us(*targetVsync - now), "us");
+            ATRACE_NAME(trace.c_str());
+        }
         setTimer(*min, now);
     } else {
         ATRACE_NAME("cancel timer");
@@ -343,7 +316,6 @@
 }
 
 void VSyncDispatchTimerQueue::timerCallback() {
-    ATRACE_CALL();
     struct Invocation {
         std::shared_ptr<VSyncDispatchTimerQueueEntry> callback;
         nsecs_t vsyncTimestamp;
@@ -366,9 +338,8 @@
                 continue;
             }
 
-            traceEntry(*callback, now);
-
             auto const readyTime = callback->readyTime();
+
             auto const lagAllowance = std::max(now - mIntendedWakeupTime, static_cast<nsecs_t>(0));
             if (*wakeupTime < mIntendedWakeupTime + mTimerSlack + lagAllowance) {
                 callback->executing();
@@ -382,8 +353,6 @@
     }
 
     for (auto const& invocation : invocations) {
-        ftl::Concat trace(ftl::truncated<5>(invocation.callback->name()));
-        ATRACE_FORMAT("%s: %s", __func__, trace.c_str());
         invocation.callback->callback(invocation.vsyncTimestamp, invocation.wakeupTimestamp,
                                       invocation.deadlineTimestamp);
     }
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
index e4ddc03..252c09c 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
@@ -91,8 +91,8 @@
     };
 
     nsecs_t adjustVsyncIfNeeded(VSyncTracker& tracker, nsecs_t nextVsyncTime) const;
-    ArmingInfo getArmedInfo(VSyncTracker&, nsecs_t now, VSyncDispatch::ScheduleTiming,
-                            std::optional<ArmingInfo>) const;
+    ArmingInfo update(VSyncTracker&, nsecs_t now, VSyncDispatch::ScheduleTiming,
+                      std::optional<ArmingInfo>) const;
 
     const std::string mName;
     const VSyncDispatch::Callback mCallback;
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index 2f9dfea..8697696 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -45,35 +45,11 @@
 
 static auto constexpr kMaxPercent = 100u;
 
-namespace {
-nsecs_t getVsyncFixup(VSyncPredictor::Model model, Period minFramePeriod, nsecs_t vsyncTime,
-                      std::optional<nsecs_t> lastVsyncOpt) {
-    const auto threshold = model.slope / 2;
-
-    if (FlagManager::getInstance().vrr_config() && lastVsyncOpt) {
-        const auto vsyncDiff = vsyncTime - *lastVsyncOpt;
-        if (vsyncDiff >= threshold && vsyncDiff <= minFramePeriod.ns() - threshold) {
-            const auto vsyncFixup = *lastVsyncOpt + minFramePeriod.ns() - vsyncTime;
-            ATRACE_FORMAT_INSTANT("minFramePeriod violation. next in %.2f which is %.2f from prev. "
-                                  "adjust by %.2f",
-                                  static_cast<float>(vsyncTime - TimePoint::now().ns()) / 1e6f,
-                                  static_cast<float>(vsyncTime - *lastVsyncOpt) / 1e6f,
-                                  static_cast<float>(vsyncFixup) / 1e6f);
-            return vsyncFixup;
-        }
-    }
-
-    return 0;
-}
-} // namespace
-
 VSyncPredictor::~VSyncPredictor() = default;
 
-VSyncPredictor::VSyncPredictor(std::unique_ptr<Clock> clock, ftl::NonNull<DisplayModePtr> modePtr,
-                               size_t historySize, size_t minimumSamplesForPrediction,
-                               uint32_t outlierTolerancePercent)
-      : mClock(std::move(clock)),
-        mId(modePtr->getPhysicalDisplayId()),
+VSyncPredictor::VSyncPredictor(ftl::NonNull<DisplayModePtr> modePtr, size_t historySize,
+                               size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent)
+      : mId(modePtr->getPhysicalDisplayId()),
         mTraceOn(property_get_bool("debug.sf.vsp_trace", false)),
         kHistorySize(historySize),
         kMinimumSamplesForPrediction(minimumSamplesForPrediction),
@@ -171,7 +147,7 @@
             mKnownTimestamp = timestamp;
         }
         ATRACE_FORMAT_INSTANT("timestamp rejected. mKnownTimestamp was %.2fms ago",
-                              (mClock->now() - *mKnownTimestamp) / 1e6f);
+            (systemTime() - *mKnownTimestamp) / 1e6f);
         return false;
     }
 
@@ -274,6 +250,17 @@
     return true;
 }
 
+auto VSyncPredictor::getVsyncSequenceLocked(nsecs_t timestamp) const -> VsyncSequence {
+    const auto vsync = snapToVsync(timestamp);
+    if (!mLastVsyncSequence) return {vsync, 0};
+
+    const auto [slope, _] = getVSyncPredictionModelLocked();
+    const auto [lastVsyncTime, lastVsyncSequence] = *mLastVsyncSequence;
+    const auto vsyncSequence = lastVsyncSequence +
+            static_cast<int64_t>(std::round((vsync - lastVsyncTime) / static_cast<float>(slope)));
+    return {vsync, vsyncSequence};
+}
+
 nsecs_t VSyncPredictor::snapToVsync(nsecs_t timePoint) const {
     auto const [slope, intercept] = getVSyncPredictionModelLocked();
 
@@ -311,32 +298,51 @@
 }
 
 nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint,
-                                                     std::optional<nsecs_t> lastVsyncOpt) {
+                                                     std::optional<nsecs_t> lastVsyncOpt) const {
     ATRACE_CALL();
     std::lock_guard lock(mMutex);
+    const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope;
+    const auto threshold = currentPeriod / 2;
+    const auto minFramePeriod = minFramePeriodLocked().ns();
+    const auto lastFrameMissed =
+            lastVsyncOpt && std::abs(*lastVsyncOpt - mLastMissedVsync.ns()) < threshold;
+    const nsecs_t baseTime =
+            FlagManager::getInstance().vrr_config() && !lastFrameMissed && lastVsyncOpt
+            ? std::max(timePoint, *lastVsyncOpt + minFramePeriod - threshold)
+            : timePoint;
+    return snapToVsyncAlignedWithRenderRate(baseTime);
+}
 
-    const auto now = TimePoint::fromNs(mClock->now());
-    purgeTimelines(now);
+nsecs_t VSyncPredictor::snapToVsyncAlignedWithRenderRate(nsecs_t timePoint) const {
+    // update the mLastVsyncSequence for reference point
+    mLastVsyncSequence = getVsyncSequenceLocked(timePoint);
 
-    std::optional<TimePoint> vsyncOpt;
-    for (auto& timeline : mTimelines) {
-        vsyncOpt = timeline.nextAnticipatedVSyncTimeFrom(getVSyncPredictionModelLocked(),
-                                                         minFramePeriodLocked(),
-                                                         snapToVsync(timePoint), mMissedVsync,
-                                                         lastVsyncOpt);
-        if (vsyncOpt) {
-            break;
+    const auto renderRatePhase = [&]() REQUIRES(mMutex) -> int {
+        if (!mRenderRateOpt) return 0;
+        const auto divisor =
+                RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(idealPeriod()),
+                                                         *mRenderRateOpt);
+        if (divisor <= 1) return 0;
+
+        int mod = mLastVsyncSequence->seq % divisor;
+        if (mod == 0) return 0;
+
+        // This is actually a bug fix, but guarded with vrr_config since we found it with this
+        // config
+        if (FlagManager::getInstance().vrr_config()) {
+            if (mod < 0) mod += divisor;
         }
-    }
-    LOG_ALWAYS_FATAL_IF(!vsyncOpt);
 
-    if (*vsyncOpt > mLastCommittedVsync) {
-        mLastCommittedVsync = *vsyncOpt;
-        ATRACE_FORMAT_INSTANT("mLastCommittedVsync in %.2fms",
-                              float(mLastCommittedVsync.ns() - mClock->now()) / 1e6f);
+        return divisor - mod;
+    }();
+
+    if (renderRatePhase == 0) {
+        return mLastVsyncSequence->vsyncTime;
     }
 
-    return vsyncOpt->ns();
+    auto const [slope, intercept] = getVSyncPredictionModelLocked();
+    const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * renderRatePhase;
+    return snapToVsync(approximateNextVsync - slope / 2);
 }
 
 /*
@@ -347,28 +353,32 @@
  * isVSyncInPhase(33.3, 30) = false
  * isVSyncInPhase(50.0, 30) = true
  */
-bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) {
-    if (timePoint == 0) {
+bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const {
+    std::lock_guard lock(mMutex);
+    const auto divisor =
+            RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(idealPeriod()),
+                                                     frameRate);
+    return isVSyncInPhaseLocked(timePoint, static_cast<unsigned>(divisor));
+}
+
+bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const {
+    const TimePoint now = TimePoint::now();
+    const auto getTimePointIn = [](TimePoint now, nsecs_t timePoint) -> float {
+        return ticks<std::milli, float>(TimePoint::fromNs(timePoint) - now);
+    };
+    ATRACE_FORMAT("%s timePoint in: %.2f divisor: %zu", __func__, getTimePointIn(now, timePoint),
+                  divisor);
+
+    if (divisor <= 1 || timePoint == 0) {
         return true;
     }
 
-    std::lock_guard lock(mMutex);
-    const auto model = getVSyncPredictionModelLocked();
-    const nsecs_t period = model.slope;
+    const nsecs_t period = mRateMap[idealPeriod()].slope;
     const nsecs_t justBeforeTimePoint = timePoint - period / 2;
-    const auto now = TimePoint::fromNs(mClock->now());
-    const auto vsync = snapToVsync(justBeforeTimePoint);
-
-    purgeTimelines(now);
-
-    for (auto& timeline : mTimelines) {
-        if (timeline.validUntil() && timeline.validUntil()->ns() > vsync) {
-            return timeline.isVSyncInPhase(model, vsync, frameRate);
-        }
-    }
-
-    // The last timeline should always be valid
-    return mTimelines.back().isVSyncInPhase(model, vsync, frameRate);
+    const auto vsyncSequence = getVsyncSequenceLocked(justBeforeTimePoint);
+    ATRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64,
+                          getTimePointIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq);
+    return vsyncSequence.seq % divisor == 0;
 }
 
 void VSyncPredictor::setRenderRate(Fps renderRate) {
@@ -376,9 +386,6 @@
     ALOGV("%s %s: RenderRate %s ", __func__, to_string(mId).c_str(), to_string(renderRate).c_str());
     std::lock_guard lock(mMutex);
     mRenderRateOpt = renderRate;
-    mTimelines.back().freeze(TimePoint::fromNs(mLastCommittedVsync.ns() + mIdealPeriod.ns() / 2));
-    mTimelines.emplace_back(mIdealPeriod, renderRate);
-    purgeTimelines(TimePoint::fromNs(mClock->now()));
 }
 
 void VSyncPredictor::setDisplayModePtr(ftl::NonNull<DisplayModePtr> modePtr) {
@@ -408,9 +415,8 @@
     clearTimestamps();
 }
 
-Duration VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime,
-                                                      TimePoint lastConfirmedPresentTime) {
-    ATRACE_CALL();
+void VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime,
+                                                  TimePoint lastConfirmedPresentTime) {
     const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope;
     const auto threshold = currentPeriod / 2;
     const auto minFramePeriod = minFramePeriodLocked().ns();
@@ -436,20 +442,17 @@
     if (!mPastExpectedPresentTimes.empty()) {
         const auto phase = Duration(mPastExpectedPresentTimes.back() - expectedPresentTime);
         if (phase > 0ns) {
-            for (auto& timeline : mTimelines) {
-                timeline.shiftVsyncSequence(phase);
+            if (mLastVsyncSequence) {
+                mLastVsyncSequence->vsyncTime += phase.ns();
             }
             mPastExpectedPresentTimes.clear();
-            return phase;
         }
     }
-
-    return 0ns;
 }
 
 void VSyncPredictor::onFrameBegin(TimePoint expectedPresentTime,
                                   TimePoint lastConfirmedPresentTime) {
-    ATRACE_NAME("VSyncPredictor::onFrameBegin");
+    ATRACE_CALL();
     std::lock_guard lock(mMutex);
 
     if (!mDisplayModePtr->getVrrConfig()) return;
@@ -479,14 +482,11 @@
         }
     }
 
-    const auto phase = ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime);
-    if (phase > 0ns) {
-        mMissedVsync = {expectedPresentTime, minFramePeriodLocked()};
-    }
+    ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime);
 }
 
 void VSyncPredictor::onFrameMissed(TimePoint expectedPresentTime) {
-    ATRACE_NAME("VSyncPredictor::onFrameMissed");
+    ATRACE_CALL();
 
     std::lock_guard lock(mMutex);
     if (!mDisplayModePtr->getVrrConfig()) return;
@@ -496,15 +496,14 @@
     const auto lastConfirmedPresentTime =
             TimePoint::fromNs(expectedPresentTime.ns() + currentPeriod);
 
-    const auto phase = ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime);
-    if (phase > 0ns) {
-        mMissedVsync = {expectedPresentTime, Duration::fromNs(0)};
-    }
+    ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime);
+    mLastMissedVsync = expectedPresentTime;
 }
 
 VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const {
     std::lock_guard lock(mMutex);
-    return VSyncPredictor::getVSyncPredictionModelLocked();
+    const auto model = VSyncPredictor::getVSyncPredictionModelLocked();
+    return {model.slope, model.intercept};
 }
 
 VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModelLocked() const {
@@ -525,11 +524,6 @@
         mTimestamps.clear();
         mLastTimestampIndex = 0;
     }
-
-    mTimelines.clear();
-    mLastCommittedVsync = TimePoint::fromNs(0);
-    mIdealPeriod = Period::fromNs(idealPeriod());
-    mTimelines.emplace_back(mIdealPeriod, mRenderRateOpt);
 }
 
 bool VSyncPredictor::needsMoreSamples() const {
@@ -553,130 +547,6 @@
                       period / 1e6f, periodInterceptTuple.slope / 1e6f,
                       periodInterceptTuple.intercept);
     }
-    StringAppendF(&result, "\tmTimelines.size()=%zu\n", mTimelines.size());
-}
-
-void VSyncPredictor::purgeTimelines(android::TimePoint now) {
-    while (mTimelines.size() > 1) {
-        const auto validUntilOpt = mTimelines.front().validUntil();
-        if (validUntilOpt && *validUntilOpt < now) {
-            mTimelines.pop_front();
-        } else {
-            break;
-        }
-    }
-    LOG_ALWAYS_FATAL_IF(mTimelines.empty());
-    LOG_ALWAYS_FATAL_IF(mTimelines.back().validUntil().has_value());
-}
-
-VSyncPredictor::VsyncTimeline::VsyncTimeline(Period idealPeriod, std::optional<Fps> renderRateOpt)
-      : mIdealPeriod(idealPeriod), mRenderRateOpt(renderRateOpt) {}
-
-void VSyncPredictor::VsyncTimeline::freeze(TimePoint lastVsync) {
-    LOG_ALWAYS_FATAL_IF(mValidUntil.has_value());
-    ATRACE_FORMAT_INSTANT("renderRate %s valid for %.2f",
-                          mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA",
-                          float(lastVsync.ns() - TimePoint::now().ns()) / 1e6f);
-    mValidUntil = lastVsync;
-}
-
-std::optional<TimePoint> VSyncPredictor::VsyncTimeline::nextAnticipatedVSyncTimeFrom(
-        Model model, Period minFramePeriod, nsecs_t vsync, MissedVsync missedVsync,
-        std::optional<nsecs_t> lastVsyncOpt) {
-    ATRACE_FORMAT("renderRate %s", mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA");
-
-    const auto threshold = model.slope / 2;
-    const auto lastFrameMissed =
-            lastVsyncOpt && std::abs(*lastVsyncOpt - missedVsync.vsync.ns()) < threshold;
-    nsecs_t vsyncTime = snapToVsyncAlignedWithRenderRate(model, vsync);
-    nsecs_t vsyncFixupTime = 0;
-    if (FlagManager::getInstance().vrr_config() && lastFrameMissed) {
-        vsyncTime += missedVsync.fixup.ns();
-        ATRACE_FORMAT_INSTANT("lastFrameMissed");
-    } else {
-        vsyncFixupTime = getVsyncFixup(model, minFramePeriod, vsyncTime, lastVsyncOpt);
-        vsyncTime += vsyncFixupTime;
-    }
-
-    ATRACE_FORMAT_INSTANT("vsync in %.2fms", float(vsyncTime - TimePoint::now().ns()) / 1e6f);
-    if (mValidUntil && vsyncTime > mValidUntil->ns()) {
-        ATRACE_FORMAT_INSTANT("no longer valid for vsync in %.2f",
-                              static_cast<float>(vsyncTime - TimePoint::now().ns()) / 1e6f);
-        return std::nullopt;
-    }
-
-    if (vsyncFixupTime > 0) {
-        shiftVsyncSequence(Duration::fromNs(vsyncFixupTime));
-    }
-
-    return TimePoint::fromNs(vsyncTime);
-}
-
-auto VSyncPredictor::VsyncTimeline::getVsyncSequenceLocked(Model model, nsecs_t vsync)
-        -> VsyncSequence {
-    if (!mLastVsyncSequence) return {vsync, 0};
-
-    const auto [lastVsyncTime, lastVsyncSequence] = *mLastVsyncSequence;
-    const auto vsyncSequence = lastVsyncSequence +
-            static_cast<int64_t>(std::round((vsync - lastVsyncTime) /
-                                            static_cast<float>(model.slope)));
-    return {vsync, vsyncSequence};
-}
-
-nsecs_t VSyncPredictor::VsyncTimeline::snapToVsyncAlignedWithRenderRate(Model model,
-                                                                        nsecs_t vsync) {
-    // update the mLastVsyncSequence for reference point
-    mLastVsyncSequence = getVsyncSequenceLocked(model, vsync);
-
-    const auto renderRatePhase = [&]() -> int {
-        if (!mRenderRateOpt) return 0;
-        const auto divisor =
-                RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod.ns()),
-                                                         *mRenderRateOpt);
-        if (divisor <= 1) return 0;
-
-        int mod = mLastVsyncSequence->seq % divisor;
-        if (mod == 0) return 0;
-
-        // This is actually a bug fix, but guarded with vrr_config since we found it with this
-        // config
-        if (FlagManager::getInstance().vrr_config()) {
-            if (mod < 0) mod += divisor;
-        }
-
-        return divisor - mod;
-    }();
-
-    if (renderRatePhase == 0) {
-        return mLastVsyncSequence->vsyncTime;
-    }
-
-    return mLastVsyncSequence->vsyncTime + model.slope * renderRatePhase;
-}
-
-bool VSyncPredictor::VsyncTimeline::isVSyncInPhase(Model model, nsecs_t vsync, Fps frameRate) {
-    const auto getVsyncIn = [](TimePoint now, nsecs_t timePoint) -> float {
-        return ticks<std::milli, float>(TimePoint::fromNs(timePoint) - now);
-    };
-
-    Fps displayFps = mRenderRateOpt ? *mRenderRateOpt : Fps::fromPeriodNsecs(mIdealPeriod.ns());
-    const auto divisor = RefreshRateSelector::getFrameRateDivisor(displayFps, frameRate);
-    const auto now = TimePoint::now();
-
-    if (divisor <= 1) {
-        return true;
-    }
-    const auto vsyncSequence = getVsyncSequenceLocked(model, vsync);
-    ATRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64 " divisor: %zu",
-                          getVsyncIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq, divisor);
-    return vsyncSequence.seq % divisor == 0;
-}
-
-void VSyncPredictor::VsyncTimeline::shiftVsyncSequence(Duration phase) {
-    if (mLastVsyncSequence) {
-        ATRACE_FORMAT_INSTANT("adjusting vsync by %.2f", static_cast<float>(phase.ns()) / 1e6f);
-        mLastVsyncSequence->vsyncTime += phase.ns();
-    }
 }
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index c175765..8fd7e60 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -22,7 +22,6 @@
 #include <vector>
 
 #include <android-base/thread_annotations.h>
-#include <scheduler/TimeKeeper.h>
 #include <ui/DisplayId.h>
 
 #include "VSyncTracker.h"
@@ -32,7 +31,6 @@
 class VSyncPredictor : public VSyncTracker {
 public:
     /*
-     * \param [in] Clock The clock abstraction. Useful for unit tests.
      * \param [in] PhysicalDisplayid The display this corresponds to.
      * \param [in] modePtr  The initial display mode
      * \param [in] historySize  The internal amount of entries to store in the model.
@@ -40,13 +38,13 @@
      * predicting. \param [in] outlierTolerancePercent a number 0 to 100 that will be used to filter
      * samples that fall outlierTolerancePercent from an anticipated vsync event.
      */
-    VSyncPredictor(std::unique_ptr<Clock>, ftl::NonNull<DisplayModePtr> modePtr, size_t historySize,
+    VSyncPredictor(ftl::NonNull<DisplayModePtr> modePtr, size_t historySize,
                    size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent);
     ~VSyncPredictor();
 
     bool addVsyncTimestamp(nsecs_t timestamp) final EXCLUDES(mMutex);
     nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint,
-                                         std::optional<nsecs_t> lastVsyncOpt = {}) final
+                                         std::optional<nsecs_t> lastVsyncOpt = {}) const final
             EXCLUDES(mMutex);
     nsecs_t currentPeriod() const final EXCLUDES(mMutex);
     Period minFramePeriod() const final EXCLUDES(mMutex);
@@ -64,7 +62,7 @@
 
     VSyncPredictor::Model getVSyncPredictionModel() const EXCLUDES(mMutex);
 
-    bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) final EXCLUDES(mMutex);
+    bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const final EXCLUDES(mMutex);
 
     void setDisplayModePtr(ftl::NonNull<DisplayModePtr>) final EXCLUDES(mMutex);
 
@@ -77,42 +75,10 @@
     void dump(std::string& result) const final EXCLUDES(mMutex);
 
 private:
-    struct VsyncSequence {
-        nsecs_t vsyncTime;
-        int64_t seq;
-    };
-
-    struct MissedVsync {
-        TimePoint vsync;
-        Duration fixup = Duration::fromNs(0);
-    };
-
-    class VsyncTimeline {
-    public:
-        VsyncTimeline(Period idealPeriod, std::optional<Fps> renderRateOpt);
-        std::optional<TimePoint> nextAnticipatedVSyncTimeFrom(
-                Model model, Period minFramePeriod, nsecs_t vsyncTime, MissedVsync lastMissedVsync,
-                std::optional<nsecs_t> lastVsyncOpt = {});
-        void freeze(TimePoint lastVsync);
-        std::optional<TimePoint> validUntil() const { return mValidUntil; }
-        bool isVSyncInPhase(Model, nsecs_t vsync, Fps frameRate);
-        void shiftVsyncSequence(Duration phase);
-
-    private:
-        nsecs_t snapToVsyncAlignedWithRenderRate(Model model, nsecs_t vsync);
-        VsyncSequence getVsyncSequenceLocked(Model, nsecs_t vsync);
-
-        const Period mIdealPeriod = Duration::fromNs(0);
-        const std::optional<Fps> mRenderRateOpt;
-        std::optional<TimePoint> mValidUntil;
-        std::optional<VsyncSequence> mLastVsyncSequence;
-    };
-
     VSyncPredictor(VSyncPredictor const&) = delete;
     VSyncPredictor& operator=(VSyncPredictor const&) = delete;
     void clearTimestamps() REQUIRES(mMutex);
 
-    const std::unique_ptr<Clock> mClock;
     const PhysicalDisplayId mId;
 
     inline void traceInt64If(const char* name, int64_t value) const;
@@ -122,10 +88,16 @@
     bool validate(nsecs_t timestamp) const REQUIRES(mMutex);
     Model getVSyncPredictionModelLocked() const REQUIRES(mMutex);
     nsecs_t snapToVsync(nsecs_t timePoint) const REQUIRES(mMutex);
+    nsecs_t snapToVsyncAlignedWithRenderRate(nsecs_t timePoint) const REQUIRES(mMutex);
+    bool isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const REQUIRES(mMutex);
     Period minFramePeriodLocked() const REQUIRES(mMutex);
-    Duration ensureMinFrameDurationIsKept(TimePoint, TimePoint) REQUIRES(mMutex);
-    void purgeTimelines(android::TimePoint now) REQUIRES(mMutex);
+    void ensureMinFrameDurationIsKept(TimePoint, TimePoint) REQUIRES(mMutex);
 
+    struct VsyncSequence {
+        nsecs_t vsyncTime;
+        int64_t seq;
+    };
+    VsyncSequence getVsyncSequenceLocked(nsecs_t timestamp) const REQUIRES(mMutex);
     nsecs_t idealPeriod() const REQUIRES(mMutex);
 
     bool const mTraceOn;
@@ -143,15 +115,13 @@
     std::vector<nsecs_t> mTimestamps GUARDED_BY(mMutex);
 
     ftl::NonNull<DisplayModePtr> mDisplayModePtr GUARDED_BY(mMutex);
+    std::optional<Fps> mRenderRateOpt GUARDED_BY(mMutex);
+
+    mutable std::optional<VsyncSequence> mLastVsyncSequence GUARDED_BY(mMutex);
 
     std::deque<TimePoint> mPastExpectedPresentTimes GUARDED_BY(mMutex);
 
-    MissedVsync mMissedVsync GUARDED_BY(mMutex);
-
-    std::deque<VsyncTimeline> mTimelines GUARDED_BY(mMutex);
-    TimePoint mLastCommittedVsync GUARDED_BY(mMutex) = TimePoint::fromNs(0);
-    Period mIdealPeriod GUARDED_BY(mMutex) = Duration::fromNs(0);
-    std::optional<Fps> mRenderRateOpt GUARDED_BY(mMutex);
+    TimePoint mLastMissedVsync GUARDED_BY(mMutex);
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h
index 1e55a87..37bd4b4 100644
--- a/services/surfaceflinger/Scheduler/VSyncTracker.h
+++ b/services/surfaceflinger/Scheduler/VSyncTracker.h
@@ -56,8 +56,8 @@
      *                          and avoid crossing the minimal frame period of a VRR display.
      * \return                  A prediction of the timestamp of a vsync event.
      */
-    virtual nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint,
-                                                 std::optional<nsecs_t> lastVsyncOpt = {}) = 0;
+    virtual nsecs_t nextAnticipatedVSyncTimeFrom(
+            nsecs_t timePoint, std::optional<nsecs_t> lastVsyncOpt = {}) const = 0;
 
     /*
      * The current period of the vsync signal.
@@ -82,7 +82,7 @@
      * \param [in] timePoint  A vsync timestamp
      * \param [in] frameRate  The frame rate to check for
      */
-    virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) = 0;
+    virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const = 0;
 
     /*
      * Sets the active mode of the display which includes the vsync period and other VRR attributes.
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
index 2fa3318..001938c 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
@@ -120,8 +120,8 @@
     constexpr size_t kMinSamplesForPrediction = 6;
     constexpr uint32_t kDiscardOutlierPercent = 20;
 
-    return std::make_unique<VSyncPredictor>(std::make_unique<SystemClock>(), modePtr, kHistorySize,
-                                            kMinSamplesForPrediction, kDiscardOutlierPercent);
+    return std::make_unique<VSyncPredictor>(modePtr, kHistorySize, kMinSamplesForPrediction,
+                                            kDiscardOutlierPercent);
 }
 
 VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(TrackerPtr tracker) {
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h
index 881d678..85cd3e7 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.h
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h
@@ -81,7 +81,7 @@
     bool addResyncSample(TimePoint timestamp, ftl::Optional<Period> hwcVsyncPeriod);
 
     // TODO(b/185535769): Hide behind API.
-    VsyncTracker& getTracker() const { return *mTracker; }
+    const VsyncTracker& getTracker() const { return *mTracker; }
     VsyncTracker& getTracker() { return *mTracker; }
     VsyncController& getController() { return *mController; }
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 46a2259..2501f4b 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -854,6 +854,9 @@
     mCompositionEngine->getHwComposer().setCallback(*this);
     ClientCache::getInstance().setRenderEngine(&getRenderEngine());
 
+    mHasReliablePresentFences =
+            !getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE);
+
     enableLatchUnsignaledConfig = getLatchUnsignaledConfig();
 
     if (base::GetBoolProperty("debug.sf.enable_hwc_vds"s, false)) {
@@ -927,9 +930,7 @@
 
     // Inform native graphics APIs whether the present timestamp is supported:
 
-    const bool presentFenceReliable =
-            !getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE);
-    mStartPropertySetThread = getFactory().createStartPropertySetThread(presentFenceReliable);
+    mStartPropertySetThread = getFactory().createStartPropertySetThread(mHasReliablePresentFences);
 
     if (mStartPropertySetThread->Start() != NO_ERROR) {
         ALOGE("Run StartPropertySetThread failed!");
@@ -1003,9 +1004,7 @@
         FrameEvent::RELEASE,
     };
 
-    ConditionalLock lock(mStateLock, std::this_thread::get_id() != mMainThreadId);
-
-    if (!getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE)) {
+    if (mHasReliablePresentFences) {
         outSupported->push_back(FrameEvent::DISPLAY_PRESENT);
     }
     return NO_ERROR;
@@ -3031,10 +3030,9 @@
     // but that should be okay since CompositorTiming has snapping logic.
     const TimePoint compositeTime =
             TimePoint::fromNs(mCompositionEngine->getLastFrameRefreshTimestamp());
-    const Duration presentLatency =
-            getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE)
-            ? Duration::zero()
-            : mPresentLatencyTracker.trackPendingFrame(compositeTime, pacesetterPresentFenceTime);
+    const Duration presentLatency = mHasReliablePresentFences
+            ? mPresentLatencyTracker.trackPendingFrame(compositeTime, pacesetterPresentFenceTime)
+            : Duration::zero();
 
     const auto schedule = mScheduler->getVsyncSchedule();
     const TimePoint vsyncDeadline = schedule->vsyncDeadlineAfter(presentTime);
@@ -4357,7 +4355,7 @@
         features |= Feature::kTracePredictedVsync;
     }
     if (!base::GetBoolProperty("debug.sf.vsync_reactor_ignore_present_fences"s, false) &&
-        !getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE)) {
+        mHasReliablePresentFences) {
         features |= Feature::kPresentFences;
     }
     if (display->refreshRateSelector().kernelIdleTimerController()) {
@@ -4702,14 +4700,7 @@
         return TransactionReadiness::NotReady;
     }
 
-    const auto vsyncId = VsyncId{transaction.frameTimelineInfo.vsyncId};
-
-    // Transactions with VsyncId are already throttled by the vsyncId (i.e. Choreographer issued
-    // the vsyncId according to the frame rate override cadence) so we shouldn't throttle again
-    // when applying the transaction. Otherwise we might throttle older transactions
-    // incorrectly as the frame rate of SF changed before it drained the older transactions.
-    if (ftl::to_underlying(vsyncId) == FrameTimelineInfo::INVALID_VSYNC_ID &&
-        !mScheduler->isVsyncValid(expectedPresentTime, transaction.originUid)) {
+    if (!mScheduler->isVsyncValid(expectedPresentTime, transaction.originUid)) {
         ATRACE_FORMAT("!isVsyncValid expectedPresentTime: %" PRId64 " uid: %d", expectedPresentTime,
                       transaction.originUid);
         return TransactionReadiness::NotReady;
@@ -4717,7 +4708,8 @@
 
     // If the client didn't specify desiredPresentTime, use the vsyncId to determine the
     // expected present time of this transaction.
-    if (transaction.isAutoTimestamp && frameIsEarly(expectedPresentTime, vsyncId)) {
+    if (transaction.isAutoTimestamp &&
+        frameIsEarly(expectedPresentTime, VsyncId{transaction.frameTimelineInfo.vsyncId})) {
         ATRACE_FORMAT("frameIsEarly vsyncId: %" PRId64 " expectedPresentTime: %" PRId64,
                       transaction.frameTimelineInfo.vsyncId, expectedPresentTime);
         return TransactionReadiness::NotReady;
@@ -8097,9 +8089,6 @@
         return;
     }
 
-    // Snapshots must be taken from the main thread.
-    auto layers = mScheduler->schedule([=]() { return getLayerSnapshots(); }).get();
-
     // Loop over all visible layers to see whether there's any protected layer. A protected layer is
     // typically a layer with DRM contents, or have the GRALLOC_USAGE_PROTECTED set on the buffer.
     // A protected layer has no implication on whether it's secure, which is explicitly set by
@@ -8107,6 +8096,8 @@
     const bool supportsProtected = getRenderEngine().supportsProtectedContent();
     bool hasProtectedLayer = false;
     if (allowProtected && supportsProtected) {
+        // Snapshots must be taken from the main thread.
+        auto layers = mScheduler->schedule([=]() { return getLayerSnapshots(); }).get();
         hasProtectedLayer = layersHasProtectedLayer(layers);
     }
     const bool isProtected = hasProtectedLayer && allowProtected && supportsProtected;
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 1ce8606..005f2e6 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -1224,6 +1224,7 @@
     // constant members (no synchronization needed for access)
     const nsecs_t mBootTime = systemTime();
     bool mIsUserBuild = true;
+    bool mHasReliablePresentFences = false;
 
     // Can only accessed from the main thread, these members
     // don't need synchronization
diff --git a/services/surfaceflinger/common/Android.bp b/services/surfaceflinger/common/Android.bp
index f2ff00b..4a89dd0 100644
--- a/services/surfaceflinger/common/Android.bp
+++ b/services/surfaceflinger/common/Android.bp
@@ -35,6 +35,7 @@
     ],
     static_libs: [
         "libsurfaceflingerflags",
+        "android.os.flags-aconfig-cc",
     ],
 }
 
@@ -45,5 +46,30 @@
     ],
     static_libs: [
         "libsurfaceflingerflags_test",
+        "android.os.flags-aconfig-cc-test",
+    ],
+}
+
+cc_defaults {
+    name: "libsurfaceflinger_common_deps",
+    shared_libs: [
+        "server_configurable_flags",
+    ],
+    static_libs: [
+        "libsurfaceflinger_common",
+        "libsurfaceflingerflags",
+        "android.os.flags-aconfig-cc",
+    ],
+}
+
+cc_defaults {
+    name: "libsurfaceflinger_common_test_deps",
+    shared_libs: [
+        "server_configurable_flags",
+    ],
+    static_libs: [
+        "libsurfaceflinger_common_test",
+        "libsurfaceflingerflags_test",
+        "android.os.flags-aconfig-cc-test",
     ],
 }
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index b7f06a9..3b669c6 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -26,6 +26,7 @@
 #include <server_configurable_flags/get_flags.h>
 #include <cinttypes>
 
+#include <android_os.h>
 #include <com_android_graphics_surfaceflinger_flags.h>
 
 namespace android {
@@ -109,6 +110,7 @@
 
     /// Trunk stable server flags ///
     DUMP_SERVER_FLAG(refresh_rate_overlay_on_external_display);
+    DUMP_SERVER_FLAG(adpf_use_fmq_channel);
 
     /// Trunk stable readonly flags ///
     DUMP_READ_ONLY_FLAG(connected_display);
@@ -158,7 +160,7 @@
         return getServerConfigurableFlag(serverFlagName);                                   \
     }
 
-#define FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, checkForBootCompleted)                \
+#define FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, checkForBootCompleted, owner)         \
     bool FlagManager::name() const {                                                            \
         if (checkForBootCompleted) {                                                            \
             LOG_ALWAYS_FATAL_IF(!mBootCompleted,                                                \
@@ -166,21 +168,24 @@
                                 __func__);                                                      \
         }                                                                                       \
         static const std::optional<bool> debugOverride = getBoolProperty(syspropOverride);      \
-        static const bool value = getFlagValue([] { return flags::name(); }, debugOverride);    \
+        static const bool value = getFlagValue([] { return owner ::name(); }, debugOverride);   \
         if (mUnitTestMode) {                                                                    \
             /*                                                                                  \
              * When testing, we don't want to rely on the cached `value` or the debugOverride.  \
              */                                                                                 \
-            return flags::name();                                                               \
+            return owner ::name();                                                              \
         }                                                                                       \
         return value;                                                                           \
     }
 
 #define FLAG_MANAGER_SERVER_FLAG(name, syspropOverride) \
-    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, true)
+    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, true, flags)
 
 #define FLAG_MANAGER_READ_ONLY_FLAG(name, syspropOverride) \
-    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, false)
+    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, false, flags)
+
+#define FLAG_MANAGER_SERVER_FLAG_IMPORTED(name, syspropOverride, owner) \
+    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, true, owner)
 
 /// Legacy server flags ///
 FLAG_MANAGER_LEGACY_SERVER_FLAG(test_flag, "", "")
@@ -216,4 +221,7 @@
 /// Trunk stable server flags ///
 FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "")
 
+/// Trunk stable server flags from outside SurfaceFlinger ///
+FLAG_MANAGER_SERVER_FLAG_IMPORTED(adpf_use_fmq_channel, "", android::os)
+
 } // namespace android
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index 241c814..763963e 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -49,6 +49,7 @@
 
     /// Trunk stable server flags ///
     bool refresh_rate_overlay_on_external_display() const;
+    bool adpf_use_fmq_channel() const;
 
     /// Trunk stable readonly flags ///
     bool connected_display() const;
diff --git a/services/surfaceflinger/surfaceflinger_flags.aconfig b/services/surfaceflinger/surfaceflinger_flags.aconfig
index eda0674..5174fa7 100644
--- a/services/surfaceflinger/surfaceflinger_flags.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags.aconfig
@@ -1,3 +1,5 @@
+# This file is locked and should not be changed. Use surfaceflinger_flags_new.aconfig
+
 package: "com.android.graphics.surfaceflinger.flags"
 container: "system"
 
@@ -25,6 +27,8 @@
   is_fixed_read_only: true
 }
 
+# This file is locked and should not be changed. Use surfaceflinger_flags_new.aconfig
+
 flag {
   name: "enable_layer_command_batching"
   namespace: "core_graphics"
@@ -49,6 +53,8 @@
   is_fixed_read_only: true
 }
 
+# This file is locked and should not be changed. Use surfaceflinger_flags_new.aconfig
+
 flag {
   name: "hotplug2"
   namespace: "core_graphics"
@@ -73,6 +79,8 @@
   is_fixed_read_only: true
 }
 
+# This file is locked and should not be changed. Use surfaceflinger_flags_new.aconfig
+
 flag {
   name: "refresh_rate_overlay_on_external_display"
   namespace: "core_graphics"
@@ -98,6 +106,8 @@
 #   is_fixed_read_only: true
 # }
 
+# This file is locked and should not be changed. Use surfaceflinger_flags_new.aconfig
+
 flag {
   name: "cache_when_source_crop_layer_only_moved"
   namespace: "core_graphics"
@@ -130,6 +140,8 @@
   is_fixed_read_only: true
 }
 
+# This file is locked and should not be changed. Use surfaceflinger_flags_new.aconfig
+
 flag {
   name: "game_default_frame_rate"
   namespace: "game"
@@ -162,6 +174,8 @@
   is_fixed_read_only: true
 }
 
+# This file is locked and should not be changed. Use surfaceflinger_flags_new.aconfig
+
 flag {
   name: "renderable_buffer_usage"
   namespace: "core_graphics"
@@ -184,6 +198,8 @@
   }
 }
 
+# This file is locked and should not be changed. Use surfaceflinger_flags_new.aconfig
+
 flag {
   name: "dont_skip_on_early_ro"
   namespace: "core_graphics"
@@ -205,3 +221,5 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+# This file is locked and should not be changed. Use surfaceflinger_flags_new.aconfig
diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
new file mode 100644
index 0000000..5451752
--- /dev/null
+++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
@@ -0,0 +1,13 @@
+# IMPORTANT - please keep alphabetize to reduce merge conflicts
+
+package: "com.android.graphics.surfaceflinger.flags"
+container: "system"
+
+flag {
+  name: "dont_skip_on_early_ro2"
+  namespace: "core_graphics"
+  description: "This flag is guarding the behaviour where SurfaceFlinger is trying to opportunistically present a frame when the configuration change from late to early"
+  bug: "273702768"
+} # dont_skip_on_early_ro2
+
+# IMPORTANT - please keep alphabetize to reduce merge conflicts
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index dab0a3f..925fe0b 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -27,6 +27,7 @@
     defaults: [
         "android.hardware.graphics.common-ndk_shared",
         "surfaceflinger_defaults",
+        "libsurfaceflinger_common_test_deps",
     ],
     test_suites: ["device-tests"],
     srcs: [
@@ -66,7 +67,6 @@
     static_libs: [
         "liblayers_proto",
         "android.hardware.graphics.composer@2.1",
-        "libsurfaceflingerflags",
     ],
     shared_libs: [
         "android.hardware.graphics.common@1.2",
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index f529f7c..0c13db3 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -29,7 +29,7 @@
         "mock/DisplayHardware/MockComposer.cpp",
         "mock/DisplayHardware/MockHWC2.cpp",
         "mock/DisplayHardware/MockIPower.cpp",
-        "mock/DisplayHardware/MockIPowerHintSession.cpp",
+        "mock/DisplayHardware/MockPowerHintSessionWrapper.cpp",
         "mock/DisplayHardware/MockPowerAdvisor.cpp",
         "mock/MockEventThread.cpp",
         "mock/MockFrameTimeline.cpp",
@@ -149,6 +149,7 @@
         "android.hardware.graphics.composer3-ndk_static",
         "android.hardware.power-ndk_static",
         "librenderengine_deps",
+        "libsurfaceflinger_common_test_deps",
     ],
     static_libs: [
         "android.hardware.common-V2-ndk",
@@ -173,13 +174,11 @@
         "librenderengine_mocks",
         "libscheduler",
         "libserviceutils",
-        "libsurfaceflinger_common_test",
         "libtimestats",
         "libtimestats_atoms_proto",
         "libtimestats_proto",
         "libtonemap",
         "perfetto_trace_protos",
-        "libsurfaceflingerflags_test",
     ],
     shared_libs: [
         "android.hardware.configstore-utils",
@@ -208,7 +207,6 @@
         "libsync",
         "libui",
         "libutils",
-        "server_configurable_flags",
     ],
     header_libs: [
         "android.hardware.graphics.composer3-command-buffer",
diff --git a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
index 9c66a97..1d44a3e 100644
--- a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
@@ -26,8 +26,8 @@
 #include <chrono>
 #include <future>
 #include "TestableSurfaceFlinger.h"
-#include "mock/DisplayHardware/MockIPowerHintSession.h"
 #include "mock/DisplayHardware/MockPowerHalController.h"
+#include "mock/DisplayHardware/MockPowerHintSessionWrapper.h"
 
 using namespace android;
 using namespace android::Hwc2::mock;
@@ -54,7 +54,7 @@
     TestableSurfaceFlinger mFlinger;
     std::unique_ptr<PowerAdvisor> mPowerAdvisor;
     MockPowerHalController* mMockPowerHalController;
-    std::shared_ptr<MockIPowerHintSession> mMockPowerHintSession;
+    std::shared_ptr<MockPowerHintSessionWrapper> mMockPowerHintSession;
 };
 
 bool PowerAdvisorTest::sessionExists() {
@@ -68,25 +68,29 @@
     mMockPowerHalController =
             reinterpret_cast<MockPowerHalController*>(mPowerAdvisor->mPowerHal.get());
     ON_CALL(*mMockPowerHalController, getHintSessionPreferredRate)
-            .WillByDefault(Return(HalResult<int64_t>::fromStatus(binder::Status::ok(), 16000)));
+            .WillByDefault(Return(
+                    ByMove(HalResult<int64_t>::fromStatus(ndk::ScopedAStatus::ok(), 16000))));
 }
 
 void PowerAdvisorTest::startPowerHintSession(bool returnValidSession) {
-    mMockPowerHintSession = ndk::SharedRefBase::make<NiceMock<MockIPowerHintSession>>();
+    mMockPowerHintSession = std::make_shared<NiceMock<MockPowerHintSessionWrapper>>();
     if (returnValidSession) {
         ON_CALL(*mMockPowerHalController, createHintSession)
-                .WillByDefault(
-                        Return(HalResult<std::shared_ptr<IPowerHintSession>>::
-                                       fromStatus(binder::Status::ok(), mMockPowerHintSession)));
+                .WillByDefault([&](int32_t, int32_t, const std::vector<int32_t>&, int64_t) {
+                    return HalResult<std::shared_ptr<PowerHintSessionWrapper>>::
+                            fromStatus(ndk::ScopedAStatus::ok(), mMockPowerHintSession);
+                });
     } else {
-        ON_CALL(*mMockPowerHalController, createHintSession)
-                .WillByDefault(Return(HalResult<std::shared_ptr<IPowerHintSession>>::
-                                              fromStatus(binder::Status::ok(), nullptr)));
+        ON_CALL(*mMockPowerHalController, createHintSession).WillByDefault([] {
+            return HalResult<
+                    std::shared_ptr<PowerHintSessionWrapper>>::fromStatus(ndk::ScopedAStatus::ok(),
+                                                                          nullptr);
+        });
     }
     mPowerAdvisor->enablePowerHintSession(true);
     mPowerAdvisor->startPowerHintSession({1, 2, 3});
     ON_CALL(*mMockPowerHintSession, updateTargetWorkDuration)
-            .WillByDefault(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
+            .WillByDefault(Return(testing::ByMove(HalResult<void>::ok())));
 }
 
 void PowerAdvisorTest::setExpectedTiming(Duration totalFrameTargetDuration,
@@ -148,7 +152,7 @@
                 reportActualWorkDuration(ElementsAre(
                         Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns())))))
             .Times(1)
-            .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
+            .WillOnce(Return(testing::ByMove(HalResult<void>::ok())));
     fakeBasicFrameTiming(startTime, vsyncPeriod);
     setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
     mPowerAdvisor->setDisplays(displayIds);
@@ -188,7 +192,7 @@
                 reportActualWorkDuration(ElementsAre(
                         Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns())))))
             .Times(1)
-            .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
+            .WillOnce(Return(testing::ByMove(HalResult<void>::ok())));
 
     fakeBasicFrameTiming(startTime, vsyncPeriod);
     setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
@@ -231,7 +235,7 @@
                 reportActualWorkDuration(ElementsAre(
                         Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns())))))
             .Times(1)
-            .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
+            .WillOnce(Return(testing::ByMove(HalResult<void>::ok())));
 
     fakeBasicFrameTiming(startTime, vsyncPeriod);
     setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
@@ -328,17 +332,17 @@
 
     ON_CALL(*mMockPowerHintSession, sendHint).WillByDefault([&letSendHintFinish] {
         letSendHintFinish.get_future().wait();
-        return ndk::ScopedAStatus::fromExceptionCode(-127);
+        return HalResult<void>::fromStatus(ndk::ScopedAStatus::fromExceptionCode(-127));
     });
 
     ON_CALL(*mMockPowerHintSession, reportActualWorkDuration).WillByDefault([] {
-        return ndk::ScopedAStatus::fromExceptionCode(-127);
+        return HalResult<void>::fromStatus(ndk::ScopedAStatus::fromExceptionCode(-127));
     });
 
-    ON_CALL(*mMockPowerHalController, createHintSession)
-            .WillByDefault(Return(
-                    HalResult<std::shared_ptr<IPowerHintSession>>::
-                            fromStatus(ndk::ScopedAStatus::fromExceptionCode(-127), nullptr)));
+    ON_CALL(*mMockPowerHalController, createHintSession).WillByDefault([] {
+        return HalResult<std::shared_ptr<PowerHintSessionWrapper>>::
+                fromStatus(ndk::ScopedAStatus::fromExceptionCode(-127), nullptr);
+    });
 
     // First background call, to notice the session is down
     auto firstHint = std::async(std::launch::async, [this] {
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index 39a8aac..0a6e305 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -3159,6 +3159,210 @@
     EXPECT_TRUE(frameRateOverrides.empty());
 }
 
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_withFrameRateCategory) {
+    if (GetParam() == Config::FrameRateOverride::Disabled) {
+        return;
+    }
+
+    ASSERT_TRUE(GetParam() == Config::FrameRateOverride::AppOverrideNativeRefreshRates ||
+                GetParam() == Config::FrameRateOverride::AppOverride ||
+                GetParam() == Config::FrameRateOverride::Enabled);
+
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+    std::vector<LayerRequirement> layers = {{.ownerUid = 1234, .weight = 1.f},
+                                            {.ownerUid = 1234, .weight = 1.f}};
+
+    // HighHint case with touch boost and thus should skip frame rate override.
+    layers[0].name = "ExplicitCategory HighHint";
+    layers[0].vote = LayerVoteType::ExplicitCategory;
+    layers[0].desiredRefreshRate = 0_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::HighHint;
+    layers[1].name = "ExplicitCategory High";
+    layers[1].vote = LayerVoteType::ExplicitCategory;
+    layers[1].desiredRefreshRate = 0_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::High;
+    auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_TRUE(frameRateOverrides.empty());
+
+    // HighHint case with touch boost and thus should skip frame rate override.
+    layers[0].name = "ExplicitCategory HighHint";
+    layers[0].vote = LayerVoteType::ExplicitCategory;
+    layers[0].desiredRefreshRate = 0_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::HighHint;
+    layers[1].name = "ExplicitCategory Normal";
+    layers[1].vote = LayerVoteType::ExplicitCategory;
+    layers[1].desiredRefreshRate = 0_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::Normal;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_TRUE(frameRateOverrides.empty());
+
+    // HighHint case with touch boost and thus should skip frame rate override.
+    layers[0].name = "ExplicitCategory HighHint";
+    layers[0].vote = LayerVoteType::ExplicitCategory;
+    layers[0].desiredRefreshRate = 0_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::HighHint;
+    layers[1].name = "ExplicitCategory Low";
+    layers[1].vote = LayerVoteType::ExplicitCategory;
+    layers[1].desiredRefreshRate = 0_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::Low;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_TRUE(frameRateOverrides.empty());
+
+    // HighHint case with touch boost and thus should skip frame rate override.
+    layers[0].name = "ExplicitCategory HighHint";
+    layers[0].vote = LayerVoteType::ExplicitCategory;
+    layers[0].desiredRefreshRate = 0_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::HighHint;
+    layers[1].name = "ExplicitCategory NoPreference";
+    layers[1].vote = LayerVoteType::ExplicitCategory;
+    layers[1].desiredRefreshRate = 0_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::NoPreference;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_TRUE(frameRateOverrides.empty());
+
+    // HighHint case *without* touch boost has frame rate override.
+    // For example, game and touch interaction.
+    layers[0].name = "ExplicitCategory HighHint";
+    layers[0].vote = LayerVoteType::ExplicitCategory;
+    layers[0].desiredRefreshRate = 0_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::HighHint;
+    layers[1].name = "ExplicitDefault 60";
+    layers[1].vote = LayerVoteType::ExplicitDefault;
+    layers[1].desiredRefreshRate = 60_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::Default;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+    // HighHint case with touch boost and thus should skip frame rate override.
+    layers[0].name = "ExplicitCategory HighHint";
+    layers[0].vote = LayerVoteType::ExplicitCategory;
+    layers[0].desiredRefreshRate = 0_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::HighHint;
+    layers[1].name = "ExplicitExactOrMultiple 30";
+    layers[1].vote = LayerVoteType::ExplicitExactOrMultiple;
+    layers[1].desiredRefreshRate = 30_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::Default;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_TRUE(frameRateOverrides.empty());
+
+    // HighHint case with touch boost and thus should skip frame rate override.
+    layers[0].name = "ExplicitCategory HighHint";
+    layers[0].vote = LayerVoteType::ExplicitCategory;
+    layers[0].desiredRefreshRate = 0_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::HighHint;
+    layers[1].name = "ExplicitExact 60";
+    layers[1].vote = LayerVoteType::ExplicitExact;
+    layers[1].desiredRefreshRate = 60_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::Default;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_TRUE(frameRateOverrides.empty());
+
+    // HighHint case with touch boost and thus should skip frame rate override.
+    layers[0].name = "ExplicitCategory HighHint";
+    layers[0].vote = LayerVoteType::ExplicitCategory;
+    layers[0].desiredRefreshRate = 0_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::HighHint;
+    layers[1].name = "ExplicitGte 60";
+    layers[1].vote = LayerVoteType::ExplicitGte;
+    layers[1].desiredRefreshRate = 60_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::Default;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_TRUE(frameRateOverrides.empty());
+
+    // ExplicitCategory case that expects no global touch boost and thus has frame rate override.
+    layers[0].name = "ExplicitDefault 60";
+    layers[0].vote = LayerVoteType::ExplicitDefault;
+    layers[0].desiredRefreshRate = 60_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::Default;
+    layers[1].name = "ExplicitCategory High";
+    layers[1].vote = LayerVoteType::ExplicitCategory;
+    layers[1].desiredRefreshRate = 0_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::High;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(120_Hz, frameRateOverrides.at(1234));
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(120_Hz, frameRateOverrides.at(1234));
+
+    // ExplicitCategory case that expects no global touch boost and thus has frame rate override.
+    layers[0].name = "ExplicitDefault 60";
+    layers[0].vote = LayerVoteType::ExplicitDefault;
+    layers[0].desiredRefreshRate = 60_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::Default;
+    layers[1].name = "ExplicitCategory Normal";
+    layers[1].vote = LayerVoteType::ExplicitCategory;
+    layers[1].desiredRefreshRate = 0_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::Normal;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+    // ExplicitCategory case that expects no global touch boost and thus has frame rate override.
+    layers[0].name = "ExplicitDefault 60";
+    layers[0].vote = LayerVoteType::ExplicitDefault;
+    layers[0].desiredRefreshRate = 60_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::Default;
+    layers[1].name = "ExplicitCategory Low";
+    layers[1].vote = LayerVoteType::ExplicitCategory;
+    layers[1].desiredRefreshRate = 0_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::Low;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+    // ExplicitCategory case that expects no global touch boost and thus has frame rate override.
+    layers[0].name = "ExplicitDefault 60";
+    layers[0].vote = LayerVoteType::ExplicitDefault;
+    layers[0].desiredRefreshRate = 60_Hz;
+    layers[0].frameRateCategory = FrameRateCategory::Default;
+    layers[1].name = "ExplicitCategory NoPreference";
+    layers[1].vote = LayerVoteType::ExplicitCategory;
+    layers[1].desiredRefreshRate = 0_Hz;
+    layers[1].frameRateCategory = FrameRateCategory::NoPreference;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+}
+
 TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_touch) {
     if (GetParam() == Config::FrameRateOverride::Disabled) {
         return;
@@ -3204,6 +3408,17 @@
 
     frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
     EXPECT_TRUE(frameRateOverrides.empty());
+
+    layers[0].vote = LayerVoteType::ExplicitGte;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
 }
 
 TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_DivisorIsNotDisplayRefreshRate) {
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 049b092..10e2220 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -25,7 +25,6 @@
 #include "Scheduler/EventThread.h"
 #include "Scheduler/RefreshRateSelector.h"
 #include "Scheduler/VSyncPredictor.h"
-#include "Scheduler/VSyncReactor.h"
 #include "TestableScheduler.h"
 #include "TestableSurfaceFlinger.h"
 #include "mock/DisplayHardware/MockDisplayMode.h"
@@ -564,8 +563,7 @@
                                  hal::VrrConfig{.minFrameIntervalNs = static_cast<int32_t>(
                                                         frameRate.getPeriodNsecs())}));
     std::shared_ptr<VSyncPredictor> vrrTracker =
-            std::make_shared<VSyncPredictor>(std::make_unique<SystemClock>(), kMode, kHistorySize,
-                                             kMinimumSamplesForPrediction,
+            std::make_shared<VSyncPredictor>(kMode, kHistorySize, kMinimumSamplesForPrediction,
                                              kOutlierTolerancePercent);
     std::shared_ptr<RefreshRateSelector> vrrSelectorPtr =
             std::make_shared<RefreshRateSelector>(makeModes(kMode), kMode->getId());
@@ -580,8 +578,6 @@
     vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate);
     scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate);
     vrrTracker->addVsyncTimestamp(0);
-    // Set 1000 as vsync seq #0
-    vrrTracker->nextAnticipatedVSyncTimeFrom(700);
 
     EXPECT_EQ(Fps::fromPeriodNsecs(1000),
               scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
@@ -591,7 +587,7 @@
                                              TimePoint::fromNs(2000)));
 
     // Not crossing the min frame period
-    EXPECT_EQ(Fps::fromPeriodNsecs(1000),
+    EXPECT_EQ(Fps::fromPeriodNsecs(1500),
               scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
                                              TimePoint::fromNs(2500)));
     // Change render rate
@@ -599,9 +595,6 @@
     vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate);
     scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate);
 
-    // Set 2000 as vsync seq #0
-    vrrTracker->nextAnticipatedVSyncTimeFrom(1700);
-
     EXPECT_EQ(Fps::fromPeriodNsecs(2000),
               scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
                                              TimePoint::fromNs(2000)));
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
index c22deab..d891008 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
@@ -48,7 +48,7 @@
     Period minFramePeriod() const final { return Period::fromNs(currentPeriod()); }
     void resetModel() final {}
     bool needsMoreSamples() const final { return false; }
-    bool isVSyncInPhase(nsecs_t, Fps) final { return false; }
+    bool isVSyncInPhase(nsecs_t, Fps) const final { return false; }
     void setDisplayModePtr(ftl::NonNull<DisplayModePtr>) final {}
     void setRenderRate(Fps) final {}
     void onFrameBegin(TimePoint, TimePoint) final {}
@@ -64,7 +64,7 @@
 public:
     FixedRateIdealStubTracker() : StubTracker{toNs(3ms)} {}
 
-    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, std::optional<nsecs_t>) final {
+    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, std::optional<nsecs_t>) const final {
         auto const floor = timePoint % mPeriod;
         if (floor == 0) {
             return timePoint;
@@ -77,7 +77,7 @@
 public:
     VRRStubTracker(nsecs_t period) : StubTracker(period) {}
 
-    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point, std::optional<nsecs_t>) final {
+    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point, std::optional<nsecs_t>) const final {
         std::lock_guard lock(mMutex);
         auto const normalized_to_base = time_point - mBase;
         auto const floor = (normalized_to_base) % mPeriod;
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index 6b9ea56..b9f3d70 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -75,28 +75,6 @@
     return ftl::as_non_null(createDisplayMode(DisplayModeId(0), refreshRate, kGroup, kResolution,
                                               DEFAULT_DISPLAY_ID));
 }
-
-class TestClock : public Clock {
-public:
-    TestClock() = default;
-
-    nsecs_t now() const override { return mNow; }
-    void setNow(nsecs_t now) { mNow = now; }
-
-private:
-    nsecs_t mNow = 0;
-};
-
-class ClockWrapper : public Clock {
-public:
-    ClockWrapper(std::shared_ptr<Clock> const& clock) : mClock(clock) {}
-
-    nsecs_t now() const { return mClock->now(); }
-
-private:
-    std::shared_ptr<Clock> const mClock;
-};
-
 } // namespace
 
 struct VSyncPredictorTest : testing::Test {
@@ -108,10 +86,8 @@
     static constexpr size_t kOutlierTolerancePercent = 25;
     static constexpr nsecs_t mMaxRoundingError = 100;
 
-    std::shared_ptr<TestClock> mClock{std::make_shared<TestClock>()};
-
-    VSyncPredictor tracker{std::make_unique<ClockWrapper>(mClock), mMode, kHistorySize,
-                           kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+    VSyncPredictor tracker{mMode, kHistorySize, kMinimumSamplesForPrediction,
+                           kOutlierTolerancePercent};
 };
 
 TEST_F(VSyncPredictorTest, reportsAnticipatedPeriod) {
@@ -432,8 +408,7 @@
 // See b/151146131
 TEST_F(VSyncPredictorTest, hasEnoughPrecision) {
     const auto mode = displayMode(mPeriod);
-    VSyncPredictor tracker{std::make_unique<ClockWrapper>(mClock), mode, 20,
-                           kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+    VSyncPredictor tracker{mode, 20, kMinimumSamplesForPrediction, kOutlierTolerancePercent};
     std::vector<nsecs_t> const simulatedVsyncs{840873348817, 840890049444, 840906762675,
                                                840923581635, 840940161584, 840956868096,
                                                840973702473, 840990256277, 841007116851,
@@ -631,6 +606,35 @@
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 7 * mPeriod));
 }
 
+TEST_F(VSyncPredictorTest, setRenderRateOfDivisorIsInPhase) {
+    auto last = mNow;
+    for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
+        EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
+        mNow += mPeriod;
+        last = mNow;
+        tracker.addVsyncTimestamp(mNow);
+    }
+
+    const auto refreshRate = Fps::fromPeriodNsecs(mPeriod);
+
+    tracker.setRenderRate(refreshRate / 4);
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 3 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 7 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 11 * mPeriod));
+
+    tracker.setRenderRate(refreshRate / 2);
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 3 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 5 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5 * mPeriod), Eq(mNow + 7 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 9 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 9 * mPeriod), Eq(mNow + 11 * mPeriod));
+
+    tracker.setRenderRate(refreshRate / 6);
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 7 * mPeriod));
+}
+
 TEST_F(VSyncPredictorTest, setRenderRateIsIgnoredIfNotDivisor) {
     auto last = mNow;
     for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
@@ -666,8 +670,8 @@
                                      .setVrrConfig(std::move(vrrConfig))
                                      .build());
 
-    VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
-                              kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+    VSyncPredictor vrrTracker{kMode, kHistorySize, kMinimumSamplesForPrediction,
+                              kOutlierTolerancePercent};
 
     vrrTracker.setRenderRate(minFrameRate);
     vrrTracker.addVsyncTimestamp(0);
@@ -683,44 +687,7 @@
     vrrTracker.onFrameMissed(TimePoint::fromNs(4500));
     EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4500, 4500));
     EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
-
-    vrrTracker.onFrameBegin(TimePoint::fromNs(7000), TimePoint::fromNs(6500));
-    EXPECT_EQ(10500, vrrTracker.nextAnticipatedVSyncTimeFrom(9000, 7000));
 }
-
-TEST_F(VSyncPredictorTest, renderRateIsPreservedForCommittedVsyncs) {
-    tracker.addVsyncTimestamp(1000);
-
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000));
-
-    tracker.setRenderRate(Fps::fromPeriodNsecs(2000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(7001), Eq(8000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(8001), Eq(10000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(10000));
-
-    tracker.setRenderRate(Fps::fromPeriodNsecs(3000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(7001), Eq(8000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(8001), Eq(10000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(10000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(10001), Eq(11000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(11001), Eq(14000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(12001), Eq(14000));
-
-    // Check the purge logic works
-    mClock->setNow(20000);
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(2000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(8000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(8000));
-}
-
 } // namespace android::scheduler
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
deleted file mode 100644
index 27564b2..0000000
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.
- */
-
-#pragma once
-
-#include "binder/Status.h"
-
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-#include <aidl/android/hardware/power/IPower.h>
-#pragma clang diagnostic pop
-
-#include <gmock/gmock.h>
-
-using aidl::android::hardware::power::IPowerHintSession;
-using aidl::android::hardware::power::SessionConfig;
-using aidl::android::hardware::power::SessionHint;
-using aidl::android::hardware::power::SessionMode;
-using android::binder::Status;
-
-using namespace aidl::android::hardware::power;
-
-namespace android::Hwc2::mock {
-
-class MockIPowerHintSession : public IPowerHintSession {
-public:
-    MockIPowerHintSession();
-
-    MOCK_METHOD(ndk::SpAIBinder, asBinder, (), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, pause, (), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, resume, (), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, close, (), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t * version), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string * hash), (override));
-    MOCK_METHOD(bool, isRemote, (), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, updateTargetWorkDuration, (int64_t), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, reportActualWorkDuration, (const ::std::vector<WorkDuration>&),
-                (override));
-    MOCK_METHOD(ndk::ScopedAStatus, sendHint, (SessionHint), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, setThreads, (const ::std::vector<int32_t>&), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, setMode, (SessionMode, bool), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, getSessionConfig, (SessionConfig * _aidl_return), (override));
-};
-
-} // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
index ae41e7e..af1862d 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
@@ -44,10 +44,10 @@
     MOCK_METHOD(HalResult<void>, setBoost, (aidl::android::hardware::power::Boost, int32_t),
                 (override));
     MOCK_METHOD(HalResult<void>, setMode, (aidl::android::hardware::power::Mode, bool), (override));
-    MOCK_METHOD(HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>,
+    MOCK_METHOD(HalResult<std::shared_ptr<android::power::PowerHintSessionWrapper>>,
                 createHintSession, (int32_t, int32_t, const std::vector<int32_t>&, int64_t),
                 (override));
-    MOCK_METHOD(HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>,
+    MOCK_METHOD(HalResult<std::shared_ptr<android::power::PowerHintSessionWrapper>>,
                 createHintSessionWithConfig,
                 (int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
                  int64_t durationNanos, aidl::android::hardware::power::SessionTag tag,
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.cpp b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.cpp
similarity index 75%
rename from services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.cpp
rename to services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.cpp
index 770bc15..d383283 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.cpp
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2024 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.
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-#include "mock/DisplayHardware/MockIPowerHintSession.h"
+#include "mock/DisplayHardware/MockPowerHintSessionWrapper.h"
 
 namespace android::Hwc2::mock {
 
 // Explicit default instantiation is recommended.
-MockIPowerHintSession::MockIPowerHintSession() = default;
+MockPowerHintSessionWrapper::MockPowerHintSessionWrapper()
+      : power::PowerHintSessionWrapper(nullptr) {}
 
 } // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.h
new file mode 100644
index 0000000..bc6950c
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "binder/Status.h"
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+#include <aidl/android/hardware/power/IPower.h>
+#include <powermanager/PowerHintSessionWrapper.h>
+#pragma clang diagnostic pop
+
+#include <gmock/gmock.h>
+
+using aidl::android::hardware::power::IPowerHintSession;
+using aidl::android::hardware::power::SessionConfig;
+using aidl::android::hardware::power::SessionHint;
+using aidl::android::hardware::power::SessionMode;
+using android::binder::Status;
+
+using namespace aidl::android::hardware::power;
+
+namespace android::Hwc2::mock {
+
+class MockPowerHintSessionWrapper : public power::PowerHintSessionWrapper {
+public:
+    MockPowerHintSessionWrapper();
+
+    MOCK_METHOD(power::HalResult<void>, updateTargetWorkDuration, (int64_t), (override));
+    MOCK_METHOD(power::HalResult<void>, reportActualWorkDuration,
+                (const ::std::vector<WorkDuration>&), (override));
+    MOCK_METHOD(power::HalResult<void>, pause, (), (override));
+    MOCK_METHOD(power::HalResult<void>, resume, (), (override));
+    MOCK_METHOD(power::HalResult<void>, close, (), (override));
+    MOCK_METHOD(power::HalResult<void>, sendHint, (SessionHint), (override));
+    MOCK_METHOD(power::HalResult<void>, setThreads, (const ::std::vector<int32_t>&), (override));
+    MOCK_METHOD(power::HalResult<void>, setMode, (SessionMode, bool), (override));
+    MOCK_METHOD(power::HalResult<SessionConfig>, getSessionConfig, (), (override));
+};
+
+} // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
index 6d10a5c..3870983 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
@@ -29,12 +29,12 @@
 
     MOCK_METHOD(bool, addVsyncTimestamp, (nsecs_t), (override));
     MOCK_METHOD(nsecs_t, nextAnticipatedVSyncTimeFrom, (nsecs_t, std::optional<nsecs_t>),
-                (override));
+                (const, override));
     MOCK_METHOD(nsecs_t, currentPeriod, (), (const, override));
     MOCK_METHOD(Period, minFramePeriod, (), (const, override));
     MOCK_METHOD(void, resetModel, (), (override));
     MOCK_METHOD(bool, needsMoreSamples, (), (const, override));
-    MOCK_METHOD(bool, isVSyncInPhase, (nsecs_t, Fps), (override));
+    MOCK_METHOD(bool, isVSyncInPhase, (nsecs_t, Fps), (const, override));
     MOCK_METHOD(void, setDisplayModePtr, (ftl::NonNull<DisplayModePtr>), (override));
     MOCK_METHOD(void, setRenderRate, (Fps), (override));
     MOCK_METHOD(void, onFrameBegin, (TimePoint, TimePoint), (override));