Merge "Revert^2 "Mark @FlaggedApi flags as exported"" into main
diff --git a/cmds/dumpsys/tests/dumpsys_test.cpp b/cmds/dumpsys/tests/dumpsys_test.cpp
index b8e5ce1..17ae0aa 100644
--- a/cmds/dumpsys/tests/dumpsys_test.cpp
+++ b/cmds/dumpsys/tests/dumpsys_test.cpp
@@ -67,6 +67,8 @@
     MOCK_METHOD2(unregisterForNotifications, status_t(const String16&,
                                              const sp<LocalRegistrationCallback>&));
     MOCK_METHOD0(getServiceDebugInfo, std::vector<ServiceDebugInfo>());
+    MOCK_METHOD1(enableAddServiceCache, void(bool));
+
   protected:
     MOCK_METHOD0(onAsBinder, IBinder*());
 };
diff --git a/cmds/idlcli/Android.bp b/cmds/idlcli/Android.bp
index 50c2cd8..36dcbca 100644
--- a/cmds/idlcli/Android.bp
+++ b/cmds/idlcli/Android.bp
@@ -50,6 +50,7 @@
         "vibrator/CommandAlwaysOnEnable.cpp",
         "vibrator/CommandCompose.cpp",
         "vibrator/CommandComposePwle.cpp",
+        "vibrator/CommandGetFrequencyToOutputAccelerationMap.cpp",
         "vibrator/CommandGetBandwidthAmplitudeMap.cpp",
         "vibrator/CommandGetCapabilities.cpp",
         "vibrator/CommandGetCompositionDelayMax.cpp",
@@ -72,6 +73,11 @@
         "vibrator/CommandSetExternalControl.cpp",
         "vibrator/CommandSupportsAmplitudeControl.cpp",
         "vibrator/CommandSupportsExternalControl.cpp",
+        "vibrator/CommandGetPwleV2PrimitiveDurationMaxMillis.cpp",
+        "vibrator/CommandGetPwleV2CompositionSizeMax.cpp",
+        "vibrator/CommandGetPwleV2PrimitiveDurationMinMillis.cpp",
+        "vibrator/CommandComposePwleV2.cpp",
+        "vibrator/CommandPerformVendorEffect.cpp",
     ],
     visibility: [":__subpackages__"],
 }
diff --git a/cmds/idlcli/vibrator/CommandComposePwleV2.cpp b/cmds/idlcli/vibrator/CommandComposePwleV2.cpp
new file mode 100644
index 0000000..6d3cf84
--- /dev/null
+++ b/cmds/idlcli/vibrator/CommandComposePwleV2.cpp
@@ -0,0 +1,146 @@
+/*
+ * 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 <stdlib.h>
+
+#include <charconv>
+
+#include "utils.h"
+#include "vibrator.h"
+
+namespace android {
+namespace idlcli {
+
+class CommandVibrator;
+
+namespace vibrator {
+
+using aidl::CompositePwleV2;
+using aidl::PwleV2Primitive;
+
+class CommandComposePwleV2 : public Command {
+    std::string getDescription() const override { return "Compose normalized PWLE vibration."; }
+
+    std::string getUsageSummary() const override {
+        return "[options] <time> <frequency> <amplitude> ...";
+    }
+
+    UsageDetails getUsageDetails() const override {
+        UsageDetails details{
+                {"-b", {"Block for duration of vibration."}},
+                {"<time>", {"Segment duration in milliseconds"}},
+                {"<frequency>", {"Target frequency in Hz"}},
+                {"<amplitude>", {"Target amplitude in [0.0, 1.0]"}},
+                {"...", {"May repeat multiple times."}},
+        };
+        return details;
+    }
+
+    Status doArgs(Args& args) override {
+        while (args.get<std::string>().value_or("").find("-") == 0) {
+            auto opt = *args.pop<std::string>();
+            if (opt == "--") {
+                break;
+            } else if (opt == "-b") {
+                mBlocking = true;
+            } else {
+                std::cerr << "Invalid Option '" << opt << "'!" << std::endl;
+                return USAGE;
+            }
+        }
+
+        if (args.empty()) {
+            std::cerr << "Missing arguments! Please see usage" << std::endl;
+            return USAGE;
+        }
+
+        while (!args.empty()) {
+            PwleV2Primitive segment;
+
+            if (auto timeMs = args.pop<decltype(segment.timeMillis)>();
+                timeMs && *timeMs >= 0 && *timeMs <= 0x7ffff) {
+                segment.timeMillis = *timeMs;
+                std::cout << "Time: " << segment.timeMillis << std::endl;
+            } else {
+                std::cerr << "Missing or Invalid Time!" << std::endl;
+                return USAGE;
+            }
+
+            if (auto frequencyHz = args.pop<decltype(segment.frequencyHz)>();
+                frequencyHz && *frequencyHz >= 30 && *frequencyHz <= 300) {
+                segment.frequencyHz = *frequencyHz;
+                std::cout << "Frequency: " << segment.frequencyHz << std::endl;
+            } else {
+                std::cerr << "Missing or Invalid Frequency!" << std::endl;
+                return USAGE;
+            }
+
+            if (auto amplitude = args.pop<decltype(segment.amplitude)>();
+                amplitude && *amplitude >= 0 && *amplitude <= 1.0) {
+                segment.amplitude = *amplitude;
+                std::cout << "Amplitude: " << segment.amplitude << std::endl;
+            } else {
+                std::cerr << "Missing or Invalid Amplitude!" << std::endl;
+                return USAGE;
+            }
+
+            mCompositePwle.pwlePrimitives.emplace_back(std::move(segment));
+        }
+
+        if (!args.empty()) {
+            std::cerr << "Unexpected Arguments!" << std::endl;
+            return USAGE;
+        }
+
+        return OK;
+    }
+
+    Status doMain(Args&& /*args*/) override {
+        auto hal = getHal<aidl::IVibrator>();
+
+        if (!hal) {
+            return UNAVAILABLE;
+        }
+
+        ABinderProcess_setThreadPoolMaxThreadCount(1);
+        ABinderProcess_startThreadPool();
+
+        std::shared_ptr<VibratorCallback> callback;
+
+        if (mBlocking) {
+            callback = ndk::SharedRefBase::make<VibratorCallback>();
+        }
+
+        auto status = hal->call(&aidl::IVibrator::composePwleV2, mCompositePwle, callback);
+
+        if (status.isOk() && callback) {
+            callback->waitForComplete();
+        }
+
+        std::cout << "Status: " << status.getDescription() << std::endl;
+
+        return status.isOk() ? OK : ERROR;
+    }
+
+    bool mBlocking;
+    CompositePwleV2 mCompositePwle;
+};
+
+static const auto Command =
+        CommandRegistry<CommandVibrator>::Register<CommandComposePwleV2>("composePwleV2");
+
+} // namespace vibrator
+} // namespace idlcli
+} // namespace android
diff --git a/cmds/idlcli/vibrator/CommandGetFrequencyToOutputAccelerationMap.cpp b/cmds/idlcli/vibrator/CommandGetFrequencyToOutputAccelerationMap.cpp
new file mode 100644
index 0000000..2edd0ca
--- /dev/null
+++ b/cmds/idlcli/vibrator/CommandGetFrequencyToOutputAccelerationMap.cpp
@@ -0,0 +1,78 @@
+/*
+ * 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 "utils.h"
+#include "vibrator.h"
+
+namespace android {
+namespace idlcli {
+
+class CommandVibrator;
+
+namespace vibrator {
+
+using aidl::FrequencyAccelerationMapEntry;
+
+class CommandGetFrequencyToOutputAccelerationMap : public Command {
+    std::string getDescription() const override {
+        return "Retrieves vibrator frequency to output acceleration map.";
+    }
+
+    std::string getUsageSummary() const override { return ""; }
+
+    UsageDetails getUsageDetails() const override {
+        UsageDetails details{};
+        return details;
+    }
+
+    Status doArgs(Args& args) override {
+        if (!args.empty()) {
+            std::cerr << "Unexpected Arguments!" << std::endl;
+            return USAGE;
+        }
+        return OK;
+    }
+
+    Status doMain(Args&& /*args*/) override {
+        std::string statusStr;
+        std::vector<FrequencyAccelerationMapEntry> frequencyToOutputAccelerationMap;
+        Status ret;
+
+        if (auto hal = getHal<aidl::IVibrator>()) {
+            auto status = hal->call(&aidl::IVibrator::getFrequencyToOutputAccelerationMap,
+                                    &frequencyToOutputAccelerationMap);
+            statusStr = status.getDescription();
+            ret = (status.isOk() ? OK : ERROR);
+        } else {
+            return UNAVAILABLE;
+        }
+
+        std::cout << "Status: " << statusStr << std::endl;
+        std::cout << "Frequency to Output Amplitude Map: " << std::endl;
+        for (auto& entry : frequencyToOutputAccelerationMap) {
+            std::cout << entry.frequencyHz << " " << entry.maxOutputAccelerationGs << std::endl;
+        }
+
+        return ret;
+    }
+};
+
+static const auto Command =
+        CommandRegistry<CommandVibrator>::Register<CommandGetFrequencyToOutputAccelerationMap>(
+                "getFrequencyToOutputAccelerationMap");
+
+} // namespace vibrator
+} // namespace idlcli
+} // namespace android
diff --git a/cmds/idlcli/vibrator/CommandGetPwleV2CompositionSizeMax.cpp b/cmds/idlcli/vibrator/CommandGetPwleV2CompositionSizeMax.cpp
new file mode 100644
index 0000000..cca072c
--- /dev/null
+++ b/cmds/idlcli/vibrator/CommandGetPwleV2CompositionSizeMax.cpp
@@ -0,0 +1,72 @@
+/*
+ * 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 "utils.h"
+#include "vibrator.h"
+
+namespace android {
+namespace idlcli {
+
+class CommandVibrator;
+
+namespace vibrator {
+
+class CommandGetPwleV2CompositionSizeMax : public Command {
+    std::string getDescription() const override {
+        return "Retrieves vibrator PwleV2 max composition size.";
+    }
+
+    std::string getUsageSummary() const override { return ""; }
+
+    UsageDetails getUsageDetails() const override {
+        UsageDetails details{};
+        return details;
+    }
+
+    Status doArgs(Args& args) override {
+        if (!args.empty()) {
+            std::cerr << "Unexpected Arguments!" << std::endl;
+            return USAGE;
+        }
+        return OK;
+    }
+
+    Status doMain(Args&& /*args*/) override {
+        std::string statusStr;
+        int32_t maxSize;
+        Status ret;
+
+        if (auto hal = getHal<aidl::IVibrator>()) {
+            auto status = hal->call(&aidl::IVibrator::getPwleV2CompositionSizeMax, &maxSize);
+            statusStr = status.getDescription();
+            ret = status.isOk() ? OK : ERROR;
+        } else {
+            return UNAVAILABLE;
+        }
+
+        std::cout << "Status: " << statusStr << std::endl;
+        std::cout << "Max Size: " << maxSize << std::endl;
+
+        return ret;
+    }
+};
+
+static const auto Command =
+        CommandRegistry<CommandVibrator>::Register<CommandGetPwleV2CompositionSizeMax>(
+                "getPwleV2CompositionSizeMax");
+
+} // namespace vibrator
+} // namespace idlcli
+} // namespace android
diff --git a/cmds/idlcli/vibrator/CommandGetPwleV2PrimitiveDurationMaxMillis.cpp b/cmds/idlcli/vibrator/CommandGetPwleV2PrimitiveDurationMaxMillis.cpp
new file mode 100644
index 0000000..dbbfe1a
--- /dev/null
+++ b/cmds/idlcli/vibrator/CommandGetPwleV2PrimitiveDurationMaxMillis.cpp
@@ -0,0 +1,73 @@
+/*
+ * 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 "utils.h"
+#include "vibrator.h"
+
+namespace android {
+namespace idlcli {
+
+class CommandVibrator;
+
+namespace vibrator {
+
+class CommandGetPwleV2PrimitiveDurationMaxMillis : public Command {
+    std::string getDescription() const override {
+        return "Retrieves vibrator PwleV2 max primitive duration in milliseconds.";
+    }
+
+    std::string getUsageSummary() const override { return ""; }
+
+    UsageDetails getUsageDetails() const override {
+        UsageDetails details{};
+        return details;
+    }
+
+    Status doArgs(Args& args) override {
+        if (!args.empty()) {
+            std::cerr << "Unexpected Arguments!" << std::endl;
+            return USAGE;
+        }
+        return OK;
+    }
+
+    Status doMain(Args&& /*args*/) override {
+        std::string statusStr;
+        int32_t maxDurationMs;
+        Status ret;
+
+        if (auto hal = getHal<aidl::IVibrator>()) {
+            auto status = hal->call(&aidl::IVibrator::getPwleV2PrimitiveDurationMaxMillis,
+                                    &maxDurationMs);
+            statusStr = status.getDescription();
+            ret = status.isOk() ? OK : ERROR;
+        } else {
+            return UNAVAILABLE;
+        }
+
+        std::cout << "Status: " << statusStr << std::endl;
+        std::cout << "Primitive duration max: " << maxDurationMs << " ms" << std::endl;
+
+        return ret;
+    }
+};
+
+static const auto Command =
+        CommandRegistry<CommandVibrator>::Register<CommandGetPwleV2PrimitiveDurationMaxMillis>(
+                "getPwleV2PrimitiveDurationMaxMillis");
+
+} // namespace vibrator
+} // namespace idlcli
+} // namespace android
diff --git a/cmds/idlcli/vibrator/CommandGetPwleV2PrimitiveDurationMinMillis.cpp b/cmds/idlcli/vibrator/CommandGetPwleV2PrimitiveDurationMinMillis.cpp
new file mode 100644
index 0000000..09225c4
--- /dev/null
+++ b/cmds/idlcli/vibrator/CommandGetPwleV2PrimitiveDurationMinMillis.cpp
@@ -0,0 +1,73 @@
+/*
+ * 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 "utils.h"
+#include "vibrator.h"
+
+namespace android {
+namespace idlcli {
+
+class CommandVibrator;
+
+namespace vibrator {
+
+class CommandGetPwleV2PrimitiveDurationMinMillis : public Command {
+    std::string getDescription() const override {
+        return "Retrieves vibrator PwleV2 minimum primitive duration in milliseconds.";
+    }
+
+    std::string getUsageSummary() const override { return ""; }
+
+    UsageDetails getUsageDetails() const override {
+        UsageDetails details{};
+        return details;
+    }
+
+    Status doArgs(Args& args) override {
+        if (!args.empty()) {
+            std::cerr << "Unexpected Arguments!" << std::endl;
+            return USAGE;
+        }
+        return OK;
+    }
+
+    Status doMain(Args&& /*args*/) override {
+        std::string statusStr;
+        int32_t minDurationMs;
+        Status ret;
+
+        if (auto hal = getHal<aidl::IVibrator>()) {
+            auto status = hal->call(&aidl::IVibrator::getPwleV2PrimitiveDurationMinMillis,
+                                    &minDurationMs);
+            statusStr = status.getDescription();
+            ret = status.isOk() ? OK : ERROR;
+        } else {
+            return UNAVAILABLE;
+        }
+
+        std::cout << "Status: " << statusStr << std::endl;
+        std::cout << "Primitive duration min: " << minDurationMs << " ms" << std::endl;
+
+        return ret;
+    }
+};
+
+static const auto Command =
+        CommandRegistry<CommandVibrator>::Register<CommandGetPwleV2PrimitiveDurationMinMillis>(
+                "getPwleV2PrimitiveDurationMinMillis");
+
+} // namespace vibrator
+} // namespace idlcli
+} // namespace android
diff --git a/cmds/idlcli/vibrator/CommandPerformVendorEffect.cpp b/cmds/idlcli/vibrator/CommandPerformVendorEffect.cpp
new file mode 100644
index 0000000..9c25bd2
--- /dev/null
+++ b/cmds/idlcli/vibrator/CommandPerformVendorEffect.cpp
@@ -0,0 +1,73 @@
+/*
+ * 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 <thread>
+
+#include "utils.h"
+#include "vibrator.h"
+
+using std::chrono::milliseconds;
+using std::this_thread::sleep_for;
+
+namespace android {
+namespace idlcli {
+
+class CommandVibrator;
+
+namespace vibrator {
+
+using aidl::VendorEffect;
+
+class CommandPerformVendorEffect : public Command {
+    std::string getDescription() const override { return "Perform vendor vibration effect."; }
+
+    std::string getUsageSummary() const override { return "[options] <none>"; }
+
+    UsageDetails getUsageDetails() const override {
+        UsageDetails details{
+                {"-b", {"Block for duration of vibration."}},
+                {"<none>", {"No valid input."}},
+        };
+        return details;
+    }
+
+    Status doArgs(Args& args) override {
+        while (args.get<std::string>().value_or("").find("-") == 0) {
+            auto opt = *args.pop<std::string>();
+            if (opt == "--") {
+                break;
+            } else if (opt == "-b") {
+                mBlocking = true;
+            } else {
+                std::cerr << "Invalid Option '" << opt << "'!" << std::endl;
+                return USAGE;
+            }
+        }
+
+        return OK;
+    }
+
+    Status doMain(Args&& /*args*/) override { return UNAVAILABLE; }
+
+    bool mBlocking;
+    VendorEffect mEffect;
+};
+
+static const auto Command = CommandRegistry<CommandVibrator>::Register<CommandPerformVendorEffect>(
+        "performVendorEffect");
+
+} // namespace vibrator
+} // namespace idlcli
+} // namespace android
diff --git a/include/android/OWNERS b/include/android/OWNERS
index 5b014d0..9dfa279 100644
--- a/include/android/OWNERS
+++ b/include/android/OWNERS
@@ -9,4 +9,5 @@
 
 # ADPF
 per-file performance_hint.h = file:platform/frameworks/base:/ADPF_OWNERS
+per-file system_health.h = file:platform/frameworks/base:/ADPF_OWNERS
 per-file thermal.h = file:platform/frameworks/base:/ADPF_OWNERS
diff --git a/include/android/display_luts.h b/include/android/display_luts.h
index 08dfb12..eae2bfd 100644
--- a/include/android/display_luts.h
+++ b/include/android/display_luts.h
@@ -43,6 +43,7 @@
 enum ADisplayLuts_SamplingKey : int32_t {
     ADISPLAYLUTS_SAMPLINGKEY_RGB = 0,
     ADISPLAYLUTS_SAMPLINGKEY_MAX_RGB = 1,
+    ADISPLAYLUTS_SAMPLINGKEY_CIE_Y = 2,
 };
 typedef enum ADisplayLuts_SamplingKey ADisplayLuts_SamplingKey;
 
@@ -74,7 +75,8 @@
  * @return a new \a ADisplayLutsEntry instance.
  */
 ADisplayLutsEntry* _Nonnull ADisplayLutsEntry_createEntry(float* _Nonnull buffer,
-        int32_t length, int32_t dimension, int32_t key) __INTRODUCED_IN(36);
+    int32_t length, ADisplayLuts_Dimension dimension, ADisplayLuts_SamplingKey key)
+    __INTRODUCED_IN(36);
 
 /**
  * Destroy the \a ADisplayLutsEntry instance.
diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h
index fba063d..ca86c27 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -76,6 +76,8 @@
 struct APerformanceHintManager;
 struct APerformanceHintSession;
 struct AWorkDuration;
+struct ANativeWindow;
+struct ASurfaceControl;
 
 /**
  * {@link AWorkDuration} is an opaque type that represents the breakdown of the
@@ -150,6 +152,9 @@
  */
 typedef struct APerformanceHintSession APerformanceHintSession;
 
+typedef struct ANativeWindow ANativeWindow;
+typedef struct ASurfaceControl ASurfaceControl;
+
 /**
   * Acquire an instance of the performance hint manager.
   *
@@ -354,6 +359,39 @@
         bool cpu, bool gpu, const char* _Nonnull debugName) __INTRODUCED_IN(36);
 
 /**
+ * Associates a session with any {@link ASurfaceControl} or {@link ANativeWindow}
+ * instances managed by this session.
+ *
+ * This method is primarily intended for sessions that manage the timing of an entire
+ * graphics pipeline end-to-end, such as those using the
+ * {@link ASessionCreationConfig_setGraphicsPipeline} API. However, any session directly
+ * or indirectly managing a graphics pipeline should still associate themselves with
+ * directly relevant ASurfaceControl or ANativeWindow instances for better optimization.
+ *
+ * To see any benefit from this method, the client must make sure they are updating the framerate
+ * of attached surfaces using methods such as {@link ANativeWindow_setFrameRate}, or by updating
+ * any associated ASurfaceControls with transactions that have {ASurfaceTransaction_setFrameRate}.
+ *
+ * @param session The {@link APerformanceHintSession} instance to update.
+ * @param nativeWindows A pointer to a list of ANativeWindows associated with this session.
+ *        nullptr can be passed to indicate there are no associated ANativeWindows.
+ * @param nativeWindowsSize The number of ANativeWindows in the list.
+ * @param surfaceControls A pointer to a list of ASurfaceControls associated with this session.
+ *        nullptr can be passed to indicate there are no associated ASurfaceControls.
+ * @param surfaceControlsSize The number of ASurfaceControls in the list.
+ *
+ * @return 0 on success.
+ *         EPIPE if communication has failed.
+ *         ENOTSUP if unsupported.
+ *         EINVAL if invalid or empty arguments passed.
+ */
+
+int APerformanceHint_setNativeSurfaces(APerformanceHintSession* _Nonnull session,
+        ANativeWindow* _Nonnull* _Nullable nativeWindows, int nativeWindowsSize,
+        ASurfaceControl* _Nonnull* _Nullable surfaceControls, int surfaceControlsSize)
+        __INTRODUCED_IN(36);
+
+/**
  * Creates a new AWorkDuration. When the client finishes using {@link AWorkDuration}, it should
  * call {@link AWorkDuration_release()} to destroy {@link AWorkDuration} and release all resources
  * associated with it.
@@ -520,6 +558,63 @@
 int ASessionCreationConfig_setGraphicsPipeline(
         ASessionCreationConfig* _Nonnull config, bool enabled)  __INTRODUCED_IN(36);
 
+/**
+ * Associates a session with any {@link ASurfaceControl} or {@link ANativeWindow}
+ * instances managed by this session. See {@link APerformanceHint_setNativeSurfaces}
+ * for more details.
+ *
+ * @param config The {@link ASessionCreationConfig}
+ *        created by calling {@link ASessionCreationConfig_create()}.
+ * @param nativeWindows A pointer to a list of ANativeWindows associated with this session.
+ *        nullptr can be passed to indicate there are no associated ANativeWindows.
+ * @param nativeWindowsSize The number of ANativeWindows in the list.
+ * @param surfaceControls A pointer to a list of ASurfaceControls associated with this session.
+ *        nullptr can be passed to indicate there are no associated ASurfaceControls.
+ * @param surfaceControlsSize The number of ASurfaceControls in the list.
+ *
+ * @return 0 on success.
+ *         ENOTSUP if unsupported.
+ *         EINVAL if invalid or empty arguments passed.
+ */
+int ASessionCreationConfig_setNativeSurfaces(
+        ASessionCreationConfig* _Nonnull config,
+        ANativeWindow* _Nonnull* _Nullable nativeWindows, int nativeWindowsSize,
+        ASurfaceControl* _Nonnull* _Nullable surfaceControls, int surfaceControlsSize)
+        __INTRODUCED_IN(36);
+
+/**
+ * Enable automatic timing mode for sessions using the GRAPHICS_PIPELINE API with an attached
+ * surface. In this mode, sessions do not need to report actual durations and only need
+ * to keep their thread list up-to-date, set a native surface, call
+ * {@link ASessionCreationConfig_setGraphicsPipeline()} to signal that the session is in
+ * "graphics pipeline" mode, and then set whether automatic timing is desired for the
+ * CPU, GPU, or both, using this method.
+ *
+ * It is still be beneficial to set an accurate target time, as this may help determine
+ * timing information for some workloads where there is less information available from
+ * the framework, such as games. Additionally, reported CPU durations will be ignored
+ * while automatic CPU timing is enabled, and similarly GPU durations will be ignored
+ * when automatic GPU timing is enabled. When both are enabled, the entire
+ * reportActualWorkDuration call will be ignored, and the session will be managed
+ * completely automatically.
+ *
+ * This mode will not work unless the client makes sure they are updating the framerate
+ * of attached surfaces with methods such as {@link ANativeWindow_setFrameRate}, or updating
+ * any associated ASurfaceControls with transactions that have {ASurfaceTransaction_setFrameRate}.
+ *
+ * @param config The {@link ASessionCreationConfig}
+ *        created by calling {@link ASessionCreationConfig_create()}.
+ * @param cpu Whether to enable automatic timing for the CPU for this session.
+ * @param gpu Whether to enable automatic timing for the GPU for this session.
+ *
+ * @return 0 on success.
+ *         ENOTSUP if unsupported.
+ */
+int ASessionCreationConfig_setUseAutoTiming(
+        ASessionCreationConfig* _Nonnull config,
+        bool cpu, bool gpu)
+        __INTRODUCED_IN(36);
+
 __END_DECLS
 
 #endif // ANDROID_NATIVE_PERFORMANCE_HINT_H
diff --git a/include/android/surface_control.h b/include/android/surface_control.h
index 9554015..6323333 100644
--- a/include/android/surface_control.h
+++ b/include/android/surface_control.h
@@ -781,69 +781,6 @@
                                                         __INTRODUCED_IN(31);
 
 /**
- * Sets the intended frame rate for the given \a surface_control.
- *
- * On devices that are capable of running the display at different frame rates,
- * the system may choose a display refresh rate to better match this surface's frame
- * rate. Usage of this API won't introduce frame rate throttling, or affect other
- * aspects of the application's frame production pipeline. However, because the system
- * may change the display refresh rate, calls to this function may result in changes
- * to Choreographer callback timings, and changes to the time interval at which the
- * system releases buffers back to the application.
- *
- * You can register for changes in the refresh rate using
- * \a AChoreographer_registerRefreshRateCallback.
- *
- * See ASurfaceTransaction_clearFrameRate().
- *
- * Available since API level 36.
- *
- * \param desiredMinRate The desired minimum frame rate (inclusive) for the surface, specifying that
- * the surface prefers the device render rate to be at least `desiredMinRate`.
- *
- * <p>Set `desiredMinRate` = `desiredMaxRate` to indicate the surface prefers an exact frame rate.
- *
- * <p>Set `desiredMinRate` = 0 to indicate the surface has no preference
- * and any frame rate is acceptable.
- *
- * <p>The value should be greater than or equal to 0.
- *
- * \param desiredMaxRate The desired maximum frame rate (inclusive) for the surface, specifying that
- * the surface prefers the device render rate to be at most `desiredMaxRate`.
- *
- * <p>Set `desiredMaxRate` = `desiredMinRate` to indicate the surface prefers an exact frame rate.
- *
- * <p>Set `desiredMaxRate` = positive infinity to indicate the surface has no preference
- * and any frame rate is acceptable.
- *
- * <p>The value should be greater than or equal to `desiredMinRate`.
- *
- * \param fixedSourceRate The "fixed source" frame rate of the surface if the content has an
- * inherently fixed frame rate, e.g. a video that has a specific frame rate.
- *
- * <p>When the frame rate chosen for the surface is the `fixedSourceRate` or a
- * multiple, the surface can render without frame pulldown, for optimal smoothness. For
- * example, a 30 fps video (`fixedSourceRate`=30) renders just as smoothly on 30 fps,
- * 60 fps, 90 fps, 120 fps, and so on.
- *
- * <p>Setting the fixed source rate can also be used together with a desired
- * frame rate min and max via setting `desiredMinRate` and `desiredMaxRate`. This still
- * means the surface's content has a fixed frame rate of `fixedSourceRate`, but additionally
- * specifies the preference to be in the range [`desiredMinRate`, `desiredMaxRate`]. For example, an
- * app might want to specify there is 30 fps video (`fixedSourceRate`=30) as well as a smooth
- * animation on the same surface which looks good when drawing within a frame rate range such as
- * [`desiredMinRate`, `desiredMaxRate`] = [60,120].
- *
- * \param changeFrameRateStrategy Whether display refresh rate transitions caused by this surface
- * should be seamless. A seamless transition is one that doesn't have any visual interruptions, such
- * as a black screen for a second or two.
- */
-void ASurfaceTransaction_setFrameRateParams(
-        ASurfaceTransaction* _Nonnull transaction, ASurfaceControl* _Nonnull surface_control,
-        float desiredMinRate, float desiredMaxRate, float fixedSourceRate,
-        ANativeWindow_ChangeFrameRateStrategy changeFrameRateStrategy) __INTRODUCED_IN(36);
-
-/**
  * Clears the frame rate which is set for \a surface_control.
  *
  * This is equivalent to calling
diff --git a/include/android/system_health.h b/include/android/system_health.h
new file mode 100644
index 0000000..69620df
--- /dev/null
+++ b/include/android/system_health.h
@@ -0,0 +1,351 @@
+/*
+ * 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.
+ */
+
+/**
+* @defgroup SystemHealth
+* @{
+*/
+
+/**
+ * @file system_health.h
+ */
+
+#ifndef _ANDROID_SYSTEM_HEALTH_H
+#define _ANDROID_SYSTEM_HEALTH_H
+
+#include <sys/cdefs.h>
+
+/******************************************************************
+ *
+ * IMPORTANT NOTICE:
+ *
+ *   This file is part of Android's set of stable system headers
+ *   exposed by the Android NDK (Native Development Kit).
+ *
+ *   Third-party source AND binary code relies on the definitions
+ *   here to be FROZEN ON ALL UPCOMING PLATFORM RELEASES.
+ *
+ *   - DO NOT MODIFY ENUMS (EXCEPT IF YOU ADD NEW 32-BIT VALUES)
+ *   - DO NOT MODIFY CONSTANTS OR FUNCTIONAL MACROS
+ *   - DO NOT CHANGE THE SIGNATURE OF FUNCTIONS IN ANY WAY
+ *   - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES
+ */
+
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#if !defined(__INTRODUCED_IN)
+#define __INTRODUCED_IN(__api_level) /* nothing */
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Params used to customize the calculation of CPU headroom.
+ *
+ * Also see {@link ASystemHealth_getCpuHeadroom}.
+ */
+typedef struct ACpuHeadroomParams ACpuHeadroomParams;
+
+/**
+ * Params used to customize the calculation of GPU headroom.
+ *
+ * Also see {@link ASystemHealth_getGpuHeadroom}.
+ */
+typedef struct AGpuHeadroomParams AGpuHeadroomParams;
+
+/**
+ * Creates a new instance of ACpuHeadroomParams.
+ *
+ * When the client finishes using {@link ACpuHeadroomParams},
+ * {@link ACpuHeadroomParams_destroy()} must be called to destroy
+ * and free up the resources associated with {@link ACpuHeadroomParams}.
+ *
+ * Available since API level 36.
+ *
+ * @return A new instance of ACpuHeadroomParams.
+ */
+ACpuHeadroomParams *_Nonnull ACpuHeadroomParams_create()
+__INTRODUCED_IN(36);
+
+enum ACpuHeadroomCalculationType {
+    /**
+     * Use the minimum headroom value within the calculation window.
+     * Introduced in API level 36.
+     */
+    ACPU_HEADROOM_CALCULATION_TYPE_MIN = 0,
+    /**
+     * Use the average headroom value within the calculation window.
+     * Introduced in API level 36.
+     */
+    ACPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1,
+};
+typedef enum ACpuHeadroomCalculationType ACpuHeadroomCalculationType;
+
+enum AGpuHeadroomCalculationType {
+    /**
+     * Use the minimum headroom value within the calculation window.
+     * Introduced in API level 36.
+     */
+    AGPU_HEADROOM_CALCULATION_TYPE_MIN = 0,
+    /**
+     * Use the average headroom value within the calculation window.
+     * Introduced in API level 36.
+     */
+    AGPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1,
+};
+typedef enum AGpuHeadroomCalculationType AGpuHeadroomCalculationType;
+
+/**
+ * Sets the headroom calculation window size in ACpuHeadroomParams.
+ *
+ * Available since API level 36.
+ *
+ * @param params The params to be set.
+ * @param windowMillis The window size in milliseconds ranged from [50, 10000]. The smaller the
+ *                     window size, the larger fluctuation in the headroom value should be expected.
+ *                     The default value can be retrieved from the
+ *                     {@link #ACpuHeadroomParams_getCalculationWindowMillis} if not set. The device
+ *                     will try to use the closest feasible window size to this param.
+ */
+void ACpuHeadroomParams_setCalculationWindowMillis(ACpuHeadroomParams *_Nonnull params,
+                                                   int windowMillis)
+__INTRODUCED_IN(36);
+
+/**
+ * Gets the headroom calculation window size in ACpuHeadroomParams.
+ *
+ * Available since API level 36.
+ *
+ * @param params The params to be set.
+ * @return This will return the default value chosen by the device if the params is not set.
+ */
+int ACpuHeadroomParams_getCalculationWindowMillis(ACpuHeadroomParams *_Nonnull params)
+__INTRODUCED_IN(36);
+
+/**
+ * Sets the headroom calculation window size in AGpuHeadroomParams.
+ *
+ * Available since API level 36.
+ *
+ * @param params The params to be set.
+ * @param windowMillis The window size in milliseconds ranged from [50, 10000]. The smaller the
+ *                     window size, the larger fluctuation in the headroom value should be expected.
+ *                     The default value can be retrieved from the
+ *                     {@link #AGpuHeadroomParams_getCalculationWindowMillis} if not set. The device
+ *                     will try to use the closest feasible window size to this param.
+ */
+void AGpuHeadroomParams_setCalculationWindowMillis(AGpuHeadroomParams *_Nonnull params,
+                                                   int windowMillis)
+__INTRODUCED_IN(36);
+
+/**
+ * Gets the headroom calculation window size in AGpuHeadroomParams.
+ *
+ * Available since API level 36.
+ *
+ * @param params The params to be set.
+ * @return This will return the default value chosen by the device if the params is not set.
+ */
+int AGpuHeadroomParams_getCalculationWindowMillis(AGpuHeadroomParams *_Nonnull params)
+__INTRODUCED_IN(36);
+
+/**
+ * Sets the headroom calculation type in ACpuHeadroomParams.
+ *
+ * Available since API level 36.
+ *
+ * @param params The params to be set.
+ * @param calculationType The headroom calculation type.
+ */
+void ACpuHeadroomParams_setCalculationType(ACpuHeadroomParams *_Nonnull params,
+                                           ACpuHeadroomCalculationType calculationType)
+__INTRODUCED_IN(36);
+
+/**
+ * Gets the headroom calculation type in ACpuHeadroomParams.
+ *
+ * Available since API level 36.
+ *
+ * @param params The params to be set.
+ * @return The headroom calculation type.
+ */
+ACpuHeadroomCalculationType
+ACpuHeadroomParams_getCalculationType(ACpuHeadroomParams *_Nonnull params)
+__INTRODUCED_IN(36);
+
+/**
+ * Sets the headroom calculation type in AGpuHeadroomParams.
+ *
+ * Available since API level 36.
+ *
+ * @param params The params to be set.
+ * @param calculationType The headroom calculation type.
+ */
+void AGpuHeadroomParams_setCalculationType(AGpuHeadroomParams *_Nonnull params,
+                                           AGpuHeadroomCalculationType calculationType)
+__INTRODUCED_IN(36);
+
+/**
+ * Gets the headroom calculation type in AGpuHeadroomParams.
+ *
+ * Available since API level 36.
+ *
+ * @param params The params to be set.
+ * @return The headroom calculation type.
+ */
+AGpuHeadroomCalculationType
+AGpuHeadroomParams_getCalculationType(AGpuHeadroomParams *_Nonnull params)
+__INTRODUCED_IN(36);
+
+/**
+ * Sets the thread TIDs to track in ACpuHeadroomParams.
+ *
+ * Available since API level 36.
+ *
+ * @param params The params to be set.
+ * @param tids Non-null array of TIDs, maximum 5.
+ * @param tidsSize The size of the tids array.
+ */
+void ACpuHeadroomParams_setTids(ACpuHeadroomParams *_Nonnull params, const int *_Nonnull tids,
+                                int tidsSize)
+__INTRODUCED_IN(36);
+
+/**
+ * Creates a new instance of AGpuHeadroomParams.
+ *
+ * When the client finishes using {@link AGpuHeadroomParams},
+ * {@link AGpuHeadroomParams_destroy()} must be called to destroy
+ * and free up the resources associated with {@link AGpuHeadroomParams}.
+ *
+ * Available since API level 36.
+ *
+ * @return A new instance of AGpuHeadroomParams.
+ */
+AGpuHeadroomParams *_Nonnull AGpuHeadroomParams_create()
+__INTRODUCED_IN(36);
+
+/**
+ * Deletes the ACpuHeadroomParams instance.
+ *
+ * Available since API level 36.
+ *
+ * @param params The params to be deleted.
+ */
+void ACpuHeadroomParams_destroy(ACpuHeadroomParams *_Nonnull params)
+__INTRODUCED_IN(36);
+
+/**
+ * Deletes the AGpuHeadroomParams instance.
+ *
+ * Available since API level 36.
+ *
+ * @param params The params to be deleted.
+ */
+void AGpuHeadroomParams_destroy(AGpuHeadroomParams *_Nonnull params)
+__INTRODUCED_IN(36);
+
+/**
+ * Provides an estimate of available CPU capacity headroom of the device.
+ *
+ * The value can be used by the calling application to determine if the workload was CPU bound and
+ * then take action accordingly to ensure that the workload can be completed smoothly. It can also
+ * be used with the thermal status and headroom to determine if reducing the CPU bound workload can
+ * help reduce the device temperature to avoid thermal throttling.
+ *
+ * Available since API level 36.
+ *
+ * @param params The params to customize the CPU headroom calculation, or nullptr to use the default
+ * @param outHeadroom Non-null output pointer to a single float, which will be set to the CPU
+ *                    headroom value. The value will be a single value or `Float.NaN` if it's
+ *                    temporarily unavailable.
+ *                    Each valid value ranges from [0, 100], where 0 indicates no more cpu resources
+ *                    can be granted.
+ * @return 0 on success
+ *         EPIPE if failed to get the CPU headroom.
+ *         EPERM if the TIDs do not belong to the same process.
+ *         ENOTSUP if API or requested params is unsupported.
+ */
+int ASystemHealth_getCpuHeadroom(const ACpuHeadroomParams *_Nullable params,
+                                 float *_Nonnull outHeadroom)
+__INTRODUCED_IN(36);
+
+/**
+ * Provides an estimate of available GPU capacity headroom of the device.
+ *
+ * The value can be used by the calling application to determine if the workload was GPU bound and
+ * then take action accordingly to ensure that the workload can be completed smoothly. It can also
+ * be used with the thermal status and headroom to determine if reducing the GPU bound workload can
+ * help reduce the device temperature to avoid thermal throttling.
+ *
+ * Available since API level 36
+ *
+ * @param params The params to customize the GPU headroom calculation, or nullptr to use the default
+ * @param outHeadroom Non-null output pointer to a single float, which will be set to the GPU
+ *                    headroom value. The value will be a single value or `Float.NaN` if it's
+ *                    temporarily unavailable.
+ *                    Each valid value ranges from [0, 100], where 0 indicates no more gpu resources
+ *                    can be granted.
+ * @return 0 on success
+ *         EPIPE if failed to get the GPU headroom.
+ *         ENOTSUP if API or requested params is unsupported.
+ */
+int ASystemHealth_getGpuHeadroom(const AGpuHeadroomParams *_Nullable params,
+                                 float *_Nonnull outHeadroom)
+__INTRODUCED_IN(36);
+
+/**
+ * Gets minimum polling interval for calling {@link ASystemHealth_getCpuHeadroom} in milliseconds.
+ *
+ * The getCpuHeadroom API may return cached result if called more frequently than the interval.
+ *
+ * Available since API level 36.
+ *
+ * @param outMinIntervalMillis Non-null output pointer to a int64_t, which
+ *                will be set to the minimum polling interval in milliseconds.
+ * @return 0 on success
+ *         EPIPE if failed to get the minimum polling interval.
+ *         ENOTSUP if API is unsupported.
+ */
+int ASystemHealth_getCpuHeadroomMinIntervalMillis(int64_t *_Nonnull outMinIntervalMillis)
+__INTRODUCED_IN(36);
+
+/**
+ * Gets minimum polling interval for calling {@link ASystemHealth_getGpuHeadroom} in milliseconds.
+ *
+ * The getGpuHeadroom API may return cached result if called more frequent than the interval.
+ *
+ * Available since API level 36.
+ *
+ * @param outMinIntervalMillis Non-null output pointer to a int64_t, which
+ *                will be set to the minimum polling interval in milliseconds.
+ * @return 0 on success
+ *         EPIPE if failed to get the minimum polling interval.
+ *         ENOTSUP if API is unsupported.
+ */
+int ASystemHealth_getGpuHeadroomMinIntervalMillis(int64_t *_Nonnull outMinIntervalMillis)
+__INTRODUCED_IN(36);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _ANDROID_SYSTEM_HEALTH_H
+
+/** @} */
diff --git a/include/android/thermal.h b/include/android/thermal.h
index 7f9d2ed..e5d46b5 100644
--- a/include/android/thermal.h
+++ b/include/android/thermal.h
@@ -140,45 +140,47 @@
   * Available since API level 30.
   *
   * @param manager The manager instance to use to query the thermal status.
-  * Acquired via {@link AThermal_acquireManager}.
+  *                Acquired via {@link AThermal_acquireManager}.
   *
   * @return current thermal status, ATHERMAL_STATUS_ERROR on failure.
   */
 AThermalStatus
-AThermal_getCurrentThermalStatus(AThermalManager* _Nonnull manager) __INTRODUCED_IN(30);
+AThermal_getCurrentThermalStatus(AThermalManager *_Nonnull manager) __INTRODUCED_IN(30);
 
 /**
- * Register the thermal status listener for thermal status change.
+ * Register a thermal status listener for thermal status change.
  *
  * Available since API level 30.
  *
  * @param manager The manager instance to use to register.
- * Acquired via {@link AThermal_acquireManager}.
- * @param callback The callback function to be called when thermal status updated.
+ *                Acquired via {@link AThermal_acquireManager}.
+ * @param callback The callback function to be called on system binder thread pool when thermal
+ *                 status updated.
  * @param data The data pointer to be passed when callback is called.
  *
  * @return 0 on success
  *         EINVAL if the listener and data pointer were previously added and not removed.
- *         EPERM if the required permission is not held.
- *         EPIPE if communication with the system service has failed.
+ *         EPIPE if communication with the system service has failed, the listener will not get
+ *               removed and this call should be retried
  */
-int AThermal_registerThermalStatusListener(AThermalManager* _Nonnull manager,
+int AThermal_registerThermalStatusListener(AThermalManager *_Nonnull manager,
                                            AThermal_StatusCallback _Nullable callback,
                                            void* _Nullable data) __INTRODUCED_IN(30);
 
 /**
- * Unregister the thermal status listener previously resgistered.
+ * Unregister a thermal status listener previously registered.
+ *
+ * No subsequent invocations of the callback will occur after this function returns successfully.
  *
  * Available since API level 30.
  *
  * @param manager The manager instance to use to unregister.
- * Acquired via {@link AThermal_acquireManager}.
- * @param callback The callback function to be called when thermal status updated.
+ *                Acquired via {@link AThermal_acquireManager}.
+ * @param callback The callback function that was previously registered.
  * @param data The data pointer to be passed when callback is called.
  *
  * @return 0 on success
  *         EINVAL if the listener and data pointer were not previously added.
- *         EPERM if the required permission is not held.
  *         EPIPE if communication with the system service has failed.
  */
 int AThermal_unregisterThermalStatusListener(AThermalManager* _Nonnull manager,
@@ -254,7 +256,7 @@
  * The headroom threshold is used to interpret the possible thermal throttling status based on
  * the headroom prediction. For example, if the headroom threshold for
  * {@link ATHERMAL_STATUS_LIGHT} is 0.7, and a headroom prediction in 10s returns 0.75
- * (or {@code AThermal_getThermalHeadroom(10)=0.75}), one can expect that in 10 seconds the system
+ * (or `AThermal_getThermalHeadroom(10)=0.75}`, one can expect that in 10 seconds the system
  * could be in lightly throttled state if the workload remains the same. The app can consider
  * taking actions according to the nearest throttling status the difference between the headroom and
  * the threshold.
@@ -262,24 +264,30 @@
  * For new devices it's guaranteed to have a single sensor, but for older devices with multiple
  * sensors reporting different threshold values, the minimum threshold is taken to be conservative
  * on predictions. Thus, when reading real-time headroom, it's not guaranteed that a real-time value
- * of 0.75 (or {@code AThermal_getThermalHeadroom(0)}=0.75) exceeding the threshold of 0.7 above
+ * of 0.75 (or `AThermal_getThermalHeadroom(0)=0.75`) exceeding the threshold of 0.7 above
  * will always come with lightly throttled state
- * (or {@code AThermal_getCurrentThermalStatus()=ATHERMAL_STATUS_LIGHT}) but it can be lower
- * (or {@code AThermal_getCurrentThermalStatus()=ATHERMAL_STATUS_NONE}).
+ * (or `AThermal_getCurrentThermalStatus()=ATHERMAL_STATUS_LIGHT`) but it can be lower
+ * (or `AThermal_getCurrentThermalStatus()=ATHERMAL_STATUS_NONE`).
  * While it's always guaranteed that the device won't be throttled heavier than the unmet
  * threshold's state, so a real-time headroom of 0.75 will never come with
  * {@link #ATHERMAL_STATUS_MODERATE} but always lower, and 0.65 will never come with
  * {@link ATHERMAL_STATUS_LIGHT} but {@link #ATHERMAL_STATUS_NONE}.
  * <p>
- * The returned list of thresholds is cached on first successful query and owned by the thermal
- * manager, which will not change between calls to this function. The caller should only need to
- * free the manager with {@link AThermal_releaseManager}.
+ * Starting in Android 16, this polling API may return different results when called depending on
+ * the device. The new headroom listener API {@link #AThermal_HeadroomCallback} can be used to
+ * detect headroom thresholds changes.
+ * <p>
+ * Before API level 36 the returned list of thresholds is cached on first successful query and owned
+ * by the thermal manager, which will not change between calls to this function. The caller should
+ * only need to free the manager with {@link AThermal_releaseManager}.
+ * <p>
  *
  * @param manager The manager instance to use.
  *                Acquired via {@link AThermal_acquireManager}.
  * @param outThresholds non-null output pointer to null AThermalHeadroomThreshold pointer, which
- *                will be set to the cached array of thresholds if thermal thresholds are supported
- *                by the system or device, otherwise nullptr or unmodified.
+ *                will be set to a new array of thresholds if thermal thresholds are supported
+ *                by the system or device, otherwise nullptr or unmodified. The client should
+ *                clean up the thresholds by array-deleting the threshold pointer.
  * @param size non-null output pointer whose value will be set to the size of the threshold array
  *             or 0 if it's not supported.
  * @return 0 on success
@@ -292,6 +300,71 @@
                                           * _Nullable outThresholds,
                                           size_t* _Nonnull size) __INTRODUCED_IN(35);
 
+/**
+ * Prototype of the function that is called when thermal headroom or thresholds changes.
+ * It's passed the updated thermal headroom and thresholds as parameters, as well as the
+ * pointer provided by the client that registered a callback.
+ *
+ * @param data The data pointer to be passed when callback is called.
+ * @param headroom The current non-negative normalized headroom value, also see
+ *                 {@link AThermal_getThermalHeadroom}.
+ * @param forecastHeadroom The forecasted non-negative normalized headroom value, also see
+ *                         {@link AThermal_getThermalHeadroom}.
+ * @param forecastSeconds The seconds used for the forecast by the system.
+ * @param thresholds The current headroom thresholds. The thresholds pointer will be a constant
+ *                   shared across all callbacks registered from the same process, and it will be
+ *                   destroyed after all the callbacks are finished. If the client intents to
+ *                   persist the values, it should make a copy of it during the callback.
+ * @param thresholdsCount The count of thresholds.
+ */
+typedef void (*AThermal_HeadroomCallback)(void *_Nullable data,
+                                          float headroom,
+                                          float forecastHeadroom,
+                                          int forecastSeconds,
+                                          const AThermalHeadroomThreshold* _Nullable thresholds,
+                                          size_t thresholdsCount);
+
+/**
+ * Register a thermal headroom listener for thermal headroom or thresholds change.
+ *
+ * Available since API level 36.
+ *
+ * @param manager The manager instance to use to register.
+ *                Acquired via {@link AThermal_acquireManager}.
+ * @param callback The callback function to be called on system binder thread pool when thermal
+ *                 headroom or thresholds update.
+ * @param data The data pointer to be passed when callback is called.
+ *
+ * @return 0 on success
+ *         EINVAL if the listener and data pointer were previously added and not removed.
+ *         EPIPE if communication with the system service has failed.
+ */
+int AThermal_registerThermalHeadroomListener(AThermalManager* _Nonnull manager,
+                                             AThermal_HeadroomCallback _Nullable callback,
+                                             void* _Nullable data) __INTRODUCED_IN(36);
+
+/**
+ * Unregister a thermal headroom listener previously registered.
+ *
+ * No subsequent invocations of the callback will occur after this function returns successfully.
+ *
+ * Available since API level 36.
+ *
+ * @param manager The manager instance to use to unregister.
+ *                Acquired via {@link AThermal_acquireManager}.
+ * @param callback The callback function that was previously registered.
+ * @param data The data pointer that was previously registered.
+ *
+ * @return 0 on success
+ *         EINVAL if the listener and data pointer were not previously added.
+ *         EPIPE if communication with the system service has failed, the listener will not get
+ *               removed and this call should be retried
+ */
+
+int AThermal_unregisterThermalHeadroomListener(AThermalManager* _Nonnull manager,
+                                               AThermal_HeadroomCallback _Nullable callback,
+                                               void* _Nullable data) __INTRODUCED_IN(36);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/include/private/display_luts_private.h b/include/private/display_luts_private.h
index 240e1f9..c347a0c 100644
--- a/include/private/display_luts_private.h
+++ b/include/private/display_luts_private.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <android/display_luts.h>
 #include <stdint.h>
 #include <vector>
 #include <utils/RefBase.h>
@@ -29,9 +30,9 @@
 };
 
 struct ADisplayLutsEntry_properties {
-    int32_t dimension;
+    ADisplayLuts_Dimension dimension;
     int32_t size;
-    int32_t samplingKey;
+    ADisplayLuts_SamplingKey samplingKey;
 };
 
 struct ADisplayLutsEntry: public RefBase {
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index 0a61178..e10e4dd 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -477,9 +477,34 @@
     },
 }
 
+soong_config_module_type {
+    name: "libbinder_addservice_cache_config",
+    module_type: "cc_defaults",
+    config_namespace: "libbinder",
+    bool_variables: ["release_libbinder_addservice_cache"],
+    properties: [
+        "cflags",
+    ],
+}
+
+libbinder_addservice_cache_config {
+    name: "libbinder_addservice_cache_flag",
+    soong_config_variables: {
+        release_libbinder_addservice_cache: {
+            cflags: ["-DLIBBINDER_ADDSERVICE_CACHE"],
+            conditions_default: {
+                cflags: ["-DNO_LIBBINDER_ADDSERVICE_CACHE"],
+            },
+        },
+    },
+}
+
 cc_defaults {
     name: "libbinder_kernel_defaults",
-    defaults: ["libbinder_client_cache_flag"],
+    defaults: [
+        "libbinder_client_cache_flag",
+        "libbinder_addservice_cache_flag",
+    ],
     srcs: [
         "BufferedTextOutput.cpp",
         "BackendUnifiedServiceManager.cpp",
diff --git a/libs/binder/BackendUnifiedServiceManager.cpp b/libs/binder/BackendUnifiedServiceManager.cpp
index d32eecd..123dfd2 100644
--- a/libs/binder/BackendUnifiedServiceManager.cpp
+++ b/libs/binder/BackendUnifiedServiceManager.cpp
@@ -30,6 +30,12 @@
 constexpr bool kUseCache = false;
 #endif
 
+#ifdef LIBBINDER_ADDSERVICE_CACHE
+constexpr bool kUseCacheInAddService = true;
+#else
+constexpr bool kUseCacheInAddService = false;
+#endif
+
 using AidlServiceManager = android::os::IServiceManager;
 using android::os::IAccessor;
 using binder::Status;
@@ -125,31 +131,36 @@
     if (!kUseCache) {
         return Status::ok();
     }
+
+    if (service.getTag() == os::Service::Tag::binder) {
+        return updateCache(serviceName, service.get<os::Service::Tag::binder>());
+    }
+    return Status::ok();
+}
+
+Status BackendUnifiedServiceManager::updateCache(const std::string& serviceName,
+                                                 const sp<IBinder>& binder) {
     std::string traceStr;
     if (atrace_is_tag_enabled(ATRACE_TAG_AIDL)) {
         traceStr = "BinderCacheWithInvalidation::updateCache : " + serviceName;
     }
     binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL, traceStr.c_str());
-
-    if (service.getTag() == os::Service::Tag::binder) {
-        sp<IBinder> binder = service.get<os::Service::Tag::binder>();
-        if (!binder) {
-            binder::ScopedTrace
-                    aidlTrace(ATRACE_TAG_AIDL,
-                              "BinderCacheWithInvalidation::updateCache failed: binder_null");
-        } else if (!binder->isBinderAlive()) {
-            binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL,
-                                          "BinderCacheWithInvalidation::updateCache failed: "
-                                          "isBinderAlive_false");
-        } else if (mCacheForGetService->isClientSideCachingEnabled(serviceName)) {
-            binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL,
-                                          "BinderCacheWithInvalidation::updateCache successful");
-            return mCacheForGetService->setItem(serviceName, binder);
-        } else {
-            binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL,
-                                          "BinderCacheWithInvalidation::updateCache failed: "
-                                          "caching_not_enabled");
-        }
+    if (!binder) {
+        binder::ScopedTrace
+                aidlTrace(ATRACE_TAG_AIDL,
+                          "BinderCacheWithInvalidation::updateCache failed: binder_null");
+    } else if (!binder->isBinderAlive()) {
+        binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL,
+                                      "BinderCacheWithInvalidation::updateCache failed: "
+                                      "isBinderAlive_false");
+    } else if (mCacheForGetService->isClientSideCachingEnabled(serviceName)) {
+        binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL,
+                                      "BinderCacheWithInvalidation::updateCache successful");
+        return mCacheForGetService->setItem(serviceName, binder);
+    } else {
+        binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL,
+                                      "BinderCacheWithInvalidation::updateCache failed: "
+                                      "caching_not_enabled");
     }
     return Status::ok();
 }
@@ -277,7 +288,12 @@
 Status BackendUnifiedServiceManager::addService(const ::std::string& name,
                                                 const sp<IBinder>& service, bool allowIsolated,
                                                 int32_t dumpPriority) {
-    return mTheRealServiceManager->addService(name, service, allowIsolated, dumpPriority);
+    Status status = mTheRealServiceManager->addService(name, service, allowIsolated, dumpPriority);
+    // mEnableAddServiceCache is true by default.
+    if (kUseCacheInAddService && mEnableAddServiceCache && status.isOk()) {
+        return updateCache(name, service);
+    }
+    return status;
 }
 Status BackendUnifiedServiceManager::listServices(int32_t dumpPriority,
                                                   ::std::vector<::std::string>* _aidl_return) {
diff --git a/libs/binder/BackendUnifiedServiceManager.h b/libs/binder/BackendUnifiedServiceManager.h
index abc0eda..b46bf19 100644
--- a/libs/binder/BackendUnifiedServiceManager.h
+++ b/libs/binder/BackendUnifiedServiceManager.h
@@ -105,8 +105,7 @@
         binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL,
                                       "BinderCacheWithInvalidation::setItem Successfully Cached");
         std::lock_guard<std::mutex> lock(mCacheMutex);
-        Entry entry = {.service = item, .deathRecipient = deathRecipient};
-        mCache[key] = entry;
+        mCache[key] = {.service = item, .deathRecipient = deathRecipient};
         return binder::Status::ok();
     }
 
@@ -147,17 +146,20 @@
                                         const sp<IBinder>& service) override;
     binder::Status getServiceDebugInfo(::std::vector<os::ServiceDebugInfo>* _aidl_return) override;
 
+    void enableAddServiceCache(bool value) { mEnableAddServiceCache = value; }
     // for legacy ABI
     const String16& getInterfaceDescriptor() const override {
         return mTheRealServiceManager->getInterfaceDescriptor();
     }
 
 private:
+    bool mEnableAddServiceCache = true;
     std::shared_ptr<BinderCacheWithInvalidation> mCacheForGetService;
     sp<os::IServiceManager> mTheRealServiceManager;
     binder::Status toBinderService(const ::std::string& name, const os::Service& in,
                                    os::Service* _out);
     binder::Status updateCache(const std::string& serviceName, const os::Service& service);
+    binder::Status updateCache(const std::string& serviceName, const sp<IBinder>& binder);
     bool returnIfCached(const std::string& serviceName, os::Service* _out);
 };
 
diff --git a/libs/binder/IServiceManager.cpp b/libs/binder/IServiceManager.cpp
index 39d8c24..61866d7 100644
--- a/libs/binder/IServiceManager.cpp
+++ b/libs/binder/IServiceManager.cpp
@@ -127,6 +127,8 @@
     }
     IBinder* onAsBinder() override { return IInterface::asBinder(mUnifiedServiceManager).get(); }
 
+    void enableAddServiceCache(bool value) { mUnifiedServiceManager->enableAddServiceCache(value); }
+
 protected:
     sp<BackendUnifiedServiceManager> mUnifiedServiceManager;
     // AidlRegistrationCallback -> services that its been registered for
diff --git a/libs/binder/TEST_MAPPING b/libs/binder/TEST_MAPPING
index 99a9c91..9e5e79f 100644
--- a/libs/binder/TEST_MAPPING
+++ b/libs/binder/TEST_MAPPING
@@ -70,6 +70,9 @@
       "name": "CtsOsTestCases_ParcelAndBinderTests"
     },
     {
+      "name": "FrameworksCoreTests_all_binder"
+    },
+    {
       "name": "libbinder_rs-internal_test"
     },
     {
diff --git a/libs/binder/include/binder/IServiceManager.h b/libs/binder/include/binder/IServiceManager.h
index 81f7cdb..7d79baa 100644
--- a/libs/binder/include/binder/IServiceManager.h
+++ b/libs/binder/include/binder/IServiceManager.h
@@ -150,6 +150,12 @@
         int pid;
     };
     virtual std::vector<ServiceDebugInfo> getServiceDebugInfo() = 0;
+
+    /**
+     * Directly enable or disable caching binder during addService calls.
+     * Only used for testing. This is enabled by default.
+     */
+    virtual void enableAddServiceCache(bool value) = 0;
 };
 
 LIBBINDER_EXPORTED sp<IServiceManager> defaultServiceManager();
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index 28a3f65..642fbf3 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -70,7 +70,10 @@
     static_libs: [
         "libfakeservicemanager",
     ],
-    defaults: ["libbinder_client_cache_flag"],
+    defaults: [
+        "libbinder_client_cache_flag",
+        "libbinder_addservice_cache_flag",
+    ],
     test_suites: ["general-tests"],
     require_root: true,
 }
diff --git a/libs/binder/tests/binderCacheUnitTest.cpp b/libs/binder/tests/binderCacheUnitTest.cpp
index c5ad793..3195c55 100644
--- a/libs/binder/tests/binderCacheUnitTest.cpp
+++ b/libs/binder/tests/binderCacheUnitTest.cpp
@@ -34,6 +34,12 @@
 constexpr bool kUseLibbinderCache = false;
 #endif
 
+#ifdef LIBBINDER_ADDSERVICE_CACHE
+constexpr bool kUseCacheInAddService = true;
+#else
+constexpr bool kUseCacheInAddService = false;
+#endif
+
 // A service name which is in the static list of cachable services
 const String16 kCachedServiceName = String16("isub");
 
@@ -74,14 +80,58 @@
                 innerSm.addService(String16(name.c_str()), service, allowIsolated, dumpPriority));
     }
 
+    void clearServices() { innerSm.clear(); }
+
     FakeServiceManager innerSm;
 };
 
+class LibbinderCacheAddServiceTest : public ::testing::Test {
+protected:
+    void SetUp() override {
+        fakeServiceManager = sp<MockAidlServiceManager>::make();
+        mServiceManager = getServiceManagerShimFromAidlServiceManagerForTests(fakeServiceManager);
+        mServiceManager->enableAddServiceCache(true);
+    }
+
+    void TearDown() override {}
+
+public:
+    void cacheAddServiceAndConfirmCacheHit(const sp<IBinder>& binder1) {
+        // Add a service. This also caches it.
+        EXPECT_EQ(OK, mServiceManager->addService(kCachedServiceName, binder1));
+        // remove services from fakeservicemanager
+        fakeServiceManager->clearServices();
+
+        sp<IBinder> result = mServiceManager->checkService(kCachedServiceName);
+        if (kUseCacheInAddService && kUseLibbinderCache) {
+            // If cache is enabled, we should get the binder.
+            EXPECT_EQ(binder1, result);
+        } else {
+            // If cache is disabled, then we should get the null binder
+            EXPECT_EQ(nullptr, result);
+        }
+    }
+    sp<MockAidlServiceManager> fakeServiceManager;
+    sp<android::IServiceManager> mServiceManager;
+};
+
+TEST_F(LibbinderCacheAddServiceTest, AddLocalServiceAndConfirmCacheHit) {
+    sp<IBinder> binder1 = sp<BBinder>::make();
+    cacheAddServiceAndConfirmCacheHit(binder1);
+}
+
+TEST_F(LibbinderCacheAddServiceTest, AddRemoteServiceAndConfirmCacheHit) {
+    sp<IBinder> binder1 = defaultServiceManager()->checkService(kServerName);
+    ASSERT_NE(binder1, nullptr);
+    cacheAddServiceAndConfirmCacheHit(binder1);
+}
+
 class LibbinderCacheTest : public ::testing::Test {
 protected:
     void SetUp() override {
-        sp<MockAidlServiceManager> sm = sp<MockAidlServiceManager>::make();
-        mServiceManager = getServiceManagerShimFromAidlServiceManagerForTests(sm);
+        fakeServiceManager = sp<MockAidlServiceManager>::make();
+        mServiceManager = getServiceManagerShimFromAidlServiceManagerForTests(fakeServiceManager);
+        mServiceManager->enableAddServiceCache(false);
     }
 
     void TearDown() override {}
@@ -108,6 +158,7 @@
         }
     }
 
+    sp<MockAidlServiceManager> fakeServiceManager;
     sp<android::IServiceManager> mServiceManager;
 };
 
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index 970852c..9e92f95 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -223,6 +223,8 @@
             ASSERT_GT(m_serverpid, 0);
 
             sp<IServiceManager> sm = defaultServiceManager();
+            // disable caching during addService.
+            sm->enableAddServiceCache(false);
             //printf("%s: pid %d, get service\n", __func__, m_pid);
             LIBBINDER_IGNORE("-Wdeprecated-declarations")
             m_server = sm->getService(binderLibTestServiceName);
@@ -298,6 +300,9 @@
         virtual void SetUp() {
             m_server = static_cast<BinderLibTestEnv *>(binder_env)->getServer();
             IPCThreadState::self()->restoreCallingWorkSource(0);
+            sp<IServiceManager> sm = defaultServiceManager();
+            // disable caching during addService.
+            sm->enableAddServiceCache(false);
         }
         virtual void TearDown() {
         }
@@ -1765,6 +1770,7 @@
 
 TEST(ServiceNotifications, Unregister) {
     auto sm = defaultServiceManager();
+    sm->enableAddServiceCache(false);
     using LocalRegistrationCallback = IServiceManager::LocalRegistrationCallback;
     class LocalRegistrationCallbackImpl : public virtual LocalRegistrationCallback {
         void onServiceRegistration(const String16 &, const sp<IBinder> &) override {}
@@ -2529,6 +2535,8 @@
 
     status_t ret;
     sp<IServiceManager> sm = defaultServiceManager();
+    sm->enableAddServiceCache(false);
+
     BinderLibTestService* testServicePtr;
     {
         sp<BinderLibTestService> testService = new BinderLibTestService(index);
diff --git a/libs/fakeservicemanager/include/fakeservicemanager/FakeServiceManager.h b/libs/fakeservicemanager/include/fakeservicemanager/FakeServiceManager.h
index f62241d..f2b2aa7 100644
--- a/libs/fakeservicemanager/include/fakeservicemanager/FakeServiceManager.h
+++ b/libs/fakeservicemanager/include/fakeservicemanager/FakeServiceManager.h
@@ -65,6 +65,7 @@
 
     std::vector<IServiceManager::ServiceDebugInfo> getServiceDebugInfo() override;
 
+    void enableAddServiceCache(bool /*value*/) override {}
     // Clear all of the registered services
     void clear();
 
diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp
index 0c8f3fa..ba50bf8 100644
--- a/libs/gui/Choreographer.cpp
+++ b/libs/gui/Choreographer.cpp
@@ -369,6 +369,10 @@
           this, to_string(displayId).c_str(), connectedLevel, maxLevel);
 }
 
+void Choreographer::dispatchModeRejected(PhysicalDisplayId, int32_t) {
+    LOG_ALWAYS_FATAL("dispatchModeRejected was called but was never registered");
+}
+
 void Choreographer::handleMessage(const Message& message) {
     switch (message.what) {
         case MSG_SCHEDULE_CALLBACKS:
diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp
index c46f9c5..68f10f4 100644
--- a/libs/gui/DisplayEventDispatcher.cpp
+++ b/libs/gui/DisplayEventDispatcher.cpp
@@ -211,6 +211,9 @@
                                               ev.hdcpLevelsChange.connectedLevel,
                                               ev.hdcpLevelsChange.maxLevel);
                     break;
+                case DisplayEventReceiver::DISPLAY_EVENT_MODE_REJECTION:
+                    dispatchModeRejected(ev.header.displayId, ev.modeRejection.modeId);
+                    break;
                 default:
                     ALOGW("dispatcher %p ~ ignoring unknown event type %#x", this, ev.header.type);
                     break;
diff --git a/libs/gui/FrameRateUtils.cpp b/libs/gui/FrameRateUtils.cpp
index 01aa7ed..5c4879c 100644
--- a/libs/gui/FrameRateUtils.cpp
+++ b/libs/gui/FrameRateUtils.cpp
@@ -42,7 +42,7 @@
 
     if (compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT &&
         compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE &&
-        compatibility != ANATIVEWINDOW_FRAME_RATE_GTE &&
+        compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE &&
         (!privileged ||
          (compatibility != ANATIVEWINDOW_FRAME_RATE_EXACT &&
           compatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE))) {
diff --git a/libs/gui/include/gui/Choreographer.h b/libs/gui/include/gui/Choreographer.h
index 2e5aa4a..a93ba14 100644
--- a/libs/gui/include/gui/Choreographer.h
+++ b/libs/gui/include/gui/Choreographer.h
@@ -127,6 +127,7 @@
                                     std::vector<FrameRateOverride> overrides) override;
     void dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
                                    int32_t maxLevel) override;
+    void dispatchModeRejected(PhysicalDisplayId displayId, int32_t modeId) override;
 
     void scheduleCallbacks();
 
diff --git a/libs/gui/include/gui/DisplayEventDispatcher.h b/libs/gui/include/gui/DisplayEventDispatcher.h
index 82cd50c..b06ad07 100644
--- a/libs/gui/include/gui/DisplayEventDispatcher.h
+++ b/libs/gui/include/gui/DisplayEventDispatcher.h
@@ -68,6 +68,8 @@
     virtual void dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
                                            int32_t maxLevel) = 0;
 
+    virtual void dispatchModeRejected(PhysicalDisplayId displayId, int32_t modeId) = 0;
+
     bool processPendingEvents(nsecs_t* outTimestamp, PhysicalDisplayId* outDisplayId,
                               uint32_t* outCount, VsyncEventData* outVsyncEventData);
 
diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h
index 40a6e79..ab6a6b7 100644
--- a/libs/gui/include/gui/DisplayEventReceiver.h
+++ b/libs/gui/include/gui/DisplayEventReceiver.h
@@ -62,6 +62,7 @@
         DISPLAY_EVENT_VSYNC = fourcc('v', 's', 'y', 'n'),
         DISPLAY_EVENT_HOTPLUG = fourcc('p', 'l', 'u', 'g'),
         DISPLAY_EVENT_MODE_CHANGE = fourcc('m', 'o', 'd', 'e'),
+        DISPLAY_EVENT_MODE_REJECTION = fourcc('r', 'e', 'j', 'e'),
         DISPLAY_EVENT_NULL = fourcc('n', 'u', 'l', 'l'),
         DISPLAY_EVENT_FRAME_RATE_OVERRIDE = fourcc('r', 'a', 't', 'e'),
         DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH = fourcc('f', 'l', 's', 'h'),
@@ -96,6 +97,10 @@
             nsecs_t vsyncPeriod __attribute__((aligned(8)));
         };
 
+        struct ModeRejection {
+            int32_t modeId;
+        };
+
         struct FrameRateOverride {
             uid_t uid __attribute__((aligned(8)));
             float frameRateHz __attribute__((aligned(8)));
@@ -117,6 +122,7 @@
             ModeChange modeChange;
             FrameRateOverride frameRateOverride;
             HdcpLevelsChange hdcpLevelsChange;
+            ModeRejection modeRejection;
         };
     };
     static_assert(sizeof(Event) == 224);
diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp
index 0e84d68..dcda1ee 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -75,7 +75,7 @@
     if (input == nullptr) {
         ALOGE("Failed to link to input service");
     } else {
-        ALOGE("Linked to input");
+        ALOGI("Linked to input");
     }
     return interface_cast<IInputFlinger>(input);
 }
diff --git a/libs/gui/tests/FrameRateUtilsTest.cpp b/libs/gui/tests/FrameRateUtilsTest.cpp
index 04bfb28..5d3287d 100644
--- a/libs/gui/tests/FrameRateUtilsTest.cpp
+++ b/libs/gui/tests/FrameRateUtilsTest.cpp
@@ -34,7 +34,7 @@
                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS, ""));
     EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_GTE,
+    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE,
                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
 
     // Privileged APIs.
diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp
index 5ce4076..f5a1db5 100644
--- a/libs/nativewindow/ANativeWindow.cpp
+++ b/libs/nativewindow/ANativeWindow.cpp
@@ -265,16 +265,6 @@
     return native_window_set_frame_rate(window, frameRate, compatibility, changeFrameRateStrategy);
 }
 
-int32_t ANativeWindow_setFrameRateParams(
-        ANativeWindow* window, float desiredMinRate, float desiredMaxRate, float fixedSourceRate,
-        ANativeWindow_ChangeFrameRateStrategy changeFrameRateStrategy) {
-    if (!window || !query(window, NATIVE_WINDOW_IS_VALID)) {
-        return -EINVAL;
-    }
-    return native_window_set_frame_rate_params(window, desiredMinRate, desiredMaxRate,
-                                               fixedSourceRate, changeFrameRateStrategy);
-}
-
 /**************************************************************************************************
  * vndk-stable
  **************************************************************************************************/
diff --git a/libs/nativewindow/include/android/native_window.h b/libs/nativewindow/include/android/native_window.h
index bd8d67a..ed3e8c1 100644
--- a/libs/nativewindow/include/android/native_window.h
+++ b/libs/nativewindow/include/android/native_window.h
@@ -33,8 +33,8 @@
 #ifndef ANDROID_NATIVE_WINDOW_H
 #define ANDROID_NATIVE_WINDOW_H
 
-#include <stdbool.h>
 #include <stdint.h>
+#include <stdbool.h>
 #include <sys/cdefs.h>
 
 #include <android/data_space.h>
@@ -243,8 +243,7 @@
      * There are no inherent restrictions on the frame rate of this window. When
      * the system selects a frame rate other than what the app requested, the
      * app will be able to run at the system frame rate without requiring pull
-     * down. This value should be used when displaying game content, UIs, and
-     * anything that isn't video.
+     * down. This value should be used when displaying game content.
      */
     ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT = 0,
     /**
@@ -256,7 +255,14 @@
      * stuttering) than it would be if the system had chosen the app's requested
      * frame rate. This value should be used for video content.
      */
-    ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1
+    ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1,
+
+    /**
+     * The window requests a frame rate that is greater than or equal to the specified frame rate.
+     * This value should be used for UIs, animations, scrolling, and anything that is not a game
+     * or video.
+     */
+    ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE = 2
 };
 
 /**
@@ -282,7 +288,7 @@
 void ANativeWindow_tryAllocateBuffers(ANativeWindow* window) __INTRODUCED_IN(30);
 
 /** Change frame rate strategy value for ANativeWindow_setFrameRate. */
-typedef enum ANativeWindow_ChangeFrameRateStrategy : int8_t {
+enum ANativeWindow_ChangeFrameRateStrategy {
     /**
      * Change the frame rate only if the transition is going to be seamless.
      */
@@ -292,7 +298,7 @@
      * i.e. with visual interruptions for the user.
      */
     ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS = 1
-} ANativeWindow_ChangeFrameRateStrategy __INTRODUCED_IN(31);
+} __INTRODUCED_IN(31);
 
 /**
  * Sets the intended frame rate for this window.
@@ -345,76 +351,6 @@
         __INTRODUCED_IN(31);
 
 /**
- * Sets the intended frame rate for this window.
- *
- * On devices that are capable of running the display at different frame rates,
- * the system may choose a display refresh rate to better match this surface's frame
- * rate. Usage of this API won't introduce frame rate throttling, or affect other
- * aspects of the application's frame production pipeline. However, because the system
- * may change the display refresh rate, calls to this function may result in changes
- * to Choreographer callback timings, and changes to the time interval at which the
- * system releases buffers back to the application.
- *
- * Note that this only has an effect for surfaces presented on the display. If this
- * surface is consumed by something other than the system compositor, e.g. a media
- * codec, this call has no effect.
- *
- * You can register for changes in the refresh rate using
- * \a AChoreographer_registerRefreshRateCallback.
- *
- * See ANativeWindow_clearFrameRate().
- *
- * Available since API level 36.
- *
- * \param window pointer to an ANativeWindow object.
- *
- * \param desiredMinRate The desired minimum frame rate (inclusive) for the window, specifying that
- * the surface prefers the device render rate to be at least `desiredMinRate`.
- *
- * <p>Set `desiredMinRate` = `desiredMaxRate` to indicate the surface prefers an exact frame rate.
- *
- * <p>Set `desiredMinRate` = 0 to indicate the window has no preference
- * and any frame rate is acceptable.
- *
- * <p>The value should be greater than or equal to 0.
- *
- * \param desiredMaxRate The desired maximum frame rate (inclusive) for the window, specifying that
- * the surface prefers the device render rate to be at most `desiredMaxRate`.
- *
- * <p>Set `desiredMaxRate` = `desiredMinRate` to indicate the surface prefers an exact frame rate.
- *
- * <p>Set `desiredMaxRate` = positive infinity to indicate the window has no preference
- * and any frame rate is acceptable.
- *
- * <p>The value should be greater than or equal to `desiredMinRate`.
- *
- * \param fixedSourceRate The "fixed source" frame rate of the window if the content has an
- * inherently fixed frame rate, e.g. a video that has a specific frame rate.
- *
- * <p>When the frame rate chosen for the surface is the `fixedSourceRate` or a
- * multiple, the surface can render without frame pulldown, for optimal smoothness. For
- * example, a 30 fps video (`fixedSourceRate`=30) renders just as smoothly on 30 fps,
- * 60 fps, 90 fps, 120 fps, and so on.
- *
- * <p>Setting the fixed source rate can also be used together with a desired
- * frame rate min and max via setting `desiredMinRate` and `desiredMaxRate`. This still
- * means the window's content has a fixed frame rate of `fixedSourceRate`, but additionally
- * specifies the preference to be in the range [`desiredMinRate`, `desiredMaxRate`]. For example, an
- * app might want to specify there is 30 fps video (`fixedSourceRate`=30) as well as a smooth
- * animation on the same window which looks good when drawing within a frame rate range such as
- * [`desiredMinRate`, `desiredMaxRate`] = [60,120].
- *
- * \param changeFrameRateStrategy Whether display refresh rate transitions caused by this surface
- * should be seamless. A seamless transition is one that doesn't have any visual interruptions, such
- * as a black screen for a second or two.
- *
- * \return 0 for success, -EINVAL if the arguments are invalid.
- */
-int32_t ANativeWindow_setFrameRateParams(
-        ANativeWindow* window, float desiredMinRate, float desiredMaxRate, float fixedSourceRate,
-        ANativeWindow_ChangeFrameRateStrategy changeFrameRateStrategy) __INTRODUCED_IN(36);
-
-/**
  * Clears the frame rate which is set for this window.
  *
  * This is equivalent to calling
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index 05f49ad..f669f77 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -1060,12 +1060,7 @@
     /**
      * This surface will vote for the minimum refresh rate.
      */
-    ANATIVEWINDOW_FRAME_RATE_MIN,
-
-    /**
-     * The surface requests a frame rate that is greater than or equal to `frameRate`.
-     */
-    ANATIVEWINDOW_FRAME_RATE_GTE
+    ANATIVEWINDOW_FRAME_RATE_MIN
 };
 
 /*
@@ -1156,23 +1151,6 @@
                            (int)compatibility, (int)changeFrameRateStrategy);
 }
 
-static inline int native_window_set_frame_rate_params(struct ANativeWindow* window,
-                                                      float desiredMinRate, float desiredMaxRate,
-                                                      float fixedSourceRate,
-                                                      int8_t changeFrameRateStrategy) {
-    // TODO(b/362798998): Fix plumbing to send whole params
-    int compatibility = fixedSourceRate == 0 ? ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT
-                                             : ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
-    double frameRate = compatibility == ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
-            ? fixedSourceRate
-            : desiredMinRate;
-    if (desiredMaxRate < desiredMinRate) {
-        return -EINVAL;
-    }
-    return window->perform(window, NATIVE_WINDOW_SET_FRAME_RATE, frameRate, compatibility,
-                           changeFrameRateStrategy);
-}
-
 struct ANativeWindowFrameTimelineInfo {
     // Frame Id received from ANativeWindow_getNextFrameId.
     uint64_t frameNumber;
diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt
index 071e354..e29d5a6 100644
--- a/libs/nativewindow/libnativewindow.map.txt
+++ b/libs/nativewindow/libnativewindow.map.txt
@@ -53,7 +53,6 @@
     ANativeWindow_setBuffersTransform;
     ANativeWindow_setDequeueTimeout; # systemapi introduced=30
     ANativeWindow_setFrameRate; # introduced=30
-    ANativeWindow_setFrameRateParams; # introduced=36
     ANativeWindow_setFrameRateWithChangeStrategy; # introduced=31
     ANativeWindow_setSharedBufferMode; # llndk
     ANativeWindow_setSwapInterval; # llndk
diff --git a/libs/nativewindow/rust/src/lib.rs b/libs/nativewindow/rust/src/lib.rs
index 9876362..d760285 100644
--- a/libs/nativewindow/rust/src/lib.rs
+++ b/libs/nativewindow/rust/src/lib.rs
@@ -30,8 +30,8 @@
     StatusCode,
 };
 use ffi::{
-    AHardwareBuffer, AHardwareBuffer_Desc, AHardwareBuffer_readFromParcel,
-    AHardwareBuffer_writeToParcel, ARect,
+    AHardwareBuffer, AHardwareBuffer_Desc, AHardwareBuffer_Plane, AHardwareBuffer_Planes,
+    AHardwareBuffer_readFromParcel, AHardwareBuffer_writeToParcel, ARect,
 };
 use std::ffi::c_void;
 use std::fmt::{self, Debug, Formatter};
@@ -313,6 +313,57 @@
         })
     }
 
+    /// Lock a potentially multi-planar hardware buffer for direct CPU access.
+    ///
+    /// # Safety
+    ///
+    /// - If `fence` is `None`, the caller must ensure that all writes to the buffer have completed
+    ///   before calling this function.
+    /// - If the buffer has `AHARDWAREBUFFER_FORMAT_BLOB`, multiple threads or process may lock the
+    ///   buffer simultaneously, but the caller must ensure that they don't access it simultaneously
+    ///   and break Rust's aliasing rules, like any other shared memory.
+    /// - Otherwise if `usage` includes `AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY` or
+    ///   `AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN`, the caller must ensure that no other threads or
+    ///   processes lock the buffer simultaneously for any usage.
+    /// - Otherwise, the caller must ensure that no other threads lock the buffer for writing
+    ///   simultaneously.
+    /// - If `rect` is not `None`, the caller must not modify the buffer outside of that rectangle.
+    pub unsafe fn lock_planes<'a>(
+        &'a self,
+        usage: AHardwareBuffer_UsageFlags,
+        fence: Option<BorrowedFd>,
+        rect: Option<&ARect>,
+    ) -> Result<Vec<PlaneGuard<'a>>, StatusCode> {
+        let fence = if let Some(fence) = fence { fence.as_raw_fd() } else { -1 };
+        let rect = rect.map(ptr::from_ref).unwrap_or(null());
+        let mut planes = AHardwareBuffer_Planes {
+            planeCount: 0,
+            planes: [const { AHardwareBuffer_Plane { data: null_mut(), pixelStride: 0, rowStride: 0 } };
+                4],
+        };
+
+        // SAFETY: The `AHardwareBuffer` pointer we wrap is always valid, and the various out
+        // pointers are valid because they come from references. Our caller promises that writes have
+        // completed and there will be no simultaneous read/write locks.
+        let status = unsafe {
+            ffi::AHardwareBuffer_lockPlanes(self.0.as_ptr(), usage.0, fence, rect, &mut planes)
+        };
+        status_result(status)?;
+        let plane_count = planes.planeCount.try_into().unwrap();
+        Ok(planes.planes[..plane_count]
+            .iter()
+            .map(|plane| PlaneGuard {
+                guard: HardwareBufferGuard {
+                    buffer: self,
+                    address: NonNull::new(plane.data)
+                        .expect("AHardwareBuffer_lockAndGetInfo set a null outVirtualAddress"),
+                },
+                pixel_stride: plane.pixelStride,
+                row_stride: plane.rowStride,
+            })
+            .collect())
+    }
+
     /// Locks the hardware buffer for direct CPU access, returning information about the bytes per
     /// pixel and stride as well.
     ///
@@ -510,6 +561,18 @@
     pub stride: u32,
 }
 
+/// A guard for a single plane of a locked `HardwareBuffer`, with additional information about the
+/// stride.
+#[derive(Debug)]
+pub struct PlaneGuard<'a> {
+    /// The locked buffer guard.
+    pub guard: HardwareBufferGuard<'a>,
+    /// The stride in bytes between the color channel for one pixel to the next pixel.
+    pub pixel_stride: u32,
+    /// The stride in bytes between rows in the buffer.
+    pub row_stride: u32,
+}
+
 #[cfg(test)]
 mod test {
     use super::*;
diff --git a/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
index db0b133..da47aae 100644
--- a/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
+++ b/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
@@ -96,13 +96,17 @@
 void KawaseBlurDualFilter::blurInto(const sk_sp<SkSurface>& drawSurface, sk_sp<SkShader> input,
                                     const float inverseScale, const float radius,
                                     const float alpha) const {
-    SkRuntimeShaderBuilder blurBuilder(mBlurEffect);
-    blurBuilder.child("child") = std::move(input);
-    blurBuilder.uniform("in_inverseScale") = inverseScale;
-    blurBuilder.uniform("in_blurOffset") = radius;
-    blurBuilder.uniform("in_crossFade") = alpha;
     SkPaint paint;
-    paint.setShader(blurBuilder.makeShader(nullptr));
+    if (radius == 0) {
+        paint.setShader(std::move(input));
+        paint.setAlphaf(alpha);
+    } else {
+        SkRuntimeShaderBuilder blurBuilder(mBlurEffect);
+        blurBuilder.child("child") = std::move(input);
+        blurBuilder.uniform("in_blurOffset") = radius;
+        blurBuilder.uniform("in_crossFade") = alpha;
+        paint.setShader(blurBuilder.makeShader(nullptr));
+    }
     paint.setBlendMode(alpha == 1.0f ? SkBlendMode::kSrc : SkBlendMode::kSrcOver);
     drawSurface->getCanvas()->drawPaint(paint);
 }
@@ -116,32 +120,35 @@
 
     // Use a variable number of blur passes depending on the radius. The non-integer part of this
     // calculation is used to mix the final pass into the second-last with an alpha blend.
-    constexpr int kMaxSurfaces = 4;
-    const float filterDepth =
-            std::min(kMaxSurfaces - 1.0f, 1.0f + std::max(0.0f, log2f(radius * kInputScale)));
+    constexpr int kMaxSurfaces = 3;
+    const float filterDepth = std::min(kMaxSurfaces - 1.0f, radius * kInputScale / 2.5f);
     const int filterPasses = std::min(kMaxSurfaces - 1, static_cast<int>(ceil(filterDepth)));
 
-    // Render into surfaces downscaled by 1x, 1x, 2x, and 4x from the initial downscale.
+    // Render into surfaces downscaled by 1x, 2x, and 4x from the initial downscale.
     sk_sp<SkSurface> surfaces[kMaxSurfaces] =
             {filterPasses >= 0 ? makeSurface(context, blurRect, 1 * kInverseInputScale) : nullptr,
-             filterPasses >= 1 ? makeSurface(context, blurRect, 1 * kInverseInputScale) : nullptr,
-             filterPasses >= 2 ? makeSurface(context, blurRect, 2 * kInverseInputScale) : nullptr,
-             filterPasses >= 3 ? makeSurface(context, blurRect, 4 * kInverseInputScale) : nullptr};
+             filterPasses >= 1 ? makeSurface(context, blurRect, 2 * kInverseInputScale) : nullptr,
+             filterPasses >= 2 ? makeSurface(context, blurRect, 4 * kInverseInputScale) : nullptr};
 
-    // These weights for scaling offsets per-pass are handpicked to look good at 1 <= radius <= 600.
-    static const float kWeights[7] = {1.0f, 2.0f, 3.5f, 1.0f, 2.0f, 2.0f, 2.0f};
+    // These weights for scaling offsets per-pass are handpicked to look good at 1 <= radius <= 250.
+    static const float kWeights[5] = {
+            1.0f, // 1st downsampling pass
+            1.0f, // 2nd downsampling pass
+            1.0f, // 3rd downsampling pass
+            0.0f, // 1st upscaling pass. Set to zero to upscale without blurring for performance.
+            1.0f, // 2nd upscaling pass
+    };
 
     // Kawase is an approximation of Gaussian, but behaves differently because it is made up of many
     // simpler blurs. A transformation is required to approximate the same effect as Gaussian.
-    float sumSquaredR = powf(kWeights[0] * powf(2.0f, 1), 2.0f);
+    float sumSquaredR = powf(kWeights[0], 2.0f);
     for (int i = 0; i < filterPasses; i++) {
         const float alpha = std::min(1.0f, filterDepth - i);
-        sumSquaredR += powf(powf(2.0f, i + 1) * alpha * kWeights[1 + i], 2.0f);
-        sumSquaredR += powf(powf(2.0f, i + 1) * alpha * kWeights[6 - i], 2.0f);
+        sumSquaredR += powf(powf(2.0f, i) * alpha * kWeights[1 + i] / kInputScale, 2.0f);
+        sumSquaredR += powf(powf(2.0f, i + 1) * alpha * kWeights[4 - i] / kInputScale, 2.0f);
     }
-    // Solve for R = sqrt(sum(r_i^2)). Divide R by hypot(1,1) to find some (x,y) offsets.
-    const float step = M_SQRT1_2 *
-            sqrtf(max(0.0f, (powf(radius, 2.0f) - powf(kInverseInputScale, 2.0f)) / sumSquaredR));
+    // Solve for R = sqrt(sum(r_i^2)).
+    const float step = radius * sqrt(1.0f / sumSquaredR);
 
     // Start by downscaling and doing the first blur pass.
     {
@@ -162,7 +169,7 @@
     }
     // Finally blur+upscale back to our original size.
     for (int i = filterPasses - 1; i >= 0; i--) {
-        blurInto(surfaces[i], surfaces[i + 1]->makeImageSnapshot(), kWeights[6 - i] * step,
+        blurInto(surfaces[i], surfaces[i + 1]->makeImageSnapshot(), kWeights[4 - i] * step,
                  std::min(1.0f, filterDepth - i));
     }
     return surfaces[0]->makeImageSnapshot();
diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.cpp b/libs/tracing_perfetto/tracing_perfetto_internal.cpp
index c4f8663..a58bc77 100644
--- a/libs/tracing_perfetto/tracing_perfetto_internal.cpp
+++ b/libs/tracing_perfetto/tracing_perfetto_internal.cpp
@@ -236,6 +236,7 @@
   std::call_once(registration, [test]() {
     struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT();
     args.backends = test ? PERFETTO_BACKEND_IN_PROCESS : PERFETTO_BACKEND_SYSTEM;
+    args.shmem_size_hint_kb = 1024;
     PerfettoProducerInit(args);
     PerfettoTeInit();
     PERFETTO_TE_REGISTER_CATEGORIES(FRAMEWORK_CATEGORIES);
diff --git a/libs/ui/PublicFormat.cpp b/libs/ui/PublicFormat.cpp
index c9663ed..dbc0884 100644
--- a/libs/ui/PublicFormat.cpp
+++ b/libs/ui/PublicFormat.cpp
@@ -30,6 +30,7 @@
         case PublicFormat::DEPTH_POINT_CLOUD:
         case PublicFormat::DEPTH_JPEG:
         case PublicFormat::HEIC:
+        case PublicFormat::HEIC_ULTRAHDR:
         case PublicFormat::JPEG_R:
             return HAL_PIXEL_FORMAT_BLOB;
         case PublicFormat::DEPTH16:
@@ -74,6 +75,9 @@
         case PublicFormat::HEIC:
             dataspace = Dataspace::HEIF;
             break;
+        case PublicFormat::HEIC_ULTRAHDR:
+            dataspace = Dataspace::HEIF_ULTRAHDR;
+            break;
         case PublicFormat::JPEG_R:
             dataspace = Dataspace::JPEG_R;
             break;
@@ -153,6 +157,9 @@
                         return PublicFormat::DEPTH_JPEG;
                     } else if (dataSpace == static_cast<android_dataspace>(Dataspace::JPEG_R)) {
                         return PublicFormat::JPEG_R;
+                    } else if (dataSpace == static_cast<android_dataspace>(
+                                Dataspace::HEIF_ULTRAHDR)) {
+                        return PublicFormat::HEIC_ULTRAHDR;
                     }else {
                         // Assume otherwise-marked blobs are also JPEG
                         return PublicFormat::JPEG;
diff --git a/libs/ui/include/ui/PublicFormat.h b/libs/ui/include/ui/PublicFormat.h
index 2248cca..e87931e 100644
--- a/libs/ui/include/ui/PublicFormat.h
+++ b/libs/ui/include/ui/PublicFormat.h
@@ -59,6 +59,7 @@
     DEPTH_JPEG = 0x69656963,
     JPEG_R = 0x1005,
     HEIC = 0x48454946,
+    HEIC_ULTRAHDR = 0x1006,
 };
 
 /* Convert from android.graphics.ImageFormat/PixelFormat enums to graphics.h HAL
diff --git a/opengl/libs/Android.bp b/opengl/libs/Android.bp
index b19a862..91250b9 100644
--- a/opengl/libs/Android.bp
+++ b/opengl/libs/Android.bp
@@ -63,6 +63,18 @@
     unversioned_until: "current",
 }
 
+aconfig_declarations {
+    name: "egl_flags",
+    package: "com.android.graphics.egl.flags",
+    container: "system",
+    srcs: ["EGL/egl_flags.aconfig"],
+}
+
+cc_aconfig_library {
+    name: "libegl_flags",
+    aconfig_declarations: "egl_flags",
+}
+
 cc_defaults {
     name: "gl_libs_defaults",
     cflags: [
@@ -136,6 +148,7 @@
     ],
     export_include_dirs: ["EGL"],
     shared_libs: [
+        "libegl_flags",
         "libz",
     ],
 }
@@ -166,6 +179,7 @@
         "android.hardware.configstore@1.0",
         "android.hardware.configstore-utils",
         "libbase",
+        "libegl_flags",
         "libhidlbase",
         "libnativebridge_lazy",
         "libnativeloader_lazy",
@@ -202,6 +216,7 @@
         "EGL/MultifileBlobCache_test.cpp",
     ],
     shared_libs: [
+        "libegl_flags",
         "libutils",
         "libz",
     ],
diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp
index f7e33b3..04c525e 100644
--- a/opengl/libs/EGL/MultifileBlobCache.cpp
+++ b/opengl/libs/EGL/MultifileBlobCache.cpp
@@ -38,11 +38,20 @@
 
 #include <utils/JenkinsHash.h>
 
+#include <com_android_graphics_egl_flags.h>
+
+using namespace com::android::graphics::egl;
+
 using namespace std::literals;
 
 constexpr uint32_t kMultifileMagic = 'MFB$';
 constexpr uint32_t kCrcPlaceholder = 0;
 
+// When removing files, what fraction of the overall limit should be reached when removing files
+// A divisor of two will decrease the cache to 50%, four to 25% and so on
+// We use the same limit to manage size and entry count
+constexpr uint32_t kCacheLimitDivisor = 2;
+
 namespace {
 
 // Helper function to close entries or free them
@@ -72,6 +81,7 @@
         mMaxTotalEntries(maxTotalEntries),
         mTotalCacheSize(0),
         mTotalCacheEntries(0),
+        mTotalCacheSizeDivisor(kCacheLimitDivisor),
         mHotCacheLimit(0),
         mHotCacheSize(0),
         mWorkerThreadIdle(true) {
@@ -80,8 +90,13 @@
         return;
     }
 
-    // Set the cache version, override if debug value set
+    // Set the cache version
     mCacheVersion = kMultifileBlobCacheVersion;
+    // Bump the version if we're using flagged features
+    if (flags::multifile_blobcache_advanced_usage()) {
+        mCacheVersion++;
+    }
+    // Override if debug value set
     int debugCacheVersion = base::GetIntProperty("debug.egl.blobcache.cache_version", -1);
     if (debugCacheVersion >= 0) {
         ALOGV("INIT: Using %u as cacheVersion instead of %u", debugCacheVersion, mCacheVersion);
@@ -122,7 +137,7 @@
     // Check that our cacheVersion and buildId match
     struct stat st;
     if (stat(mMultifileDirName.c_str(), &st) == 0) {
-        if (checkStatus(mMultifileDirName.c_str())) {
+        if (checkStatus(mMultifileDirName)) {
             statusGood = true;
         } else {
             ALOGV("INIT: Cache status has changed, clearing the cache");
@@ -237,11 +252,9 @@
 
                 ALOGV("INIT: Entry %u is good, tracking it now.", entryHash);
 
-                // Track details for rapid lookup later
-                trackEntry(entryHash, header.valueSize, fileSize, st.st_atime);
-
-                // Track the total size
-                increaseTotalCacheSize(fileSize);
+                // Track details for rapid lookup later and update total size
+                // Note access time is a full timespec instead of just seconds
+                trackEntry(entryHash, header.valueSize, fileSize, st.st_atim);
 
                 // Preload the entry for fast retrieval
                 if ((mHotCacheSize + fileSize) < mHotCacheLimit) {
@@ -317,6 +330,28 @@
     // Generate a hash of the key and use it to track this entry
     uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
 
+    std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
+
+    // See if we already have this file
+    if (flags::multifile_blobcache_advanced_usage() && contains(entryHash)) {
+        // Remove previous entry from hot cache
+        removeFromHotCache(entryHash);
+
+        // Remove previous entry and update the overall cache size
+        removeEntry(entryHash);
+
+        // If valueSize is zero, this is an indication that the user wants to remove the entry from
+        // cache It has already been removed from tracking, now remove it from disk It is safe to do
+        // this immediately because we drained the write queue in removeFromHotCache
+        if (valueSize == 0) {
+            ALOGV("SET: Zero size detected for existing entry, removing %u from cache", entryHash);
+            if (remove(fullPath.c_str()) != 0) {
+                ALOGW("SET: Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+            }
+            return;
+        }
+    }
+
     size_t fileSize = sizeof(MultifileHeader) + keySize + valueSize;
 
     // If we're going to be over the cache limit, kick off a trim to clear space
@@ -339,13 +374,12 @@
     memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader) + keySize),
            static_cast<const void*>(value), valueSize);
 
-    std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
-
-    // Track the size and access time for quick recall
-    trackEntry(entryHash, valueSize, fileSize, time(0));
-
-    // Update the overall cache size
-    increaseTotalCacheSize(fileSize);
+    // Track the size and access time for quick recall and update the overall cache size
+    struct timespec time = {0, 0};
+    if (flags::multifile_blobcache_advanced_usage()) {
+        clock_gettime(CLOCK_REALTIME, &time);
+    }
+    trackEntry(entryHash, valueSize, fileSize, time);
 
     // Keep the entry in hot cache for quick retrieval
     ALOGV("SET: Adding %u to hot cache.", entryHash);
@@ -427,6 +461,14 @@
     if (mHotCache.find(entryHash) != mHotCache.end()) {
         ALOGV("GET: HotCache HIT for entry %u", entryHash);
         cacheEntry = mHotCache[entryHash].entryBuffer;
+
+        if (flags::multifile_blobcache_advanced_usage()) {
+            // Update last access time on disk
+            struct timespec times[2];
+            times[0].tv_nsec = UTIME_NOW;
+            times[1].tv_nsec = UTIME_OMIT;
+            utimensat(0, fullPath.c_str(), times, 0);
+        }
     } else {
         ALOGV("GET: HotCache MISS for entry: %u", entryHash);
 
@@ -455,6 +497,14 @@
         cacheEntry =
                 reinterpret_cast<uint8_t*>(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
 
+        if (flags::multifile_blobcache_advanced_usage()) {
+            // Update last access time and omit last modify time
+            struct timespec times[2];
+            times[0].tv_nsec = UTIME_NOW;
+            times[1].tv_nsec = UTIME_OMIT;
+            futimens(fd, times);
+        }
+
         // We can close the file now and the mmap will remain
         close(fd);
 
@@ -491,6 +541,13 @@
         return 0;
     }
 
+    if (flags::multifile_blobcache_advanced_usage()) {
+        // Update the entry time for this hash, so it reflects LRU
+        struct timespec time;
+        clock_gettime(CLOCK_REALTIME, &time);
+        updateEntryTime(entryHash, time);
+    }
+
     // Remaining entry following the key is the value
     uint8_t* cachedValue = cacheEntry + (keySize + sizeof(MultifileHeader));
     memcpy(value, cachedValue, cachedValueSize);
@@ -626,9 +683,47 @@
 }
 
 void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
-                                    time_t accessTime) {
+                                    const timespec& accessTime) {
+#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+    // When we add this entry to the map, it is sorted by accessTime
+    MultifileEntryStatsMapIter entryStatsIter =
+            mEntryStats.emplace(std::piecewise_construct, std::forward_as_tuple(accessTime),
+                                std::forward_as_tuple(entryHash, valueSize, fileSize));
+
+    // Track all entries with quick access to its stats
+    mEntries.emplace(entryHash, entryStatsIter);
+#else
+    (void)accessTime;
     mEntries.insert(entryHash);
-    mEntryStats[entryHash] = {valueSize, fileSize, accessTime};
+    mEntryStats[entryHash] = {entryHash, valueSize, fileSize};
+#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+
+    increaseTotalCacheSize(fileSize);
+}
+
+bool MultifileBlobCache::removeEntry(uint32_t entryHash) {
+    auto entryIter = mEntries.find(entryHash);
+    if (entryIter == mEntries.end()) {
+        return false;
+    }
+
+#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+    MultifileEntryStatsMapIter entryStatsIter = entryIter->second;
+    MultifileEntryStats entryStats = entryStatsIter->second;
+    decreaseTotalCacheSize(entryStats.fileSize);
+#else
+    auto entryStatsIter = mEntryStats.find(entryHash);
+    if (entryStatsIter == mEntryStats.end()) {
+        ALOGE("Failed to remove entryHash (%u) from mEntryStats", entryHash);
+        return false;
+    }
+    decreaseTotalCacheSize(entryStatsIter->second.fileSize);
+#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+
+    mEntryStats.erase(entryStatsIter);
+    mEntries.erase(entryIter);
+
+    return true;
 }
 
 bool MultifileBlobCache::contains(uint32_t hashEntry) const {
@@ -636,7 +731,40 @@
 }
 
 MultifileEntryStats MultifileBlobCache::getEntryStats(uint32_t entryHash) {
+#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+    auto entryIter = mEntries.find(entryHash);
+    if (entryIter == mEntries.end()) {
+        return {};
+    }
+
+    MultifileEntryStatsMapIter entryStatsIter = entryIter->second;
+    MultifileEntryStats entryStats = entryStatsIter->second;
+    return entryStats;
+#else
     return mEntryStats[entryHash];
+#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+}
+
+void MultifileBlobCache::updateEntryTime(uint32_t entryHash, const timespec& newTime) {
+#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+    // This function updates the ordering of the map by removing the old iterators
+    // and re-adding them. If should be perforant as it does not perform a full re-sort.
+    // First, pull out the old entryStats
+    auto entryIter = mEntries.find(entryHash);
+    MultifileEntryStatsMapIter entryStatsIter = entryIter->second;
+    MultifileEntryStats entryStats = std::move(entryStatsIter->second);
+
+    // Remove the old iterators
+    mEntryStats.erase(entryStatsIter);
+    mEntries.erase(entryIter);
+
+    // Insert the new with updated time
+    entryStatsIter = mEntryStats.emplace(std::make_pair(newTime, std::move(entryStats)));
+    mEntries.emplace(entryHash, entryStatsIter);
+#else
+    (void)entryHash;
+    (void)newTime;
+#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
 }
 
 void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) {
@@ -718,31 +846,32 @@
 bool MultifileBlobCache::applyLRU(size_t cacheSizeLimit, size_t cacheEntryLimit) {
     // Walk through our map of sorted last access times and remove files until under the limit
     for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) {
+#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+        const MultifileEntryStats& entryStats = cacheEntryIter->second;
+        uint32_t entryHash = entryStats.entryHash;
+#else
         uint32_t entryHash = cacheEntryIter->first;
+        const MultifileEntryStats& entryStats = cacheEntryIter->second;
+#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
 
         ALOGV("LRU: Removing entryHash %u", entryHash);
 
-        // Track the overall size
-        MultifileEntryStats entryStats = getEntryStats(entryHash);
-        decreaseTotalCacheSize(entryStats.fileSize);
-
         // Remove it from hot cache if present
         removeFromHotCache(entryHash);
 
         // Remove it from the system
         std::string entryPath = mMultifileDirName + "/" + std::to_string(entryHash);
         if (remove(entryPath.c_str()) != 0) {
-            ALOGE("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno));
-            return false;
+            // Continue evicting invalid item (app's cache might be cleared)
+            ALOGW("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno));
         }
 
         // Increment the iterator before clearing the entry
         cacheEntryIter++;
 
-        // Delete the entry from our tracking
-        size_t count = mEntryStats.erase(entryHash);
-        if (count != 1) {
-            ALOGE("LRU: Failed to remove entryHash (%u) from mEntryStats", entryHash);
+        // Delete the entry from our tracking and update the overall cache size
+        if (!removeEntry(entryHash)) {
+            ALOGE("LRU: Failed to remove entryHash %u", entryHash);
             return false;
         }
 
@@ -794,11 +923,6 @@
     return true;
 }
 
-// When removing files, what fraction of the overall limit should be reached when removing files
-// A divisor of two will decrease the cache to 50%, four to 25% and so on
-// We use the same limit to manage size and entry count
-constexpr uint32_t kCacheLimitDivisor = 2;
-
 // Calculate the cache size and remove old entries until under the limit
 void MultifileBlobCache::trimCache() {
     // Wait for all deferred writes to complete
@@ -806,8 +930,10 @@
     waitForWorkComplete();
 
     ALOGV("TRIM: Reducing multifile cache size to %zu, entries %zu",
-          mMaxTotalSize / kCacheLimitDivisor, mMaxTotalEntries / kCacheLimitDivisor);
-    if (!applyLRU(mMaxTotalSize / kCacheLimitDivisor, mMaxTotalEntries / kCacheLimitDivisor)) {
+          mMaxTotalSize / mTotalCacheSizeDivisor, mMaxTotalEntries / mTotalCacheSizeDivisor);
+
+    if (!applyLRU(mMaxTotalSize / mTotalCacheSizeDivisor,
+                  mMaxTotalEntries / mTotalCacheSizeDivisor)) {
         ALOGE("Error when clearing multifile shader cache");
         return;
     }
@@ -830,9 +956,36 @@
             // Create the file or reset it if already present, read+write for user only
             int fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
             if (fd == -1) {
-                ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s",
-                      fullPath.c_str(), std::strerror(errno));
-                return;
+                if (flags::multifile_blobcache_advanced_usage()) {
+                    struct stat st;
+                    if (stat(mMultifileDirName.c_str(), &st) == -1) {
+                        ALOGW("Cache directory missing (app's cache cleared?). Recreating...");
+
+                        // Restore the multifile directory
+                        if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) {
+                            ALOGE("Cache error in SET - Unable to create directory (%s), errno "
+                                  "(%i)",
+                                  mMultifileDirName.c_str(), errno);
+                            return;
+                        }
+
+                        // Create new status file
+                        if (!createStatus(mMultifileDirName.c_str())) {
+                            ALOGE("Cache error in SET - Failed to create status file!");
+                            return;
+                        }
+
+                        // Try to open the file again
+                        fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC,
+                                  S_IRUSR | S_IWUSR);
+                    }
+                }
+
+                if (fd == -1) {
+                    ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s",
+                          fullPath.c_str(), std::strerror(errno));
+                    return;
+                }
             }
 
             ALOGV("DEFERRED: Opened fd %i from %s", fd, fullPath.c_str());
@@ -849,6 +1002,14 @@
                 return;
             }
 
+            if (flags::multifile_blobcache_advanced_usage()) {
+                // Update last access time and last modify time
+                struct timespec times[2];
+                times[0].tv_nsec = UTIME_NOW;
+                times[1].tv_nsec = UTIME_NOW;
+                futimens(fd, times);
+            }
+
             ALOGV("DEFERRED: Completed write for: %s", fullPath.c_str());
             close(fd);
 
diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h
index 65aa2db..3bd393f 100644
--- a/opengl/libs/EGL/MultifileBlobCache.h
+++ b/opengl/libs/EGL/MultifileBlobCache.h
@@ -32,6 +32,10 @@
 
 #include "FileBlobCache.h"
 
+#include <com_android_graphics_egl_flags.h>
+
+using namespace com::android::graphics::egl;
+
 namespace android {
 
 constexpr uint32_t kMultifileBlobCacheVersion = 2;
@@ -45,9 +49,9 @@
 };
 
 struct MultifileEntryStats {
+    uint32_t entryHash;
     EGLsizeiANDROID valueSize;
     size_t fileSize;
-    time_t accessTime;
 };
 
 struct MultifileStatus {
@@ -100,6 +104,26 @@
     size_t mBufferSize;
 };
 
+#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+struct MultifileTimeLess {
+    bool operator()(const struct timespec& t1, const struct timespec& t2) const {
+        if (t1.tv_sec == t2.tv_sec) {
+            // If seconds are equal, check nanoseconds
+            return t1.tv_nsec < t2.tv_nsec;
+        } else {
+            // Otherwise, compare seconds
+            return t1.tv_sec < t2.tv_sec;
+        }
+    }
+};
+
+// The third parameter here causes all entries to be sorted by access time,
+// so oldest will be accessed first in applyLRU
+using MultifileEntryStatsMap =
+        std::multimap<struct timespec, MultifileEntryStats, MultifileTimeLess>;
+using MultifileEntryStatsMapIter = MultifileEntryStatsMap::iterator;
+#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+
 class MultifileBlobCache {
 public:
     MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize,
@@ -115,6 +139,7 @@
 
     size_t getTotalSize() const { return mTotalCacheSize; }
     size_t getTotalEntries() const { return mTotalCacheEntries; }
+    size_t getTotalCacheSizeDivisor() const { return mTotalCacheSizeDivisor; }
 
     const std::string& getCurrentBuildId() const { return mBuildId; }
     void setCurrentBuildId(const std::string& buildId) { mBuildId = buildId; }
@@ -124,10 +149,11 @@
 
 private:
     void trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
-                    time_t accessTime);
+                    const timespec& accessTime);
     bool contains(uint32_t entryHash) const;
     bool removeEntry(uint32_t entryHash);
     MultifileEntryStats getEntryStats(uint32_t entryHash);
+    void updateEntryTime(uint32_t entryHash, const timespec& newTime);
 
     bool createStatus(const std::string& baseDir);
     bool checkStatus(const std::string& baseDir);
@@ -151,8 +177,14 @@
     std::string mBuildId;
     uint32_t mCacheVersion;
 
+#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+    std::unordered_map<uint32_t, MultifileEntryStatsMapIter> mEntries;
+    MultifileEntryStatsMap mEntryStats;
+#else
     std::unordered_set<uint32_t> mEntries;
     std::unordered_map<uint32_t, MultifileEntryStats> mEntryStats;
+#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
+
     std::unordered_map<uint32_t, MultifileHotCache> mHotCache;
 
     size_t mMaxKeySize;
@@ -161,6 +193,7 @@
     size_t mMaxTotalEntries;
     size_t mTotalCacheSize;
     size_t mTotalCacheEntries;
+    size_t mTotalCacheSizeDivisor;
     size_t mHotCacheLimit;
     size_t mHotCacheEntryLimit;
     size_t mHotCacheSize;
diff --git a/opengl/libs/EGL/MultifileBlobCache_test.cpp b/opengl/libs/EGL/MultifileBlobCache_test.cpp
index 90a0f1e..85fb29e 100644
--- a/opengl/libs/EGL/MultifileBlobCache_test.cpp
+++ b/opengl/libs/EGL/MultifileBlobCache_test.cpp
@@ -21,10 +21,15 @@
 #include <fcntl.h>
 #include <gtest/gtest.h>
 #include <stdio.h>
+#include <utils/JenkinsHash.h>
 
 #include <fstream>
 #include <memory>
 
+#include <com_android_graphics_egl_flags.h>
+
+using namespace com::android::graphics::egl;
+
 using namespace std::literals;
 
 namespace android {
@@ -55,6 +60,7 @@
     std::vector<std::string> getCacheEntries();
 
     void clearProperties();
+    bool clearCache();
 
     std::unique_ptr<TemporaryFile> mTempFile;
     std::unique_ptr<MultifileBlobCache> mMBC;
@@ -314,7 +320,7 @@
 
     struct stat info;
     if (stat(multifileDirName.c_str(), &info) == 0) {
-        // We have a multifile dir. Skip the status file and return the only entry.
+        // We have a multifile dir. Skip the status file and return the entries.
         DIR* dir;
         struct dirent* entry;
         if ((dir = opendir(multifileDirName.c_str())) != nullptr) {
@@ -325,6 +331,7 @@
                 if (strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) {
                     continue;
                 }
+                // printf("Found entry: %s\n", entry->d_name);
                 cacheEntries.push_back(multifileDirName + "/" + entry->d_name);
             }
         } else {
@@ -458,6 +465,8 @@
     // Set one entry
     mMBC->set("abcd", 4, "efgh", 4);
 
+    uint32_t initialCacheVersion = mMBC->getCurrentCacheVersion();
+
     // Close the cache so everything writes out
     mMBC->finish();
     mMBC.reset();
@@ -466,7 +475,7 @@
     ASSERT_EQ(getCacheEntries().size(), 1);
 
     // Set a debug cacheVersion
-    std::string newCacheVersion = std::to_string(kMultifileBlobCacheVersion + 1);
+    std::string newCacheVersion = std::to_string(initialCacheVersion + 1);
     ASSERT_TRUE(base::SetProperty("debug.egl.blobcache.cache_version", newCacheVersion.c_str()));
     ASSERT_TRUE(
             base::WaitForProperty("debug.egl.blobcache.cache_version", newCacheVersion.c_str()));
@@ -503,4 +512,404 @@
     ASSERT_EQ(getCacheEntries().size(), 0);
 }
 
+// Ensure cache is correct when a key is reused
+TEST_F(MultifileBlobCacheTest, SameKeyDifferentValues) {
+    if (!flags::multifile_blobcache_advanced_usage()) {
+        GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
+    }
+
+    unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
+
+    size_t startingSize = mMBC->getTotalSize();
+
+    // New cache should be empty
+    ASSERT_EQ(startingSize, 0);
+
+    // Set an initial value
+    mMBC->set("ab", 2, "cdef", 4);
+
+    // Grab the new size
+    size_t firstSize = mMBC->getTotalSize();
+
+    // Ensure the size went up
+    // Note: Checking for an exact size is challenging, as the
+    // file size can differ between platforms.
+    ASSERT_GT(firstSize, startingSize);
+
+    // Verify the cache is correct
+    ASSERT_EQ(size_t(4), mMBC->get("ab", 2, buf, 4));
+    ASSERT_EQ('c', buf[0]);
+    ASSERT_EQ('d', buf[1]);
+    ASSERT_EQ('e', buf[2]);
+    ASSERT_EQ('f', buf[3]);
+
+    // Now reuse the key with a smaller value
+    mMBC->set("ab", 2, "gh", 2);
+
+    // Grab the new size
+    size_t secondSize = mMBC->getTotalSize();
+
+    // Ensure it decreased in size
+    ASSERT_LT(secondSize, firstSize);
+
+    // Verify the cache is correct
+    ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2));
+    ASSERT_EQ('g', buf[0]);
+    ASSERT_EQ('h', buf[1]);
+
+    // Now put back the original value
+    mMBC->set("ab", 2, "cdef", 4);
+
+    // And we should get back a stable size
+    size_t finalSize = mMBC->getTotalSize();
+    ASSERT_EQ(firstSize, finalSize);
+}
+
+// Ensure cache is correct when a key is reused with large value size
+TEST_F(MultifileBlobCacheTest, SameKeyLargeValues) {
+    if (!flags::multifile_blobcache_advanced_usage()) {
+        GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
+    }
+
+    // Create the cache with larger limits to stress test reuse
+    constexpr uint32_t kLocalMaxKeySize = 1 * 1024 * 1024;
+    constexpr uint32_t kLocalMaxValueSize = 4 * 1024 * 1024;
+    constexpr uint32_t kLocalMaxTotalSize = 32 * 1024 * 1024;
+    mMBC.reset(new MultifileBlobCache(kLocalMaxKeySize, kLocalMaxValueSize, kLocalMaxTotalSize,
+                                      kMaxTotalEntries, &mTempFile->path[0]));
+
+    constexpr uint32_t kLargeValueCount = 8;
+    constexpr uint32_t kLargeValueSize = 64 * 1024;
+
+    // Create a several really large values
+    unsigned char largeValue[kLargeValueCount][kLargeValueSize];
+    for (int i = 0; i < kLargeValueCount; i++) {
+        for (int j = 0; j < kLargeValueSize; j++) {
+            // Fill the value with the index for uniqueness
+            largeValue[i][j] = i;
+        }
+    }
+
+    size_t startingSize = mMBC->getTotalSize();
+
+    // New cache should be empty
+    ASSERT_EQ(startingSize, 0);
+
+    // Cycle through the values and set them all in sequence
+    for (int i = 0; i < kLargeValueCount; i++) {
+        mMBC->set("abcd", 4, largeValue[i], kLargeValueSize);
+    }
+
+    // Ensure we get the last one back
+    unsigned char outBuf[kLargeValueSize];
+    mMBC->get("abcd", 4, outBuf, kLargeValueSize);
+
+    for (int i = 0; i < kLargeValueSize; i++) {
+        // Buffer should contain highest index value
+        ASSERT_EQ(kLargeValueCount - 1, outBuf[i]);
+    }
+}
+
+// Ensure cache eviction is LRU
+TEST_F(MultifileBlobCacheTest, CacheEvictionIsLRU) {
+    if (!flags::multifile_blobcache_advanced_usage()) {
+        GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
+    }
+
+    // Fill the cache with exactly how much it can hold
+    int entry = 0;
+    for (entry = 0; entry < kMaxTotalEntries; entry++) {
+        // Use the index as the key and value
+        mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
+
+        int result = 0;
+        ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+        ASSERT_EQ(entry, result);
+    }
+
+    // Ensure the cache is full
+    ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries);
+
+    // Add one more entry to trigger eviction
+    size_t overflowEntry = kMaxTotalEntries;
+    mMBC->set(&overflowEntry, sizeof(overflowEntry), &overflowEntry, sizeof(overflowEntry));
+
+    // Verify it contains the right amount, which will be one more than reduced size
+    // because we evict the cache before adding a new entry
+    size_t evictionLimit = kMaxTotalEntries / mMBC->getTotalCacheSizeDivisor();
+    ASSERT_EQ(mMBC->getTotalEntries(), evictionLimit + 1);
+
+    // Ensure cache is as expected, with old entries removed, newer entries remaining
+    for (entry = 0; entry < kMaxTotalEntries; entry++) {
+        int result = 0;
+        mMBC->get(&entry, sizeof(entry), &result, sizeof(result));
+
+        if (entry < evictionLimit) {
+            // We should get no hits on evicted entries, i.e. the first added
+            ASSERT_EQ(result, 0);
+        } else {
+            // Above the limit should still be present
+            ASSERT_EQ(result, entry);
+        }
+    }
+}
+
+// Ensure calling GET on an entry updates its access time, even if already in hotcache
+TEST_F(MultifileBlobCacheTest, GetUpdatesAccessTime) {
+    if (!flags::multifile_blobcache_advanced_usage()) {
+        GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
+    }
+
+    // Fill the cache with exactly how much it can hold
+    int entry = 0;
+    int result = 0;
+    for (entry = 0; entry < kMaxTotalEntries; entry++) {
+        // Use the index as the key and value
+        mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
+        ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+        ASSERT_EQ(entry, result);
+    }
+
+    // Ensure the cache is full
+    ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries);
+
+    // GET the first few entries to update their access time
+    std::vector<int> accessedEntries = {1, 2, 3};
+    for (int i = 0; i < accessedEntries.size(); i++) {
+        entry = accessedEntries[i];
+        ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+    }
+
+    // Add one more entry to trigger eviction
+    size_t overflowEntry = kMaxTotalEntries;
+    mMBC->set(&overflowEntry, sizeof(overflowEntry), &overflowEntry, sizeof(overflowEntry));
+
+    size_t evictionLimit = kMaxTotalEntries / mMBC->getTotalCacheSizeDivisor();
+
+    // Ensure cache is as expected, with old entries removed, newer entries remaining
+    for (entry = 0; entry < kMaxTotalEntries; entry++) {
+        int result = 0;
+        mMBC->get(&entry, sizeof(entry), &result, sizeof(result));
+
+        if (std::find(accessedEntries.begin(), accessedEntries.end(), entry) !=
+            accessedEntries.end()) {
+            // If this is one of the handful we accessed after filling the cache,
+            // they should still be in the cache because LRU
+            ASSERT_EQ(result, entry);
+        } else if (entry >= (evictionLimit + accessedEntries.size())) {
+            // If they were above the eviction limit (plus three for our updated entries),
+            // they should still be present
+            ASSERT_EQ(result, entry);
+        } else {
+            // Otherwise, they shold be evicted and no longer present
+            ASSERT_EQ(result, 0);
+        }
+    }
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Open the cache again
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
+
+    // Check the cache again, ensuring the updated access time made it to disk
+    for (entry = 0; entry < kMaxTotalEntries; entry++) {
+        int result = 0;
+        mMBC->get(&entry, sizeof(entry), &result, sizeof(result));
+        if (std::find(accessedEntries.begin(), accessedEntries.end(), entry) !=
+            accessedEntries.end()) {
+            ASSERT_EQ(result, entry);
+        } else if (entry >= (evictionLimit + accessedEntries.size())) {
+            ASSERT_EQ(result, entry);
+        } else {
+            ASSERT_EQ(result, 0);
+        }
+    }
+}
+
+bool MultifileBlobCacheTest::clearCache() {
+    std::string cachePath = &mTempFile->path[0];
+    std::string multifileDirName = cachePath + ".multifile";
+
+    DIR* dir = opendir(multifileDirName.c_str());
+    if (dir == nullptr) {
+        printf("Error opening directory: %s\n", multifileDirName.c_str());
+        return false;
+    }
+
+    struct dirent* entry;
+    while ((entry = readdir(dir)) != nullptr) {
+        // Skip "." and ".." entries
+        if (std::string(entry->d_name) == "." || std::string(entry->d_name) == "..") {
+            continue;
+        }
+
+        std::string entryPath = multifileDirName + "/" + entry->d_name;
+
+        // Delete the entry (we assert it's a file, nothing nested here)
+        if (unlink(entryPath.c_str()) != 0) {
+            printf("Error deleting file: %s\n", entryPath.c_str());
+            closedir(dir);
+            return false;
+        }
+    }
+
+    closedir(dir);
+
+    // Delete the empty directory itself
+    if (rmdir(multifileDirName.c_str()) != 0) {
+        printf("Error deleting directory %s, error %s\n", multifileDirName.c_str(),
+               std::strerror(errno));
+        return false;
+    }
+
+    return true;
+}
+
+// Recover from lost cache in the case of app clearing it
+TEST_F(MultifileBlobCacheTest, RecoverFromLostCache) {
+    if (!flags::multifile_blobcache_advanced_usage()) {
+        GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
+    }
+
+    int entry = 0;
+    int result = 0;
+
+    uint32_t kEntryCount = 10;
+
+    // Add some entries
+    for (entry = 0; entry < kEntryCount; entry++) {
+        mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
+        ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+        ASSERT_EQ(entry, result);
+    }
+
+    // For testing, wait until the entries have completed writing
+    mMBC->finish();
+
+    // Manually delete the cache!
+    ASSERT_TRUE(clearCache());
+
+    // Cache should not contain any entries
+    for (entry = 0; entry < kEntryCount; entry++) {
+        ASSERT_EQ(size_t(0), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+    }
+
+    // Ensure we can still add new ones
+    for (entry = kEntryCount; entry < kEntryCount * 2; entry++) {
+        mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
+        ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+        ASSERT_EQ(entry, result);
+    }
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Open the cache again
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
+
+    // Before fixes, writing the second entries to disk should have failed due to missing
+    // cache dir.  But now they should have survived our shutdown above.
+    for (entry = kEntryCount; entry < kEntryCount * 2; entry++) {
+        ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+        ASSERT_EQ(entry, result);
+    }
+}
+
+// Ensure cache eviction succeeds if the cache is deleted
+TEST_F(MultifileBlobCacheTest, EvictAfterLostCache) {
+    if (!flags::multifile_blobcache_advanced_usage()) {
+        GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
+    }
+
+    int entry = 0;
+    int result = 0;
+
+    uint32_t kEntryCount = 10;
+
+    // Add some entries
+    for (entry = 0; entry < kEntryCount; entry++) {
+        mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
+        ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+        ASSERT_EQ(entry, result);
+    }
+
+    // For testing, wait until the entries have completed writing
+    mMBC->finish();
+
+    // Manually delete the cache!
+    ASSERT_TRUE(clearCache());
+
+    // Now start adding entries to trigger eviction, cache should survive
+    for (entry = kEntryCount; entry < 2 * kMaxTotalEntries; entry++) {
+        mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
+        ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+        ASSERT_EQ(entry, result);
+    }
+
+    // We should have triggered multiple evictions above and remain at or below the
+    // max amount of entries
+    ASSERT_LE(getCacheEntries().size(), kMaxTotalEntries);
+}
+
+// Remove from cache when size is zero
+TEST_F(MultifileBlobCacheTest, ZeroSizeRemovesEntry) {
+    if (!flags::multifile_blobcache_advanced_usage()) {
+        GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
+    }
+
+    // Put some entries in
+    int entry = 0;
+    int result = 0;
+
+    uint32_t kEntryCount = 20;
+
+    // Add some entries
+    for (entry = 0; entry < kEntryCount; entry++) {
+        mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
+        ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+        ASSERT_EQ(entry, result);
+    }
+
+    // Send some of them again with size zero
+    std::vector<int> removedEntries = {5, 10, 18};
+    for (int i = 0; i < removedEntries.size(); i++) {
+        entry = removedEntries[i];
+        mMBC->set(&entry, sizeof(entry), nullptr, 0);
+    }
+
+    // Ensure they do not get a hit
+    for (int i = 0; i < removedEntries.size(); i++) {
+        entry = removedEntries[i];
+        ASSERT_EQ(size_t(0), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+    }
+
+    // And have been removed from disk
+    std::vector<std::string> diskEntries = getCacheEntries();
+    ASSERT_EQ(diskEntries.size(), kEntryCount - removedEntries.size());
+    for (int i = 0; i < removedEntries.size(); i++) {
+        entry = removedEntries[i];
+        // Generate a hash for our removed entries and ensure they are not contained
+        // Note our entry and key and the same here, so we're hashing the key just like
+        // the multifile blobcache does.
+        uint32_t entryHash =
+                android::JenkinsHashMixBytes(0, reinterpret_cast<uint8_t*>(&entry), sizeof(entry));
+        ASSERT_EQ(std::find(diskEntries.begin(), diskEntries.end(), std::to_string(entryHash)),
+                  diskEntries.end());
+    }
+
+    // Ensure the others are still present
+    for (entry = 0; entry < kEntryCount; entry++) {
+        if (std::find(removedEntries.begin(), removedEntries.end(), entry) ==
+            removedEntries.end()) {
+            ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+            ASSERT_EQ(result, entry);
+        }
+    }
+}
+
 } // namespace android
diff --git a/opengl/libs/EGL/egl_flags.aconfig b/opengl/libs/EGL/egl_flags.aconfig
new file mode 100644
index 0000000..1157970
--- /dev/null
+++ b/opengl/libs/EGL/egl_flags.aconfig
@@ -0,0 +1,13 @@
+package: "com.android.graphics.egl.flags"
+container: "system"
+
+flag {
+  name: "multifile_blobcache_advanced_usage"
+  namespace: "gpu"
+  description: "This flag controls new behaviors to address bugs found via advanced usage"
+  bug: "380483358"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/opengl/libs/EGL/fuzzer/Android.bp b/opengl/libs/EGL/fuzzer/Android.bp
index 4947e5f..fe5f2a6 100644
--- a/opengl/libs/EGL/fuzzer/Android.bp
+++ b/opengl/libs/EGL/fuzzer/Android.bp
@@ -37,6 +37,7 @@
     ],
 
     shared_libs: [
+        "libegl_flags",
         "libz",
     ],
 
diff --git a/opengl/tests/EGLTest/Android.bp b/opengl/tests/EGLTest/Android.bp
index aebd3f2..ed46efd 100644
--- a/opengl/tests/EGLTest/Android.bp
+++ b/opengl/tests/EGLTest/Android.bp
@@ -26,6 +26,7 @@
         "android.hardware.configstore@1.0",
         "android.hardware.configstore-utils",
         "libEGL",
+        "libegl_flags",
         "libbase",
         "libcutils",
         "libbinder",
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 38e5974..811692f 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -64,9 +64,8 @@
              !isFromSource(sources, AINPUT_SOURCE_STYLUS));
 }
 
-inline void notifyPointerDisplayChange(
-        std::optional<std::tuple<ui::LogicalDisplayId, FloatPoint>> change,
-        PointerChoreographerPolicyInterface& policy) {
+inline void notifyPointerDisplayChange(std::optional<std::tuple<ui::LogicalDisplayId, vec2>> change,
+                                       PointerChoreographerPolicyInterface& policy) {
     if (!change) {
         return;
     }
@@ -205,20 +204,30 @@
 }
 
 NotifyMotionArgs PointerChoreographer::processMotion(const NotifyMotionArgs& args) {
-    std::scoped_lock _l(getLock());
+    NotifyMotionArgs newArgs(args);
+    PointerDisplayChange pointerDisplayChange;
+    { // acquire lock
+        std::scoped_lock _l(getLock());
+        if (isFromMouse(args)) {
+            newArgs = processMouseEventLocked(args);
+            pointerDisplayChange = calculatePointerDisplayChangeToNotify();
+        } else if (isFromTouchpad(args)) {
+            newArgs = processTouchpadEventLocked(args);
+            pointerDisplayChange = calculatePointerDisplayChangeToNotify();
+        } else if (isFromDrawingTablet(args)) {
+            processDrawingTabletEventLocked(args);
+        } else if (mStylusPointerIconEnabled && isStylusHoverEvent(args)) {
+            processStylusHoverEventLocked(args);
+        } else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) {
+            processTouchscreenAndStylusEventLocked(args);
+        }
+    } // release lock
 
-    if (isFromMouse(args)) {
-        return processMouseEventLocked(args);
-    } else if (isFromTouchpad(args)) {
-        return processTouchpadEventLocked(args);
-    } else if (isFromDrawingTablet(args)) {
-        processDrawingTabletEventLocked(args);
-    } else if (mStylusPointerIconEnabled && isStylusHoverEvent(args)) {
-        processStylusHoverEventLocked(args);
-    } else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) {
-        processTouchscreenAndStylusEventLocked(args);
+    if (pointerDisplayChange) {
+        // pointer display may have changed if mouse crossed display boundary
+        notifyPointerDisplayChange(pointerDisplayChange, mPolicy);
     }
-    return args;
+    return newArgs;
 }
 
 NotifyMotionArgs PointerChoreographer::processMouseEventLocked(const NotifyMotionArgs& args) {
@@ -235,9 +244,9 @@
     if (MotionEvent::isValidCursorPosition(args.xCursorPosition, args.yCursorPosition)) {
         // This is an absolute mouse device that knows about the location of the cursor on the
         // display, so set the cursor position to the specified location.
-        const auto [x, y] = pc.getPosition();
-        const float deltaX = args.xCursorPosition - x;
-        const float deltaY = args.yCursorPosition - y;
+        const auto position = pc.getPosition();
+        const float deltaX = args.xCursorPosition - position.x;
+        const float deltaY = args.yCursorPosition - position.y;
         newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
         newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY);
         pc.setPosition(args.xCursorPosition, args.yCursorPosition);
@@ -245,7 +254,8 @@
         // This is a relative mouse, so move the cursor by the specified amount.
         processPointerDeviceMotionEventLocked(/*byref*/ newArgs, /*byref*/ pc);
     }
-    if (canUnfadeOnDisplay(displayId)) {
+    // Note displayId may have changed if the cursor moved to a different display
+    if (canUnfadeOnDisplay(newArgs.displayId)) {
         pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
     }
     return newArgs;
@@ -262,17 +272,19 @@
         processPointerDeviceMotionEventLocked(/*byref*/ newArgs, /*byref*/ pc);
     } else {
         // This is a trackpad gesture with fake finger(s) that should not move the mouse pointer.
-        const auto [x, y] = pc.getPosition();
+        const auto position = pc.getPosition();
         for (uint32_t i = 0; i < newArgs.getPointerCount(); i++) {
             newArgs.pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X,
-                                                  args.pointerCoords[i].getX() + x);
+                                                  args.pointerCoords[i].getX() + position.x);
             newArgs.pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y,
-                                                  args.pointerCoords[i].getY() + y);
+                                                  args.pointerCoords[i].getY() + position.y);
         }
-        newArgs.xCursorPosition = x;
-        newArgs.yCursorPosition = y;
+        newArgs.xCursorPosition = position.x;
+        newArgs.yCursorPosition = position.y;
     }
-    if (canUnfadeOnDisplay(displayId)) {
+
+    // Note displayId may have changed if the cursor moved to a different display
+    if (canUnfadeOnDisplay(newArgs.displayId)) {
         pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
     }
     return newArgs;
@@ -283,12 +295,104 @@
     const float deltaX = newArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
     const float deltaY = newArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
 
-    pc.move(deltaX, deltaY);
-    const auto [x, y] = pc.getPosition();
-    newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
-    newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
-    newArgs.xCursorPosition = x;
-    newArgs.yCursorPosition = y;
+    vec2 unconsumedDelta = pc.move(deltaX, deltaY);
+    if (com::android::input::flags::connected_displays_cursor() &&
+        (std::abs(unconsumedDelta.x) > 0 || std::abs(unconsumedDelta.y) > 0)) {
+        handleUnconsumedDeltaLocked(pc, unconsumedDelta);
+        // pointer may have moved to a different viewport
+        newArgs.displayId = pc.getDisplayId();
+    }
+
+    const auto position = pc.getPosition();
+    newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, position.x);
+    newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, position.y);
+    newArgs.xCursorPosition = position.x;
+    newArgs.yCursorPosition = position.y;
+}
+
+void PointerChoreographer::handleUnconsumedDeltaLocked(PointerControllerInterface& pc,
+                                                       const vec2& unconsumedDelta) {
+    // Display topology is in rotated coordinate space and Pointer controller returns and expects
+    // values in the un-rotated coordinate space. So we need to transform delta and cursor position
+    // back to the rotated coordinate space to lookup adjacent display in the display topology.
+    const auto& sourceDisplayTransform = pc.getDisplayTransform();
+    const vec2 rotatedUnconsumedDelta =
+            transformWithoutTranslation(sourceDisplayTransform, unconsumedDelta);
+    const vec2 cursorPosition = pc.getPosition();
+    const vec2 rotatedCursorPosition = sourceDisplayTransform.transform(cursorPosition);
+
+    // To find out the boundary that cursor is crossing we are checking delta in x and y direction
+    // respectively. This prioritizes x direction over y.
+    // In practise, majority of cases we only have non-zero values in either x or y coordinates,
+    // except sometimes near the corners.
+    // In these cases this behaviour is not noticeable. We also do not apply unconsumed delta on
+    // the destination display for the same reason.
+    DisplayPosition sourceBoundary;
+    float cursorOffset = 0.0f;
+    if (rotatedUnconsumedDelta.x > 0) {
+        sourceBoundary = DisplayPosition::RIGHT;
+        cursorOffset = rotatedCursorPosition.y;
+    } else if (rotatedUnconsumedDelta.x < 0) {
+        sourceBoundary = DisplayPosition::LEFT;
+        cursorOffset = rotatedCursorPosition.y;
+    } else if (rotatedUnconsumedDelta.y > 0) {
+        sourceBoundary = DisplayPosition::BOTTOM;
+        cursorOffset = rotatedCursorPosition.x;
+    } else {
+        sourceBoundary = DisplayPosition::TOP;
+        cursorOffset = rotatedCursorPosition.x;
+    }
+
+    const ui::LogicalDisplayId sourceDisplayId = pc.getDisplayId();
+    std::optional<std::pair<const DisplayViewport*, float /*offset*/>> destination =
+            findDestinationDisplayLocked(sourceDisplayId, sourceBoundary, cursorOffset);
+    if (!destination.has_value()) {
+        // No matching adjacent display
+        return;
+    }
+
+    const DisplayViewport& destinationViewport = *destination->first;
+    const float destinationOffset = destination->second;
+    if (mMousePointersByDisplay.find(destinationViewport.displayId) !=
+        mMousePointersByDisplay.end()) {
+        LOG(FATAL) << "A cursor already exists on destination display"
+                   << destinationViewport.displayId;
+    }
+    mDefaultMouseDisplayId = destinationViewport.displayId;
+    auto pcNode = mMousePointersByDisplay.extract(sourceDisplayId);
+    pcNode.key() = destinationViewport.displayId;
+    mMousePointersByDisplay.insert(std::move(pcNode));
+
+    // Before updating the viewport and moving the cursor to appropriate location in the destination
+    // viewport, we need to temporarily hide the cursor. This will prevent it from appearing at the
+    // center of the display in any intermediate frames.
+    pc.fade(PointerControllerInterface::Transition::IMMEDIATE);
+    pc.setDisplayViewport(destinationViewport);
+    vec2 destinationPosition =
+            calculateDestinationPosition(destinationViewport, cursorOffset - destinationOffset,
+                                         sourceBoundary);
+
+    // Transform position back to un-rotated coordinate space before sending it to controller
+    destinationPosition = pc.getDisplayTransform().inverse().transform(destinationPosition.x,
+                                                                       destinationPosition.y);
+    pc.setPosition(destinationPosition.x, destinationPosition.y);
+    pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+}
+
+vec2 PointerChoreographer::calculateDestinationPosition(const DisplayViewport& destinationViewport,
+                                                        float pointerOffset,
+                                                        DisplayPosition sourceBoundary) {
+    // destination is opposite of the source boundary
+    switch (sourceBoundary) {
+        case DisplayPosition::RIGHT:
+            return {0, pointerOffset}; // left edge
+        case DisplayPosition::TOP:
+            return {pointerOffset, destinationViewport.logicalBottom}; // bottom edge
+        case DisplayPosition::LEFT:
+            return {destinationViewport.logicalRight, pointerOffset}; // right edge
+        case DisplayPosition::BOTTOM:
+            return {pointerOffset, 0}; // top edge
+    }
 }
 
 void PointerChoreographer::processDrawingTabletEventLocked(const android::NotifyMotionArgs& args) {
@@ -436,7 +540,8 @@
 }
 
 void PointerChoreographer::onControllerAddedOrRemovedLocked() {
-    if (!com::android::input::flags::hide_pointer_indicators_for_secure_windows()) {
+    if (!com::android::input::flags::hide_pointer_indicators_for_secure_windows() &&
+        !com::android::input::flags::connected_displays_cursor()) {
         return;
     }
     bool requireListener = !mTouchPointersByDevice.empty() || !mMousePointersByDisplay.empty() ||
@@ -502,6 +607,13 @@
     mNextListener.notify(args);
 }
 
+void PointerChoreographer::setDisplayTopology(
+        const std::unordered_map<ui::LogicalDisplayId, std::vector<AdjacentDisplay>>&
+                displayTopology) {
+    std::scoped_lock _l(getLock());
+    mTopology = displayTopology;
+}
+
 void PointerChoreographer::dump(std::string& dump) {
     std::scoped_lock _l(getLock());
 
@@ -654,7 +766,7 @@
 PointerChoreographer::PointerDisplayChange
 PointerChoreographer::calculatePointerDisplayChangeToNotify() {
     ui::LogicalDisplayId displayIdToNotify = ui::LogicalDisplayId::INVALID;
-    FloatPoint cursorPosition = {0, 0};
+    vec2 cursorPosition = {0, 0};
     if (const auto it = mMousePointersByDisplay.find(mDefaultMouseDisplayId);
         it != mMousePointersByDisplay.end()) {
         const auto& pointerController = it->second;
@@ -725,7 +837,7 @@
     return std::nullopt;
 }
 
-FloatPoint PointerChoreographer::getMouseCursorPosition(ui::LogicalDisplayId displayId) {
+vec2 PointerChoreographer::getMouseCursorPosition(ui::LogicalDisplayId displayId) {
     std::scoped_lock _l(getLock());
     const ui::LogicalDisplayId resolvedDisplayId = getTargetMouseDisplayLocked(displayId);
     if (auto it = mMousePointersByDisplay.find(resolvedDisplayId);
@@ -873,6 +985,97 @@
     return ConstructorDelegate(std::move(ctor));
 }
 
+void PointerChoreographer::populateFakeDisplayTopologyLocked(
+        const std::vector<gui::DisplayInfo>& displayInfos) {
+    if (!com::android::input::flags::connected_displays_cursor()) {
+        return;
+    }
+
+    if (displayInfos.size() == mTopology.size()) {
+        bool displaysChanged = false;
+        for (const auto& displayInfo : displayInfos) {
+            if (mTopology.find(displayInfo.displayId) == mTopology.end()) {
+                displaysChanged = true;
+                break;
+            }
+        }
+
+        if (!displaysChanged) {
+            return;
+        }
+    }
+
+    // create a fake topology assuming following order
+    // default-display (top-edge) -> next-display (right-edge) -> next-display (right-edge) ...
+    // This also adds a 100px offset on corresponding edge for better manual testing
+    //   ┌────────┐
+    //   │ next   ├─────────┐
+    // ┌─└───────┐┤ next 2  │ ...
+    // │ default │└─────────┘
+    // └─────────┘
+    mTopology.clear();
+
+    // treat default display as base, in real topology it should be the primary-display
+    ui::LogicalDisplayId previousDisplay = ui::LogicalDisplayId::DEFAULT;
+    for (const auto& displayInfo : displayInfos) {
+        if (displayInfo.displayId == ui::LogicalDisplayId::DEFAULT) {
+            continue;
+        }
+        if (previousDisplay == ui::LogicalDisplayId::DEFAULT) {
+            mTopology[previousDisplay].push_back(
+                    {displayInfo.displayId, DisplayPosition::TOP, 100});
+            mTopology[displayInfo.displayId].push_back(
+                    {previousDisplay, DisplayPosition::BOTTOM, -100});
+        } else {
+            mTopology[previousDisplay].push_back(
+                    {displayInfo.displayId, DisplayPosition::RIGHT, 100});
+            mTopology[displayInfo.displayId].push_back(
+                    {previousDisplay, DisplayPosition::LEFT, -100});
+        }
+        previousDisplay = displayInfo.displayId;
+    }
+
+    // update default pointer display. In real topology it should be the primary-display
+    if (mTopology.find(mDefaultMouseDisplayId) == mTopology.end()) {
+        mDefaultMouseDisplayId = ui::LogicalDisplayId::DEFAULT;
+    }
+}
+
+std::optional<std::pair<const DisplayViewport*, float /*offset*/>>
+PointerChoreographer::findDestinationDisplayLocked(const ui::LogicalDisplayId sourceDisplayId,
+                                                   const DisplayPosition sourceBoundary,
+                                                   float cursorOffset) const {
+    const auto& sourceNode = mTopology.find(sourceDisplayId);
+    if (sourceNode == mTopology.end()) {
+        // Topology is likely out of sync with viewport info, wait for it to be updated
+        LOG(WARNING) << "Source display missing from topology " << sourceDisplayId;
+        return std::nullopt;
+    }
+    for (const AdjacentDisplay& adjacentDisplay : sourceNode->second) {
+        if (adjacentDisplay.position != sourceBoundary) {
+            continue;
+        }
+        const DisplayViewport* destinationViewport =
+                findViewportByIdLocked(adjacentDisplay.displayId);
+        if (destinationViewport == nullptr) {
+            // Topology is likely out of sync with viewport info, wait for them to be updated
+            LOG(WARNING) << "Cannot find viewport for adjacent display "
+                         << adjacentDisplay.displayId << "of source display " << sourceDisplayId;
+            continue;
+        }
+        // target position must be within target display boundary
+        const int32_t edgeSize =
+                sourceBoundary == DisplayPosition::TOP || sourceBoundary == DisplayPosition::BOTTOM
+                ? (destinationViewport->logicalRight - destinationViewport->logicalLeft)
+                : (destinationViewport->logicalBottom - destinationViewport->logicalTop);
+        if (cursorOffset >= adjacentDisplay.offsetPx &&
+            cursorOffset <= adjacentDisplay.offsetPx + edgeSize) {
+            return std::make_pair(destinationViewport, adjacentDisplay.offsetPx);
+        }
+    }
+    return std::nullopt;
+}
+
 // --- PointerChoreographer::PointerChoreographerDisplayInfoListener ---
 
 void PointerChoreographer::PointerChoreographerDisplayInfoListener::onWindowInfosChanged(
@@ -883,12 +1086,14 @@
     }
     auto newPrivacySensitiveDisplays =
             getPrivacySensitiveDisplaysFromWindowInfos(windowInfosUpdate.windowInfos);
+
+    // PointerChoreographer uses Listener's lock.
+    base::ScopedLockAssertion assumeLocked(mPointerChoreographer->getLock());
     if (newPrivacySensitiveDisplays != mPrivacySensitiveDisplays) {
         mPrivacySensitiveDisplays = std::move(newPrivacySensitiveDisplays);
-        // PointerChoreographer uses Listener's lock.
-        base::ScopedLockAssertion assumeLocked(mPointerChoreographer->getLock());
         mPointerChoreographer->onPrivacySensitiveDisplaysChangedLocked(mPrivacySensitiveDisplays);
     }
+    mPointerChoreographer->populateFakeDisplayTopologyLocked(windowInfosUpdate.displayInfos);
 }
 
 void PointerChoreographer::PointerChoreographerDisplayInfoListener::setInitialDisplayInfosLocked(
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index fba1aef..939529f 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -59,7 +59,7 @@
     virtual void setDisplayViewports(const std::vector<DisplayViewport>& viewports) = 0;
     virtual std::optional<DisplayViewport> getViewportForPointerDevice(
             ui::LogicalDisplayId associatedDisplayId = ui::LogicalDisplayId::INVALID) = 0;
-    virtual FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId) = 0;
+    virtual vec2 getMouseCursorPosition(ui::LogicalDisplayId displayId) = 0;
     virtual void setShowTouchesEnabled(bool enabled) = 0;
     virtual void setStylusPointerIconEnabled(bool enabled) = 0;
     /**
@@ -96,7 +96,7 @@
     void setDisplayViewports(const std::vector<DisplayViewport>& viewports) override;
     std::optional<DisplayViewport> getViewportForPointerDevice(
             ui::LogicalDisplayId associatedDisplayId) override;
-    FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId) override;
+    vec2 getMouseCursorPosition(ui::LogicalDisplayId displayId) override;
     void setShowTouchesEnabled(bool enabled) override;
     void setStylusPointerIconEnabled(bool enabled) override;
     bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
@@ -113,11 +113,29 @@
     void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
     void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
 
+    // TODO(b/362719483) remove these when real topology is available
+    enum class DisplayPosition {
+        RIGHT,
+        TOP,
+        LEFT,
+        BOTTOM,
+        ftl_last = BOTTOM,
+    };
+
+    struct AdjacentDisplay {
+        ui::LogicalDisplayId displayId;
+        DisplayPosition position;
+        float offsetPx;
+    };
+    void setDisplayTopology(
+            const std::unordered_map<ui::LogicalDisplayId, std::vector<AdjacentDisplay>>&
+                    displayTopology);
+
     void dump(std::string& dump) override;
 
 private:
-    using PointerDisplayChange = std::optional<
-            std::tuple<ui::LogicalDisplayId /*displayId*/, FloatPoint /*cursorPosition*/>>;
+    using PointerDisplayChange =
+            std::optional<std::tuple<ui::LogicalDisplayId /*displayId*/, vec2 /*cursorPosition*/>>;
 
     // PointerChoreographer's DisplayInfoListener can outlive the PointerChoreographer because when
     // the listener is registered and called from display thread, a strong pointer to the listener
@@ -153,6 +171,22 @@
             const std::unordered_set<ui::LogicalDisplayId>& privacySensitiveDisplays)
             REQUIRES(getLock());
 
+    void handleUnconsumedDeltaLocked(PointerControllerInterface& pc, const vec2& unconsumedDelta)
+            REQUIRES(getLock());
+
+    void populateFakeDisplayTopologyLocked(const std::vector<gui::DisplayInfo>& displayInfos)
+            REQUIRES(getLock());
+
+    std::optional<std::pair<const DisplayViewport*, float /*offset*/>> findDestinationDisplayLocked(
+            const ui::LogicalDisplayId sourceDisplayId, const DisplayPosition sourceBoundary,
+            float cursorOffset) const REQUIRES(getLock());
+
+    static vec2 calculateDestinationPosition(const DisplayViewport& destinationViewport,
+                                             float pointerOffset, DisplayPosition sourceBoundary);
+
+    std::unordered_map<ui::LogicalDisplayId, std::vector<AdjacentDisplay>> mTopology
+            GUARDED_BY(getLock());
+
     /* This listener keeps tracks of visible privacy sensitive displays and updates the
      * choreographer if there are any changes.
      *
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index 10fec74..43eaf67 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -35,109 +35,34 @@
       ]
     },
     {
-      "name": "CtsHardwareTestCases",
-      "options": [
-        {
-          "include-filter": "android.hardware.input.cts.tests"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsHardwareTestCases_cts_tests"
     },
     {
       "name": "CtsInputTestCases"
     },
     {
-      "name": "CtsViewTestCases",
-      "options": [
-        {
-          "include-filter": "android.view.cts.input"
-        }
-      ]
+      "name": "CtsViewTestCases_cts_input"
     },
     {
-      "name": "CtsViewTestCases",
-      "options": [
-        {
-          "include-filter": "android.view.cts.HoverTest"
-        },
-        {
-          "include-filter": "android.view.cts.MotionEventTest"
-        },
-        {
-          "include-filter": "android.view.cts.PointerCaptureTest"
-        },
-        {
-          "include-filter": "android.view.cts.TooltipTest"
-        },
-        {
-          "include-filter": "android.view.cts.TouchDelegateTest"
-        },
-        {
-          "include-filter": "android.view.cts.VerifyInputEventTest"
-        },
-        {
-          "include-filter": "android.view.cts.ViewTest"
-        },
-        {
-          "include-filter": "android.view.cts.ViewUnbufferedTest"
-        }
-      ]
+      "name": "CtsViewTestCases_input_related"
     },
     {
-      "name": "CtsWidgetTestCases",
-      "options": [
-        {
-          "include-filter": "android.widget.cts.NumberPickerTest"
-        },
-        {
-          "include-filter": "android.widget.cts.SeekBarTest"
-        }
-      ]
+      "name": "CtsWidgetTestCases_seekbar_and_numberpicker"
     },
     {
-      "name": "FrameworksCoreTests",
-      "options": [
-        {
-          "include-filter": "android.hardware.input"
-        }
-      ]
+      "name": "FrameworksCoreTests_hardware_input"
     },
     {
-      "name": "FrameworksCoreTests",
-      "options": [
-        {
-          "include-filter": "android.view.VerifiedKeyEventTest"
-        },
-        {
-          "include-filter": "android.view.VerifiedMotionEventTest"
-        }
-      ]
+      "name": "FrameworksCoreTests_view_verified"
     },
     {
-      "name": "CtsAppTestCases",
-      "options": [
-        {
-          "include-filter": "android.app.cts.ToolbarActionBarTest"
-        }
-      ]
+      "name": "CtsAppTestCases_cts_toolbaractionbartest"
     },
     {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.input"
-        }
-      ]
+      "name": "FrameworksServicesTests_android_server_input"
     },
     {
-      "name": "CtsSecurityTestCases",
-      "options": [
-        {
-          "include-filter": "android.security.cts.MotionEventTest"
-        }
-      ]
+      "name": "CtsSecurityTestCases_cts_motioneventtest"
     },
     {
       "name": "CtsSecurityBulletinHostTestCases",
@@ -156,12 +81,7 @@
   ],
   "postsubmit": [
     {
-      "name": "CtsWindowManagerDeviceWindow",
-      "options": [
-        {
-          "include-filter": "android.server.wm.window.WindowInputTests"
-        }
-      ]
+      "name": "CtsWindowManagerDeviceWindow_window_windowinputtests"
     },
     {
       "name": "libinput_tests"
@@ -187,98 +107,31 @@
       ]
     },
     {
-      "name": "CtsHardwareTestCases",
-      "options": [
-        {
-          "include-filter": "android.hardware.input.cts.tests"
-        }
-      ]
+      "name": "CtsHardwareTestCases_cts_tests"
     },
     {
       "name": "CtsInputTestCases"
     },
     {
-      "name": "CtsViewTestCases",
-      "options": [
-        {
-          "include-filter": "android.view.cts.input"
-        }
-      ]
+      "name": "CtsViewTestCases_cts_input"
     },
     {
-      "name": "CtsViewTestCases",
-      "options": [
-        {
-          "include-filter": "android.view.cts.HoverTest"
-        },
-        {
-          "include-filter": "android.view.cts.MotionEventTest"
-        },
-        {
-          "include-filter": "android.view.cts.PointerCaptureTest"
-        },
-        {
-          "include-filter": "android.view.cts.TooltipTest"
-        },
-        {
-          "include-filter": "android.view.cts.TouchDelegateTest"
-        },
-        {
-          "include-filter": "android.view.cts.VerifyInputEventTest"
-        },
-        {
-          "include-filter": "android.view.cts.ViewTest"
-        },
-        {
-          "include-filter": "android.view.cts.ViewUnbufferedTest"
-        }
-      ]
+      "name": "CtsViewTestCases_input_related"
     },
     {
-      "name": "CtsWidgetTestCases",
-      "options": [
-        {
-          "include-filter": "android.widget.cts.NumberPickerTest"
-        },
-        {
-          "include-filter": "android.widget.cts.SeekBarTest"
-        }
-      ]
+      "name": "CtsWidgetTestCases_seekbar_and_numberpicker"
     },
     {
-      "name": "FrameworksCoreTests",
-      "options": [
-        {
-          "include-filter": "android.view.VerifiedKeyEventTest"
-        },
-        {
-          "include-filter": "android.view.VerifiedMotionEventTest"
-        }
-      ]
+      "name": "FrameworksCoreTests_view_verified"
     },
     {
-      "name": "CtsAppTestCases",
-      "options": [
-        {
-          "include-filter": "android.app.cts.ToolbarActionBarTest"
-        }
-      ]
+      "name": "CtsAppTestCases_cts_toolbaractionbartest"
     },
     {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.input"
-        }
-      ]
+      "name": "FrameworksServicesTests_server_input"
     },
     {
-      "name": "CtsSecurityTestCases",
-      "options": [
-        {
-          "include-filter": "android.security.cts.MotionEventTest"
-        }
-      ]
+      "name": "CtsSecurityTestCases_cts_motioneventtest"
     },
     {
       "name": "CtsSecurityBulletinHostTestCases",
diff --git a/services/inputflinger/dispatcher/EventLogTags.logtags b/services/inputflinger/dispatcher/EventLogTags.logtags
index 2c5fe21..4dd6073 100644
--- a/services/inputflinger/dispatcher/EventLogTags.logtags
+++ b/services/inputflinger/dispatcher/EventLogTags.logtags
@@ -31,7 +31,7 @@
 # 6: Percent
 # Default value for data of type int/long is 2 (bytes).
 #
-# See system/core/logcat/event.logtags for the master copy of the tags.
+# See system/logging/logcat/event.logtags for the master copy of the tags.
 
 # 62000 - 62199 reserved for inputflinger
 
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 580cde3..4d6b6c7 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -245,6 +245,9 @@
     // True to use three-finger tap as a customizable shortcut; false to use it as a middle-click.
     bool touchpadThreeFingerTapShortcutEnabled;
 
+    // True to enable system gestures (three- and four-finger swipes) on touchpads.
+    bool touchpadSystemGesturesEnabled;
+
     // The set of currently disabled input devices.
     std::set<int32_t> disabledDevices;
 
@@ -297,6 +300,7 @@
             shouldNotifyTouchpadHardwareState(false),
             touchpadRightClickZoneEnabled(false),
             touchpadThreeFingerTapShortcutEnabled(false),
+            touchpadSystemGesturesEnabled(true),
             stylusButtonMotionEventsEnabled(true),
             stylusPointerIconEnabled(false),
             mouseReverseVerticalScrollingEnabled(false),
diff --git a/services/inputflinger/include/PointerChoreographerPolicyInterface.h b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
index e1f8fda..36614b2 100644
--- a/services/inputflinger/include/PointerChoreographerPolicyInterface.h
+++ b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
@@ -54,7 +54,7 @@
      * @param position The new position of the mouse cursor on the logical display
      */
     virtual void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId,
-                                               const FloatPoint& position) = 0;
+                                               const vec2& position) = 0;
 
     /* Returns true if any InputConnection is currently active. */
     virtual bool isInputMethodConnectionActive() = 0;
diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h
index 8f3d9ca..bd46490 100644
--- a/services/inputflinger/include/PointerControllerInterface.h
+++ b/services/inputflinger/include/PointerControllerInterface.h
@@ -24,20 +24,6 @@
 
 struct SpriteIcon;
 
-struct FloatPoint {
-    float x;
-    float y;
-
-    inline FloatPoint(float x, float y) : x(x), y(y) {}
-
-    inline explicit FloatPoint(vec2 p) : x(p.x), y(p.y) {}
-
-    template <typename T, typename U>
-    operator std::tuple<T, U>() {
-        return {x, y};
-    }
-};
-
 /**
  * Interface for tracking a mouse / touch pad pointer and touch pad spots.
  *
@@ -72,14 +58,18 @@
     /* Dumps the state of the pointer controller. */
     virtual std::string dump() = 0;
 
-    /* Move the pointer. */
-    virtual void move(float deltaX, float deltaY) = 0;
+    /* Move the pointer and return unconsumed delta if the pointer has crossed the current
+     * viewport bounds.
+     *
+     * Return value may be used to move pointer to corresponding adjacent display, if it exists in
+     * the display-topology */
+    [[nodiscard]] virtual vec2 move(float deltaX, float deltaY) = 0;
 
     /* Sets the absolute location of the pointer. */
     virtual void setPosition(float x, float y) = 0;
 
     /* Gets the absolute location of the pointer. */
-    virtual FloatPoint getPosition() const = 0;
+    virtual vec2 getPosition() const = 0;
 
     enum class Transition {
         // Fade/unfade immediately.
@@ -145,6 +135,8 @@
 
     /* Resets the flag to skip screenshot of the pointer indicators for all displays. */
     virtual void clearSkipScreenshotFlags() = 0;
+
+    virtual ui::Transform getDisplayTransform() const = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/controller/PeripheralController.cpp b/services/inputflinger/reader/controller/PeripheralController.cpp
index 9eeb2b2..7434ae4 100644
--- a/services/inputflinger/reader/controller/PeripheralController.cpp
+++ b/services/inputflinger/reader/controller/PeripheralController.cpp
@@ -47,10 +47,6 @@
     return (brightness & 0xff) << 24 | (red & 0xff) << 16 | (green & 0xff) << 8 | (blue & 0xff);
 }
 
-static inline bool isKeyboardBacklightCustomLevelsEnabled() {
-    return sysprop::InputProperties::enable_keyboard_backlight_custom_levels().value_or(true);
-}
-
 /**
  * Input controller owned by InputReader device, implements the native API for querying input
  * lights, getting and setting the lights brightness and color, by interacting with EventHub
@@ -289,8 +285,7 @@
 std::set<BrightnessLevel> PeripheralController::getPreferredBrightnessLevels(
         const Light* light) const {
     std::set<BrightnessLevel> levels;
-    if (!isKeyboardBacklightCustomLevelsEnabled() ||
-        light->type != InputDeviceLightType::KEYBOARD_BACKLIGHT) {
+    if (light->type != InputDeviceLightType::KEYBOARD_BACKLIGHT) {
         return levels;
     }
     std::optional<std::string> keyboardBacklightLevels =
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index ca8266b..0c094e6 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -351,6 +351,7 @@
 
         bumpGeneration();
     }
+    std::list<NotifyArgs> out;
     if (!changes.any() || changes.test(InputReaderConfiguration::Change::TOUCHPAD_SETTINGS)) {
         mPropertyProvider.getProperty("Use Custom Touchpad Pointer Accel Curve")
                 .setBoolValues({true});
@@ -375,11 +376,11 @@
         mPropertyProvider.getProperty("Button Right Click Zone Enable")
                 .setBoolValues({config.touchpadRightClickZoneEnabled});
         mTouchpadHardwareStateNotificationsEnabled = config.shouldNotifyTouchpadHardwareState;
-
         mGestureConverter.setThreeFingerTapShortcutEnabled(
                 config.touchpadThreeFingerTapShortcutEnabled);
+        out += mGestureConverter.setEnableSystemGestures(when,
+                                                         config.touchpadSystemGesturesEnabled);
     }
-    std::list<NotifyArgs> out;
     if ((!changes.any() && config.pointerCaptureRequest.isEnable()) ||
         changes.test(InputReaderConfiguration::Change::POINTER_CAPTURE)) {
         mPointerCaptured = config.pointerCaptureRequest.isEnable();
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index 54270eb..6bd949a 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -132,6 +132,15 @@
     return out;
 }
 
+std::list<NotifyArgs> GestureConverter::setEnableSystemGestures(nsecs_t when, bool enable) {
+    std::list<NotifyArgs> out;
+    if (!enable && mCurrentClassification == MotionClassification::MULTI_FINGER_SWIPE) {
+        out += handleMultiFingerSwipeLift(when, when);
+    }
+    mEnableSystemGestures = enable;
+    return out;
+}
+
 void GestureConverter::populateMotionRanges(InputDeviceInfo& info) const {
     info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, SOURCE, 0.0f, 1.0f, 0, 0, 0);
 
@@ -461,6 +470,9 @@
                                                                              uint32_t fingerCount,
                                                                              float dx, float dy) {
     std::list<NotifyArgs> out = {};
+    if (!mEnableSystemGestures) {
+        return out;
+    }
 
     if (mCurrentClassification != MotionClassification::MULTI_FINGER_SWIPE) {
         // If the user changes the number of fingers mid-way through a swipe (e.g. they start with
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
index ad40721..8d92ead 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
@@ -59,6 +59,8 @@
         mThreeFingerTapShortcutEnabled = enabled;
     }
 
+    [[nodiscard]] std::list<NotifyArgs> setEnableSystemGestures(nsecs_t when, bool enable);
+
     void populateMotionRanges(InputDeviceInfo& info) const;
 
     [[nodiscard]] std::list<NotifyArgs> handleGesture(nsecs_t when, nsecs_t readTime,
@@ -104,6 +106,7 @@
     InputReaderContext& mReaderContext;
     const bool mEnableFlingStop;
     const bool mEnableNoFocusChange;
+    bool mEnableSystemGestures{true};
 
     bool mThreeFingerTapShortcutEnabled;
 
diff --git a/services/inputflinger/rust/sticky_keys_filter.rs b/services/inputflinger/rust/sticky_keys_filter.rs
index 7a1d0ec..3c7f432 100644
--- a/services/inputflinger/rust/sticky_keys_filter.rs
+++ b/services/inputflinger/rust/sticky_keys_filter.rs
@@ -47,6 +47,7 @@
     next: Box<dyn Filter + Send + Sync>,
     listener: ModifierStateListener,
     data: Data,
+    down_key_map: HashMap<i32, HashSet<i32>>,
 }
 
 #[derive(Default)]
@@ -69,15 +70,34 @@
         next: Box<dyn Filter + Send + Sync>,
         listener: ModifierStateListener,
     ) -> StickyKeysFilter {
-        Self { next, listener, data: Default::default() }
+        Self { next, listener, data: Default::default(), down_key_map: HashMap::new() }
     }
 }
 
 impl Filter for StickyKeysFilter {
     fn notify_key(&mut self, event: &KeyEvent) {
+        let down = event.action == KeyEventAction::DOWN;
         let up = event.action == KeyEventAction::UP;
         let mut modifier_state = self.data.modifier_state;
         let mut locked_modifier_state = self.data.locked_modifier_state;
+        if down {
+            let down_keys = self.down_key_map.entry(event.deviceId).or_default();
+            down_keys.insert(event.keyCode);
+        } else {
+            if !self.down_key_map.contains_key(&event.deviceId) {
+                self.next.notify_key(event);
+                return;
+            }
+            let down_keys = self.down_key_map.get_mut(&event.deviceId).unwrap();
+            if !down_keys.contains(&event.keyCode) {
+                self.next.notify_key(event);
+                return;
+            }
+            down_keys.remove(&event.keyCode);
+            if down_keys.is_empty() {
+                self.down_key_map.remove(&event.deviceId);
+            }
+        }
         if !is_ephemeral_modifier_key(event.keyCode) {
             // If non-ephemeral modifier key (i.e. non-modifier keys + toggle modifier keys like
             // CAPS_LOCK, NUM_LOCK etc.), don't block key and pass in the sticky modifier state with
@@ -130,6 +150,7 @@
             self.data.locked_modifier_state = ModifierState::None;
             self.listener.modifier_state_changed(ModifierState::None, ModifierState::None);
         }
+        self.down_key_map.retain(|key, _| device_infos.iter().any(|x| *key == x.deviceId));
         self.next.notify_devices_changed(device_infos);
     }
 
@@ -166,6 +187,7 @@
         result += &format!("\tmodifier_state = {:?}\n", self.data.modifier_state);
         result += &format!("\tlocked_modifier_state = {:?}\n", self.data.locked_modifier_state);
         result += &format!("\tcontributing_devices = {:?}\n", self.data.contributing_devices);
+        result += &format!("\tdown_key_map = {:?}\n", self.down_key_map);
         self.next.dump(dump_str + &result)
     }
 }
@@ -322,6 +344,31 @@
     }
 
     #[test]
+    fn test_notify_key_passes_ephemeral_modifier_keys_if_only_key_up_occurs() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        let key_codes = &[
+            KEYCODE_ALT_LEFT,
+            KEYCODE_ALT_RIGHT,
+            KEYCODE_CTRL_LEFT,
+            KEYCODE_CTRL_RIGHT,
+            KEYCODE_SHIFT_LEFT,
+            KEYCODE_SHIFT_RIGHT,
+            KEYCODE_META_LEFT,
+            KEYCODE_META_RIGHT,
+        ];
+        for key_code in key_codes.iter() {
+            let event = KeyEvent { keyCode: *key_code, ..BASE_KEY_UP };
+            sticky_keys_filter.notify_key(&event);
+            assert_eq!(test_filter.last_event().unwrap(), event);
+        }
+    }
+
+    #[test]
     fn test_notify_key_passes_non_ephemeral_modifier_keys() {
         let test_filter = TestFilter::new();
         let test_callbacks = TestCallbacks::new();
@@ -437,6 +484,26 @@
     }
 
     #[test]
+    fn test_modifier_state_unchanged_on_non_modifier_key_up_without_down() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_UP });
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP });
+
+        assert_eq!(
+            test_callbacks.get_last_modifier_state(),
+            ModifierState::CtrlLeftOn | ModifierState::CtrlOn
+        );
+        assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None);
+    }
+
+    #[test]
     fn test_locked_modifier_state_not_cleared_on_non_modifier_key_press() {
         let test_filter = TestFilter::new();
         let test_callbacks = TestCallbacks::new();
diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp
index 887a939..f033e57 100644
--- a/services/inputflinger/tests/FakePointerController.cpp
+++ b/services/inputflinger/tests/FakePointerController.cpp
@@ -43,7 +43,7 @@
     mY = y;
 }
 
-FloatPoint FakePointerController::getPosition() const {
+vec2 FakePointerController::getPosition() const {
     if (!mEnabled) {
         return {0, 0};
     }
@@ -96,9 +96,9 @@
 }
 
 void FakePointerController::assertPosition(float x, float y) {
-    const auto [actualX, actualY] = getPosition();
-    ASSERT_NEAR(x, actualX, 1);
-    ASSERT_NEAR(y, actualY, 1);
+    const auto actual = getPosition();
+    ASSERT_NEAR(x, actual.x, 1);
+    ASSERT_NEAR(y, actual.y, 1);
 }
 
 void FakePointerController::assertSpotCount(ui::LogicalDisplayId displayId, int32_t count) {
@@ -148,15 +148,20 @@
     return mIsPointerShown;
 }
 
-void FakePointerController::move(float deltaX, float deltaY) {
-    if (!mEnabled) return;
+vec2 FakePointerController::move(float deltaX, float deltaY) {
+    if (!mEnabled) return {0, 0};
 
     mX += deltaX;
+    mY += deltaY;
+
+    const vec2 position(mX, mY);
+
     if (mX < mMinX) mX = mMinX;
     if (mX > mMaxX) mX = mMaxX;
-    mY += deltaY;
     if (mY < mMinY) mY = mMinY;
     if (mY > mMaxY) mY = mMaxY;
+
+    return {position.x - mX, position.y - mY};
 }
 
 void FakePointerController::fade(Transition) {
@@ -190,4 +195,8 @@
     mSpotsByDisplay.clear();
 }
 
+ui::Transform FakePointerController::getDisplayTransform() const {
+    return ui::Transform();
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h
index 9b773a7..c526bb8 100644
--- a/services/inputflinger/tests/FakePointerController.h
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -40,7 +40,7 @@
     const std::map<ui::LogicalDisplayId, std::vector<int32_t>>& getSpots();
 
     void setPosition(float x, float y) override;
-    FloatPoint getPosition() const override;
+    vec2 getPosition() const override;
     ui::LogicalDisplayId getDisplayId() const override;
     void setDisplayViewport(const DisplayViewport& viewport) override;
     void updatePointerIcon(PointerIconStyle iconId) override;
@@ -48,6 +48,7 @@
     void setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) override;
     void clearSkipScreenshotFlags() override;
     void fade(Transition) override;
+    ui::Transform getDisplayTransform() const override;
 
     void assertViewportSet(ui::LogicalDisplayId displayId);
     void assertViewportNotSet();
@@ -65,7 +66,7 @@
 
 private:
     std::string dump() override { return ""; }
-    void move(float deltaX, float deltaY) override;
+    vec2 move(float deltaX, float deltaY) override;
     void unfade(Transition) override;
     void setPresentation(Presentation) override {}
     void setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits,
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index fad8f05..fe40a5e 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -48,6 +48,7 @@
 using testing::AllOf;
 using testing::Each;
 using testing::ElementsAre;
+using testing::IsEmpty;
 using testing::VariantWith;
 
 class GestureConverterTest : public testing::Test {
@@ -849,6 +850,107 @@
                               WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
+TEST_F(GestureConverterTest, DisablingSystemGestures_IgnoresMultiFingerSwipe) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
+
+    std::list<NotifyArgs> args = converter.setEnableSystemGestures(ARBITRARY_TIME, false);
+    ASSERT_THAT(args, IsEmpty());
+
+    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
+                         /*dy=*/10);
+    Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
+                            /*dy=*/5);
+    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+
+    args += converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+    args += converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
+    args += converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
+    ASSERT_THAT(args, IsEmpty());
+
+    args = converter.setEnableSystemGestures(ARBITRARY_TIME, true);
+    ASSERT_THAT(args, IsEmpty());
+
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_DOWN)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(
+                                    AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                    1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT)),
+                            VariantWith<NotifyMotionArgs>(WithMotionAction(
+                                    AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                    2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_MOVE))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE))));
+}
+
+TEST_F(GestureConverterTest, DisablingSystemGestures_EndsOngoingMultiFingerSwipe) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
+
+    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
+                         /*dy=*/10);
+    std::list<NotifyArgs> args;
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+    ASSERT_FALSE(args.empty());
+
+    // Disabling system gestures should end the swipe early.
+    args = converter.setEnableSystemGestures(ARBITRARY_TIME, false);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(3u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(2u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithGestureOffset(0, 0, EPSILON),
+                                          WithMotionClassification(
+                                                  MotionClassification::MULTI_FINGER_SWIPE),
+                                          WithPointerCount(1u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                          WithMotionClassification(MotionClassification::NONE)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
+
+    // Further movement in the same swipe should be ignored.
+    Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
+                            /*dy=*/5);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
+    ASSERT_THAT(args, IsEmpty());
+    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
+    ASSERT_THAT(args, IsEmpty());
+
+    // But single-finger pointer motion should be reported.
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                              WithRelativeMotion(-5, 10), WithButtonState(0)))));
+}
+
 TEST_F(GestureConverterTest, Pinch_Inwards) {
     input_flags::enable_touchpad_no_focus_change(true);
 
diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h
index 6f7c2e5..ac616d0 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -188,7 +188,7 @@
     MOCK_METHOD(std::shared_ptr<PointerControllerInterface>, createPointerController,
                 (PointerControllerInterface::ControllerType), (override));
     MOCK_METHOD(void, notifyPointerDisplayIdChanged,
-                (ui::LogicalDisplayId displayId, const FloatPoint& position), (override));
+                (ui::LogicalDisplayId displayId, const vec2& position), (override));
     MOCK_METHOD(bool, isInputMethodConnectionActive, (), (override));
     MOCK_METHOD(void, notifyMouseCursorFadedOnTyping, (), (override));
 };
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 411c7ba..27da3d3 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -135,7 +135,7 @@
         });
 
         ON_CALL(mMockPolicy, notifyPointerDisplayIdChanged)
-                .WillByDefault([this](ui::LogicalDisplayId displayId, const FloatPoint& position) {
+                .WillByDefault([this](ui::LogicalDisplayId displayId, const vec2& position) {
                     mPointerDisplayIdNotified = displayId;
                 });
     }
@@ -2601,6 +2601,173 @@
     metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_META_RIGHT);
 }
 
+using PointerChoreographerDisplayTopologyTestFixtureParam =
+        std::tuple<std::string_view /*name*/, int32_t /*source device*/,
+                   ControllerType /*PointerController*/, ToolType /*pointer tool type*/,
+                   vec2 /*source position*/, vec2 /*hover move X/Y*/,
+                   ui::LogicalDisplayId /*destination display*/, vec2 /*destination position*/>;
+
+class PointerChoreographerDisplayTopologyTestFixture
+      : public PointerChoreographerTest,
+        public testing::WithParamInterface<PointerChoreographerDisplayTopologyTestFixtureParam> {
+public:
+    static constexpr ui::LogicalDisplayId DISPLAY_CENTER_ID = ui::LogicalDisplayId{10};
+    static constexpr ui::LogicalDisplayId DISPLAY_TOP_ID = ui::LogicalDisplayId{20};
+    static constexpr ui::LogicalDisplayId DISPLAY_RIGHT_ID = ui::LogicalDisplayId{30};
+    static constexpr ui::LogicalDisplayId DISPLAY_BOTTOM_ID = ui::LogicalDisplayId{40};
+    static constexpr ui::LogicalDisplayId DISPLAY_LEFT_ID = ui::LogicalDisplayId{50};
+    static constexpr ui::LogicalDisplayId DISPLAY_TOP_RIGHT_CORNER_ID = ui::LogicalDisplayId{60};
+
+    PointerChoreographerDisplayTopologyTestFixture() {
+        com::android::input::flags::connected_displays_cursor(true);
+    }
+
+protected:
+    std::vector<DisplayViewport> mViewports{
+            createViewport(DISPLAY_CENTER_ID, /*width*/ 100, /*height*/ 100, ui::ROTATION_0),
+            createViewport(DISPLAY_TOP_ID, /*width*/ 90, /*height*/ 90, ui::ROTATION_0),
+            createViewport(DISPLAY_RIGHT_ID, /*width*/ 90, /*height*/ 90, ui::ROTATION_90),
+            createViewport(DISPLAY_BOTTOM_ID, /*width*/ 90, /*height*/ 90, ui::ROTATION_180),
+            createViewport(DISPLAY_LEFT_ID, /*width*/ 90, /*height*/ 90, ui::ROTATION_270),
+            createViewport(DISPLAY_TOP_RIGHT_CORNER_ID, /*width*/ 90, /*height*/ 90,
+                           ui::ROTATION_0),
+    };
+
+    std::unordered_map<ui::LogicalDisplayId, std::vector<PointerChoreographer::AdjacentDisplay>>
+            mTopology{
+                    {DISPLAY_CENTER_ID,
+                     {{DISPLAY_TOP_ID, PointerChoreographer::DisplayPosition::TOP, 10.0f},
+                      {DISPLAY_RIGHT_ID, PointerChoreographer::DisplayPosition::RIGHT, 10.0f},
+                      {DISPLAY_BOTTOM_ID, PointerChoreographer::DisplayPosition::BOTTOM, 10.0f},
+                      {DISPLAY_LEFT_ID, PointerChoreographer::DisplayPosition::LEFT, 10.0f},
+                      {DISPLAY_TOP_RIGHT_CORNER_ID, PointerChoreographer::DisplayPosition::RIGHT,
+                       -90.0f}}},
+            };
+
+private:
+    DisplayViewport createViewport(ui::LogicalDisplayId displayId, int32_t width, int32_t height,
+                                   ui::Rotation orientation) {
+        DisplayViewport viewport;
+        viewport.displayId = displayId;
+        viewport.logicalRight = width;
+        viewport.logicalBottom = height;
+        viewport.orientation = orientation;
+        return viewport;
+    }
+};
+
+TEST_P(PointerChoreographerDisplayTopologyTestFixture, PointerChoreographerDisplayTopologyTest) {
+    const auto& [_, device, pointerControllerType, pointerToolType, initialPosition, hoverMove,
+                 destinationDisplay, destinationPosition] = GetParam();
+
+    mChoreographer.setDisplayViewports(mViewports);
+    mChoreographer.setDefaultMouseDisplayId(
+            PointerChoreographerDisplayTopologyTestFixture::DISPLAY_CENTER_ID);
+    mChoreographer.setDisplayTopology(mTopology);
+
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, device, ui::LogicalDisplayId::INVALID)}});
+
+    auto pc = assertPointerControllerCreated(pointerControllerType);
+    ASSERT_EQ(PointerChoreographerDisplayTopologyTestFixture::DISPLAY_CENTER_ID,
+              pc->getDisplayId());
+
+    // Set initial position of the PointerController.
+    pc->setPosition(initialPosition.x, initialPosition.y);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Make NotifyMotionArgs and notify Choreographer.
+    auto pointerBuilder = PointerBuilder(/*id=*/0, pointerToolType)
+                                  .axis(AMOTION_EVENT_AXIS_RELATIVE_X, hoverMove.x)
+                                  .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, hoverMove.y);
+
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, device)
+                                        .pointer(pointerBuilder)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(ui::LogicalDisplayId::INVALID)
+                                        .build());
+
+    // Check that the PointerController updated the position and the pointer is shown.
+    ASSERT_TRUE(pc->isPointerShown());
+    ASSERT_EQ(pc->getDisplayId(), destinationDisplay);
+    auto position = pc->getPosition();
+    ASSERT_EQ(position.x, destinationPosition.x);
+    ASSERT_EQ(position.y, destinationPosition.y);
+
+    // Check that x-y coordinates, displayId and cursor position are correctly updated.
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithCoords(destinationPosition.x, destinationPosition.y),
+                  WithDisplayId(destinationDisplay),
+                  WithCursorPosition(destinationPosition.x, destinationPosition.y)));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        PointerChoreographerTest, PointerChoreographerDisplayTopologyTestFixture,
+        testing::Values(
+                // Note: Upon viewport transition cursor will be positioned at the boundary of the
+                // destination, as we drop any unconsumed delta.
+                std::make_tuple("UnchangedDisplay", AINPUT_SOURCE_MOUSE, ControllerType::MOUSE,
+                                ToolType::MOUSE, vec2(50, 50) /* initial x/y */,
+                                vec2(25, 25) /* delta x/y */,
+                                PointerChoreographerDisplayTopologyTestFixture::DISPLAY_CENTER_ID,
+                                vec2(75, 75) /* destination x/y */),
+                std::make_tuple("TransitionToRightDisplay", AINPUT_SOURCE_MOUSE,
+                                ControllerType::MOUSE, ToolType::MOUSE,
+                                vec2(50, 50) /* initial x/y */, vec2(100, 25) /* delta x/y */,
+                                PointerChoreographerDisplayTopologyTestFixture::DISPLAY_RIGHT_ID,
+                                vec2(0,
+                                     50 + 25 - 10) /* Left edge: (0, source + delta - offset) */),
+                std::make_tuple(
+                        "TransitionToLeftDisplay", AINPUT_SOURCE_MOUSE, ControllerType::MOUSE,
+                        ToolType::MOUSE, vec2(50, 50) /* initial x/y */,
+                        vec2(-100, 25) /* delta x/y */,
+                        PointerChoreographerDisplayTopologyTestFixture::DISPLAY_LEFT_ID,
+                        vec2(90, 50 + 25 - 10) /* Right edge: (width, source + delta - offset*/),
+                std::make_tuple("TransitionToTopDisplay",
+                                AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, ControllerType::MOUSE,
+                                ToolType::FINGER, vec2(50, 50) /* initial x/y */,
+                                vec2(25, -100) /* delta x/y */,
+                                PointerChoreographerDisplayTopologyTestFixture::DISPLAY_TOP_ID,
+                                vec2(50 + 25 - 10,
+                                     90) /* Bottom edge: (source + delta - offset, height) */),
+                std::make_tuple("TransitionToBottomDisplay",
+                                AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, ControllerType::MOUSE,
+                                ToolType::FINGER, vec2(50, 50) /* initial x/y */,
+                                vec2(25, 100) /* delta x/y */,
+                                PointerChoreographerDisplayTopologyTestFixture::DISPLAY_BOTTOM_ID,
+                                vec2(50 + 25 - 10, 0) /* Top edge: (source + delta - offset, 0) */),
+                std::make_tuple("NoTransitionAtTopOffset", AINPUT_SOURCE_MOUSE,
+                                ControllerType::MOUSE, ToolType::MOUSE,
+                                vec2(5, 50) /* initial x/y */, vec2(0, -100) /* Move Up */,
+                                PointerChoreographerDisplayTopologyTestFixture::DISPLAY_CENTER_ID,
+                                vec2(5, 0) /* Top edge */),
+                std::make_tuple("NoTransitionAtRightOffset", AINPUT_SOURCE_MOUSE,
+                                ControllerType::MOUSE, ToolType::MOUSE,
+                                vec2(95, 5) /* initial x/y */, vec2(100, 0) /* Move Right */,
+                                PointerChoreographerDisplayTopologyTestFixture::DISPLAY_CENTER_ID,
+                                vec2(99, 5) /* Top edge */),
+                std::make_tuple("NoTransitionAtBottomOffset",
+                                AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, ControllerType::MOUSE,
+                                ToolType::FINGER, vec2(5, 95) /* initial x/y */,
+                                vec2(0, 100) /* Move Down */,
+                                PointerChoreographerDisplayTopologyTestFixture::DISPLAY_CENTER_ID,
+                                vec2(5, 99) /* Bottom edge */),
+                std::make_tuple("NoTransitionAtLeftOffset",
+                                AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, ControllerType::MOUSE,
+                                ToolType::FINGER, vec2(5, 5) /* initial x/y */,
+                                vec2(-100, 0) /* Move Left */,
+                                PointerChoreographerDisplayTopologyTestFixture::DISPLAY_CENTER_ID,
+                                vec2(0, 5) /* Left edge */),
+                std::make_tuple(
+                        "TransitionAtTopRightCorner", AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+                        ControllerType::MOUSE, ToolType::FINGER, vec2(95, 5) /* initial x/y */,
+                        vec2(10, -10) /* Move dignally to top right corner */,
+                        PointerChoreographerDisplayTopologyTestFixture::DISPLAY_TOP_RIGHT_CORNER_ID,
+                        vec2(0, 90) /* bottom left corner */)),
+        [](const testing::TestParamInfo<PointerChoreographerDisplayTopologyTestFixtureParam>& p) {
+            return std::string{std::get<0>(p.param)};
+        });
+
 class PointerChoreographerWindowInfoListenerTest : public testing::Test {};
 
 TEST_F_WITH_FLAGS(
diff --git a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
index 71d3d1f..682d1f4 100644
--- a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
+++ b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
@@ -69,10 +69,6 @@
                 (const CpuHeadroomParams& params, CpuHeadroomResult* headroom), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getGpuHeadroom,
                 (const GpuHeadroomParams& params, GpuHeadroomResult* headroom), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, getCpuHeadroomMinIntervalMillis, (int64_t* interval),
-                (override));
-    MOCK_METHOD(ndk::ScopedAStatus, getGpuHeadroomMinIntervalMillis, (int64_t* interval),
-                (override));
     MOCK_METHOD(ndk::ScopedAStatus, sendCompositionData,
                 (const std::vector<CompositionData>& in_data), (override));
     MOCK_METHOD(ndk::ScopedAStatus, sendCompositionUpdate,
diff --git a/services/sensorservice/Android.bp b/services/sensorservice/Android.bp
index 7b2596a..8c80dd8 100644
--- a/services/sensorservice/Android.bp
+++ b/services/sensorservice/Android.bp
@@ -92,7 +92,7 @@
         "libaidlcommonsupport",
         "android.hardware.sensors@1.0-convert",
         "android.hardware.sensors-V1-convert",
-        "android.hardware.sensors-V2-ndk",
+        "android.hardware.sensors-V3-ndk",
         "sensorservice_flags_c_lib",
     ],
 
diff --git a/services/sensorservice/aidl/Android.bp b/services/sensorservice/aidl/Android.bp
index 542fcae..b9a3491 100644
--- a/services/sensorservice/aidl/Android.bp
+++ b/services/sensorservice/aidl/Android.bp
@@ -28,7 +28,7 @@
         "libbinder_ndk",
         "libsensor",
         "android.frameworks.sensorservice-V1-ndk",
-        "android.hardware.sensors-V2-ndk",
+        "android.hardware.sensors-V3-ndk",
     ],
     export_include_dirs: [
         "include/",
diff --git a/services/sensorservice/aidl/fuzzer/Android.bp b/services/sensorservice/aidl/fuzzer/Android.bp
index b2dc89b..880df08 100644
--- a/services/sensorservice/aidl/fuzzer/Android.bp
+++ b/services/sensorservice/aidl/fuzzer/Android.bp
@@ -20,7 +20,7 @@
         "android.companion.virtual.virtualdevice_aidl-cpp",
         "android.frameworks.sensorservice-V1-ndk",
         "android.hardware.sensors-V1-convert",
-        "android.hardware.sensors-V2-ndk",
+        "android.hardware.sensors-V3-ndk",
         "android.hardware.common-V2-ndk",
         "libsensor",
         "libfakeservicemanager",
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index 143c192..a040c88 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -589,30 +589,28 @@
 
 void OutputLayer::writeLutToHWC(HWC2::Layer* hwcLayer,
                                 const LayerFECompositionState& outputIndependentState) {
-    Luts luts;
-    // if outputIndependentState.luts is nullptr, it means we want to clear the LUTs
-    // and we pass an empty Luts object to the HWC.
-    if (outputIndependentState.luts) {
-        auto& lutFileDescriptor = outputIndependentState.luts->getLutFileDescriptor();
-        auto lutOffsets = outputIndependentState.luts->offsets;
-        auto& lutProperties = outputIndependentState.luts->lutProperties;
-
-        std::vector<LutProperties> aidlProperties;
-        aidlProperties.reserve(lutProperties.size());
-        for (size_t i = 0; i < lutOffsets.size(); i++) {
-            LutProperties properties;
-            properties.dimension = static_cast<LutProperties::Dimension>(lutProperties[i].dimension);
-            properties.size = lutProperties[i].size;
-            properties.samplingKeys = {
-                    static_cast<LutProperties::SamplingKey>(lutProperties[i].samplingKey)};
-            aidlProperties.emplace_back(properties);
-        }
-
-
-        luts.pfd = ndk::ScopedFileDescriptor(dup(lutFileDescriptor.get()));
-        luts.offsets = lutOffsets;
-        luts.lutProperties = std::move(aidlProperties);
+    if (!outputIndependentState.luts) {
+        return;
     }
+    auto& lutFileDescriptor = outputIndependentState.luts->getLutFileDescriptor();
+    auto lutOffsets = outputIndependentState.luts->offsets;
+    auto& lutProperties = outputIndependentState.luts->lutProperties;
+
+    std::vector<LutProperties> aidlProperties;
+    aidlProperties.reserve(lutProperties.size());
+    for (size_t i = 0; i < lutOffsets.size(); i++) {
+        LutProperties properties;
+        properties.dimension = static_cast<LutProperties::Dimension>(lutProperties[i].dimension);
+        properties.size = lutProperties[i].size;
+        properties.samplingKeys = {
+                static_cast<LutProperties::SamplingKey>(lutProperties[i].samplingKey)};
+        aidlProperties.emplace_back(properties);
+    }
+
+    Luts luts;
+    luts.pfd = ndk::ScopedFileDescriptor(dup(lutFileDescriptor.get()));
+    luts.offsets = lutOffsets;
+    luts.lutProperties = std::move(aidlProperties);
 
     switch (auto error = hwcLayer->setLuts(luts)) {
         case hal::Error::NONE:
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index 034c768..dbffe80 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -901,7 +901,6 @@
         EXPECT_CALL(*mHwcLayer, setSurfaceDamage(RegionEq(surfaceDamage))).WillOnce(Return(kError));
         EXPECT_CALL(*mHwcLayer, setBlockingRegion(RegionEq(blockingRegion)))
                 .WillOnce(Return(kError));
-        EXPECT_CALL(*mHwcLayer, setLuts(_)).WillOnce(Return(kError));
     }
 
     void expectSetCompositionTypeCall(Composition compositionType) {
diff --git a/services/surfaceflinger/Display/DisplayModeController.cpp b/services/surfaceflinger/Display/DisplayModeController.cpp
index f8b6c6e..a086aee 100644
--- a/services/surfaceflinger/Display/DisplayModeController.cpp
+++ b/services/surfaceflinger/Display/DisplayModeController.cpp
@@ -28,6 +28,7 @@
 #include <ftl/concat.h>
 #include <ftl/expected.h>
 #include <log/log.h>
+#include <utils/Errors.h>
 
 namespace android::display {
 
@@ -177,12 +178,13 @@
     }
 }
 
-bool DisplayModeController::initiateModeChange(PhysicalDisplayId displayId,
-                                               DisplayModeRequest&& desiredMode,
-                                               const hal::VsyncPeriodChangeConstraints& constraints,
-                                               hal::VsyncPeriodChangeTimeline& outTimeline) {
+auto DisplayModeController::initiateModeChange(
+        PhysicalDisplayId displayId, DisplayModeRequest&& desiredMode,
+        const hal::VsyncPeriodChangeConstraints& constraints,
+        hal::VsyncPeriodChangeTimeline& outTimeline) -> ModeChangeResult {
     std::lock_guard lock(mDisplayLock);
-    const auto& displayPtr = FTL_EXPECT(mDisplays.get(displayId).ok_or(false)).get();
+    const auto& displayPtr =
+            FTL_EXPECT(mDisplays.get(displayId).ok_or(ModeChangeResult::Aborted)).get();
 
     // TODO: b/255635711 - Flow the DisplayModeRequest through the desired/pending/active states.
     // For now, `desiredMode` and `desiredModeOpt` are one and the same, but the latter is not
@@ -201,13 +203,17 @@
 
     const auto& mode = *displayPtr->pendingModeOpt->mode.modePtr;
 
-    if (mComposerPtr->setActiveModeWithConstraints(displayId, mode.getHwcId(), constraints,
-                                                   &outTimeline) != OK) {
-        return false;
+    const auto error = mComposerPtr->setActiveModeWithConstraints(displayId, mode.getHwcId(),
+                                                                  constraints, &outTimeline);
+    switch (error) {
+        case FAILED_TRANSACTION:
+            return ModeChangeResult::Rejected;
+        case OK:
+            SFTRACE_INT(displayPtr->pendingModeFpsTrace.c_str(), mode.getVsyncRate().getIntValue());
+            return ModeChangeResult::Changed;
+        default:
+            return ModeChangeResult::Aborted;
     }
-
-    SFTRACE_INT(displayPtr->pendingModeFpsTrace.c_str(), mode.getVsyncRate().getIntValue());
-    return true;
 }
 
 void DisplayModeController::finalizeModeChange(PhysicalDisplayId displayId, DisplayModeId modeId,
diff --git a/services/surfaceflinger/Display/DisplayModeController.h b/services/surfaceflinger/Display/DisplayModeController.h
index 9ec603d..af3e909 100644
--- a/services/surfaceflinger/Display/DisplayModeController.h
+++ b/services/surfaceflinger/Display/DisplayModeController.h
@@ -70,6 +70,7 @@
     RefreshRateSelectorPtr selectorPtrFor(PhysicalDisplayId) const EXCLUDES(mDisplayLock);
 
     enum class DesiredModeAction { None, InitiateDisplayModeSwitch, InitiateRenderRateSwitch };
+    enum class ModeChangeResult { Changed, Rejected, Aborted };
 
     DesiredModeAction setDesiredMode(PhysicalDisplayId, DisplayModeRequest&&)
             EXCLUDES(mDisplayLock);
@@ -86,9 +87,9 @@
 
     scheduler::FrameRateMode getActiveMode(PhysicalDisplayId) const EXCLUDES(mDisplayLock);
 
-    bool initiateModeChange(PhysicalDisplayId, DisplayModeRequest&&,
-                            const hal::VsyncPeriodChangeConstraints&,
-                            hal::VsyncPeriodChangeTimeline& outTimeline)
+    ModeChangeResult initiateModeChange(PhysicalDisplayId, DisplayModeRequest&&,
+                                        const hal::VsyncPeriodChangeConstraints&,
+                                        hal::VsyncPeriodChangeTimeline& outTimeline)
             REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
 
     void finalizeModeChange(PhysicalDisplayId, DisplayModeId, Fps vsyncRate, Fps renderFps)
diff --git a/services/surfaceflinger/Display/VirtualDisplaySnapshot.h b/services/surfaceflinger/Display/VirtualDisplaySnapshot.h
new file mode 100644
index 0000000..c68020c
--- /dev/null
+++ b/services/surfaceflinger/Display/VirtualDisplaySnapshot.h
@@ -0,0 +1,52 @@
+/*
+ * 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 <optional>
+#include <string>
+
+#include <ui/DisplayId.h>
+
+#include "Utils/Dumper.h"
+
+namespace android::display {
+
+// Immutable state of a virtual display, captured on creation.
+class VirtualDisplaySnapshot {
+public:
+    VirtualDisplaySnapshot(GpuVirtualDisplayId gpuId, std::string uniqueId)
+          : mIsGpu(true), mUniqueId(std::move(uniqueId)), mVirtualId(gpuId) {}
+    VirtualDisplaySnapshot(HalVirtualDisplayId halId, std::string uniqueId)
+          : mIsGpu(false), mUniqueId(std::move(uniqueId)), mVirtualId(halId) {}
+
+    VirtualDisplayId displayId() const { return mVirtualId; }
+    bool isGpu() const { return mIsGpu; }
+
+    void dump(utils::Dumper& dumper) const {
+        using namespace std::string_view_literals;
+
+        dumper.dump("isGpu"sv, mIsGpu ? "true"sv : "false"sv);
+        dumper.dump("uniqueId"sv, mUniqueId);
+    }
+
+private:
+    const bool mIsGpu;
+    const std::string mUniqueId;
+    const VirtualDisplayId mVirtualId;
+};
+
+} // namespace android::display
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 4c8ff58..25f6513 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -37,10 +37,6 @@
 
 namespace android {
 
-using hardware::hidl_handle;
-using hardware::hidl_vec;
-using hardware::Return;
-
 using aidl::android::hardware::graphics::composer3::BnComposerCallback;
 using aidl::android::hardware::graphics::composer3::Capability;
 using aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness;
@@ -524,11 +520,15 @@
 
 Error AidlComposer::getDisplayAttribute(Display display, Config config,
                                         IComposerClient::Attribute attribute, int32_t* outValue) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     const auto status =
             mAidlComposerClient->getDisplayAttribute(translate<int64_t>(display),
                                                      translate<int32_t>(config),
                                                      static_cast<AidlDisplayAttribute>(attribute),
                                                      outValue);
+#pragma clang diagnostic pop
+
     if (!status.isOk()) {
         ALOGE("getDisplayAttribute failed %s", status.getDescription().c_str());
         return static_cast<Error>(status.getServiceSpecificError());
@@ -538,8 +538,13 @@
 
 Error AidlComposer::getDisplayConfigs(Display display, std::vector<Config>* outConfigs) {
     std::vector<int32_t> configs;
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     const auto status =
             mAidlComposerClient->getDisplayConfigs(translate<int64_t>(display), &configs);
+#pragma clang diagnostic pop
+
     if (!status.isOk()) {
         ALOGE("getDisplayConfigs failed %s", status.getDescription().c_str());
         return static_cast<Error>(status.getServiceSpecificError());
@@ -1384,7 +1389,7 @@
     return V2_4::Error::NONE;
 }
 
-V2_4::Error AidlComposer::setActiveConfigWithConstraints(
+Error AidlComposer::setActiveConfigWithConstraints(
         Display display, Config config,
         const IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints,
         VsyncPeriodChangeTimeline* outTimeline) {
@@ -1398,10 +1403,10 @@
                                                      &timeline);
     if (!status.isOk()) {
         ALOGE("setActiveConfigWithConstraints failed %s", status.getDescription().c_str());
-        return static_cast<V2_4::Error>(status.getServiceSpecificError());
+        return static_cast<Error>(status.getServiceSpecificError());
     }
     *outTimeline = translate<VsyncPeriodChangeTimeline>(timeline);
-    return V2_4::Error::NONE;
+    return Error::NONE;
 }
 
 V2_4::Error AidlComposer::setAutoLowLatencyMode(Display display, bool on) {
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
index 933e8d1..6b5ebc5 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -27,11 +27,6 @@
 #include <utility>
 #include <vector>
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-#pragma clang diagnostic ignored "-Wextra"
-
 #include <android/hardware/graphics/composer/2.4/IComposer.h>
 #include <android/hardware/graphics/composer/2.4/IComposerClient.h>
 
@@ -43,9 +38,6 @@
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
-
 namespace android::Hwc2 {
 
 using aidl::android::hardware::graphics::common::DisplayDecorationSupport;
@@ -206,7 +198,7 @@
     V2_4::Error getDisplayConnectionType(Display display,
                                          IComposerClient::DisplayConnectionType* outType) override;
     V2_4::Error getDisplayVsyncPeriod(Display display, VsyncPeriodNanos* outVsyncPeriod) override;
-    V2_4::Error setActiveConfigWithConstraints(
+    Error setActiveConfigWithConstraints(
             Display display, Config config,
             const IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints,
             VsyncPeriodChangeTimeline* outTimeline) override;
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index c1333c2..ff292fa 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -32,6 +32,8 @@
 #include <ui/PictureProfileHandle.h>
 #include <utils/StrongPointer.h>
 
+#include "DisplayHardware/Hal.h"
+
 #include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
 #include <aidl/android/hardware/graphics/common/HdrConversionCapability.h>
 #include <aidl/android/hardware/graphics/common/HdrConversionStrategy.h>
@@ -73,9 +75,9 @@
 using types::V1_2::Dataspace;
 using types::V1_2::PixelFormat;
 
+using hardware::graphics::composer::hal::Error;
 using V2_1::Config;
 using V2_1::Display;
-using V2_1::Error;
 using V2_1::Layer;
 using V2_4::CommandReaderBase;
 using V2_4::CommandWriterBase;
@@ -261,7 +263,7 @@
             Display display, IComposerClient::DisplayConnectionType* outType) = 0;
     virtual V2_4::Error getDisplayVsyncPeriod(Display display,
                                               VsyncPeriodNanos* outVsyncPeriod) = 0;
-    virtual V2_4::Error setActiveConfigWithConstraints(
+    virtual Error setActiveConfigWithConstraints(
             Display display, Config config,
             const IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints,
             VsyncPeriodChangeTimeline* outTimeline) = 0;
diff --git a/services/surfaceflinger/DisplayHardware/DisplayMode.h b/services/surfaceflinger/DisplayHardware/DisplayMode.h
index 224f50e..e90b5b7 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayMode.h
+++ b/services/surfaceflinger/DisplayHardware/DisplayMode.h
@@ -31,10 +31,11 @@
 #include <common/FlagManager.h>
 #include <scheduler/Fps.h>
 
-#include "DisplayHardware/Hal.h"
+#include "Hal.h"
 
 namespace android {
 
+using aidl::android::hardware::graphics::composer3::OutputType;
 namespace hal = android::hardware::graphics::composer::hal;
 
 class DisplayMode;
@@ -114,6 +115,11 @@
             return *this;
         }
 
+        Builder& setHdrOutputType(OutputType type) {
+            mDisplayMode->mHdrOutputType = type;
+            return *this;
+        }
+
     private:
         float getDefaultDensity() {
             // Default density is based on TVs: 1080p displays get XHIGH density, lower-
@@ -166,6 +172,8 @@
     // without visual interruptions such as a black screen.
     int32_t getGroup() const { return mGroup; }
 
+    OutputType getHdrOutputType() const { return mHdrOutputType; }
+
 private:
     explicit DisplayMode(hal::HWConfigId id) : mHwcId(id) {}
 
@@ -179,21 +187,25 @@
     Dpi mDpi;
     int32_t mGroup = -1;
     std::optional<hal::VrrConfig> mVrrConfig;
+    OutputType mHdrOutputType;
 };
 
 inline bool equalsExceptDisplayModeId(const DisplayMode& lhs, const DisplayMode& rhs) {
     return lhs.getHwcId() == rhs.getHwcId() && lhs.getResolution() == rhs.getResolution() &&
             lhs.getVsyncRate().getPeriodNsecs() == rhs.getVsyncRate().getPeriodNsecs() &&
-            lhs.getDpi() == rhs.getDpi() && lhs.getGroup() == rhs.getGroup();
+            lhs.getDpi() == rhs.getDpi() && lhs.getGroup() == rhs.getGroup() &&
+            lhs.getVrrConfig() == rhs.getVrrConfig() &&
+            lhs.getHdrOutputType() == rhs.getHdrOutputType();
 }
 
 inline std::string to_string(const DisplayMode& mode) {
     return base::StringPrintf("{id=%d, hwcId=%d, resolution=%dx%d, vsyncRate=%s, "
-                              "dpi=%.2fx%.2f, group=%d, vrrConfig=%s}",
+                              "dpi=%.2fx%.2f, group=%d, vrrConfig=%s, supportedHdrTypes=%s}",
                               ftl::to_underlying(mode.getId()), mode.getHwcId(), mode.getWidth(),
                               mode.getHeight(), to_string(mode.getVsyncRate()).c_str(),
                               mode.getDpi().x, mode.getDpi().y, mode.getGroup(),
-                              to_string(mode.getVrrConfig()).c_str());
+                              to_string(mode.getVrrConfig()).c_str(),
+                              toString(mode.getHdrOutputType()).c_str());
 }
 
 template <typename... DisplayModePtrs>
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index a274995..081f4aa 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -33,6 +33,8 @@
 #include <ui/GraphicBuffer.h>
 #include <ui/PictureProfileHandle.h>
 
+#include "DisplayHardware/Hal.h"
+
 #include <algorithm>
 #include <cinttypes>
 #include <iterator>
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 61d4541..55ccdef 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -27,6 +27,7 @@
 
 #include "HWComposer.h"
 
+#include <aidl/android/hardware/graphics/composer3/IComposerClient.h>
 #include <android-base/properties.h>
 #include <common/trace.h>
 #include <compositionengine/Output.h>
@@ -335,7 +336,8 @@
                                       .height = config.height,
                                       .vsyncPeriod = config.vsyncPeriod,
                                       .configGroup = config.configGroup,
-                                      .vrrConfig = config.vrrConfig};
+                                      .vrrConfig = config.vrrConfig,
+                                      .hdrOutputType = config.hdrOutputType};
 
         const DisplayConfiguration::Dpi estimatedDPI =
                 getEstimatedDotsPerInchFromSize(hwcDisplayId, hwcMode);
@@ -733,7 +735,11 @@
     auto error = mDisplayData[displayId].hwcDisplay->setActiveConfigWithConstraints(hwcModeId,
                                                                                     constraints,
                                                                                     outTimeline);
-    RETURN_IF_HWC_ERROR(error, displayId, UNKNOWN_ERROR);
+    if (error == hal::Error::CONFIG_FAILED) {
+        RETURN_IF_HWC_ERROR_FOR("setActiveConfigWithConstraints", error, displayId,
+                                FAILED_TRANSACTION);
+    }
+    RETURN_IF_HWC_ERROR_FOR("setActiveConfigWithConstraints", error, displayId, UNKNOWN_ERROR);
     return NO_ERROR;
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index e21ce1d..52662cf 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -55,6 +55,7 @@
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayLuts.h>
 #include <aidl/android/hardware/graphics/composer3/LutProperties.h>
+#include <aidl/android/hardware/graphics/composer3/OutputType.h>
 #include <aidl/android/hardware/graphics/composer3/OverlayProperties.h>
 
 namespace android {
@@ -112,12 +113,14 @@
         float dpiY = -1.f;
         int32_t configGroup = -1;
         std::optional<hal::VrrConfig> vrrConfig;
+        OutputType hdrOutputType;
 
         friend std::ostream& operator<<(std::ostream& os, const HWCDisplayMode& mode) {
             return os << "id=" << mode.hwcId << " res=" << mode.width << "x" << mode.height
                       << " vsyncPeriod=" << mode.vsyncPeriod << " dpi=" << mode.dpiX << "x"
                       << mode.dpiY << " group=" << mode.configGroup
-                      << " vrrConfig=" << to_string(mode.vrrConfig).c_str();
+                      << " vrrConfig=" << to_string(mode.vrrConfig).c_str()
+                      << " hdrOutputType=" << toString(mode.hdrOutputType);
         }
     };
 
diff --git a/services/surfaceflinger/DisplayHardware/Hal.h b/services/surfaceflinger/DisplayHardware/Hal.h
index e3d9622..568d758 100644
--- a/services/surfaceflinger/DisplayHardware/Hal.h
+++ b/services/surfaceflinger/DisplayHardware/Hal.h
@@ -17,16 +17,21 @@
 #pragma once
 
 #include <android/hardware/graphics/common/1.1/types.h>
+#include <android/hardware/graphics/composer/2.1/types.h>
 #include <android/hardware/graphics/composer/2.4/IComposer.h>
 #include <android/hardware/graphics/composer/2.4/IComposerClient.h>
+#include <android/hardware/graphics/composer/2.4/types.h>
 
 #include <aidl/android/hardware/graphics/common/DisplayHotplugEvent.h>
 #include <aidl/android/hardware/graphics/common/Hdr.h>
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayConfiguration.h>
+#include <aidl/android/hardware/graphics/composer3/IComposerClient.h>
 #include <aidl/android/hardware/graphics/composer3/VrrConfig.h>
 
+#include <ftl/enum.h>
+
 #define ERROR_HAS_CHANGES 5
 
 namespace android {
@@ -46,7 +51,6 @@
 using types::V1_2::Dataspace;
 using types::V1_2::PixelFormat;
 
-using V2_1::Error;
 using V2_4::IComposer;
 using V2_4::IComposerCallback;
 using V2_4::IComposerClient;
@@ -78,6 +82,22 @@
 using DisplayConfiguration = V3_0::DisplayConfiguration;
 using VrrConfig = V3_0::VrrConfig;
 
+enum class Error : int32_t {
+    NONE = static_cast<int32_t>(V2_1::Error::NONE),
+    BAD_CONFIG = static_cast<int32_t>(V2_1::Error::BAD_CONFIG),
+    BAD_DISPLAY = static_cast<int32_t>(V2_1::Error::BAD_DISPLAY),
+    BAD_LAYER = static_cast<int32_t>(V2_1::Error::BAD_LAYER),
+    BAD_PARAMETER = static_cast<int32_t>(V2_1::Error::BAD_PARAMETER),
+    NO_RESOURCES = static_cast<int32_t>(V2_1::Error::NO_RESOURCES),
+    NOT_VALIDATED = static_cast<int32_t>(V2_1::Error::NOT_VALIDATED),
+    UNSUPPORTED = static_cast<int32_t>(V2_1::Error::UNSUPPORTED),
+    SEAMLESS_NOT_ALLOWED = static_cast<int32_t>(V2_4::Error::SEAMLESS_NOT_ALLOWED),
+    SEAMLESS_NOT_POSSIBLE = static_cast<int32_t>(V2_4::Error::SEAMLESS_NOT_POSSIBLE),
+    CONFIG_FAILED = V3_0::IComposerClient::EX_CONFIG_FAILED,
+    PICTURE_PROFILE_MAX_EXCEEDED = V3_0::IComposerClient::EX_PICTURE_PROFILE_MAX_EXCEEDED,
+    ftl_last = PICTURE_PROFILE_MAX_EXCEEDED
+};
+
 } // namespace hardware::graphics::composer::hal
 
 inline bool hasChangesError(hardware::graphics::composer::hal::Error error) {
@@ -210,7 +230,11 @@
 }
 
 inline std::string to_string(hardware::graphics::composer::hal::Error error) {
-    return to_string(static_cast<hardware::graphics::composer::hal::V2_4::Error>(error));
+    // 5 is reserved for historical reason, during validation 5 means has changes.
+    if (hasChangesError(error)) {
+        return "HAS_CHANGES";
+    }
+    return ftl::enum_string(error);
 }
 
 // For utils::Dumper ADL.
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index e359a26..5703a2d 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -27,6 +27,7 @@
 #include <SurfaceFlingerProperties.h>
 #include <aidl/android/hardware/graphics/common/DisplayHotplugEvent.h>
 #include <android/binder_manager.h>
+#include <android/hardware/graphics/composer/2.1/types.h>
 #include <common/trace.h>
 #include <composer-command-buffer/2.2/ComposerCommandBuffer.h>
 #include <hidl/HidlTransportSupport.h>
@@ -173,7 +174,7 @@
 };
 
 // assume NO_RESOURCES when Status::isOk returns false
-constexpr Error kDefaultError = Error::NO_RESOURCES;
+constexpr V2_1::Error kDefaultError = V2_1::Error::NO_RESOURCES;
 constexpr V2_4::Error kDefaultError_2_4 = static_cast<V2_4::Error>(kDefaultError);
 
 template <typename T, typename U>
@@ -181,7 +182,7 @@
     return (ret.isOk()) ? static_cast<T>(ret) : static_cast<T>(default_val);
 }
 
-Error unwrapRet(Return<Error>& ret) {
+V2_1::Error unwrapRet(Return<V2_1::Error>& ret) {
     return unwrapRet(ret, kDefaultError);
 }
 
@@ -235,7 +236,7 @@
         });
     } else if (sp<V2_3::IComposer> composer_2_3 = V2_3::IComposer::castFrom(mComposer)) {
         composer_2_3->createClient_2_3([&](const auto& tmpError, const auto& tmpClient) {
-            if (tmpError == Error::NONE) {
+            if (tmpError == V2_1::Error::NONE) {
                 mClient = tmpClient;
                 mClient_2_2 = tmpClient;
                 mClient_2_3 = tmpClient;
@@ -243,7 +244,7 @@
         });
     } else {
         mComposer->createClient([&](const auto& tmpError, const auto& tmpClient) {
-            if (tmpError != Error::NONE) {
+            if (tmpError != V2_1::Error::NONE) {
                 return;
             }
 
@@ -325,14 +326,14 @@
 Error HidlComposer::createVirtualDisplay(uint32_t width, uint32_t height, PixelFormat* format,
                                          Display* outDisplay) {
     const uint32_t bufferSlotCount = 1;
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     if (mClient_2_2) {
         mClient_2_2->createVirtualDisplay_2_2(width, height,
                                               static_cast<types::V1_1::PixelFormat>(*format),
                                               bufferSlotCount,
                                               [&](const auto& tmpError, const auto& tmpDisplay,
                                                   const auto& tmpFormat) {
-                                                  error = tmpError;
+                                                  error = static_cast<Error>(tmpError);
                                                   if (error != Error::NONE) {
                                                       return;
                                                   }
@@ -346,7 +347,7 @@
                                       bufferSlotCount,
                                       [&](const auto& tmpError, const auto& tmpDisplay,
                                           const auto& tmpFormat) {
-                                          error = tmpError;
+                                          error = static_cast<Error>(tmpError);
                                           if (error != Error::NONE) {
                                               return;
                                           }
@@ -361,7 +362,7 @@
 
 Error HidlComposer::destroyVirtualDisplay(Display display) {
     auto ret = mClient->destroyVirtualDisplay(display);
-    return unwrapRet(ret);
+    return static_cast<Error>(unwrapRet(ret));
 }
 
 Error HidlComposer::acceptDisplayChanges(Display display) {
@@ -371,10 +372,10 @@
 }
 
 Error HidlComposer::createLayer(Display display, Layer* outLayer) {
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     mClient->createLayer(display, kMaxLayerBufferCount,
                          [&](const auto& tmpError, const auto& tmpLayer) {
-                             error = tmpError;
+                             error = static_cast<Error>(tmpError);
                              if (error != Error::NONE) {
                                  return;
                              }
@@ -387,13 +388,13 @@
 
 Error HidlComposer::destroyLayer(Display display, Layer layer) {
     auto ret = mClient->destroyLayer(display, layer);
-    return unwrapRet(ret);
+    return static_cast<Error>(unwrapRet(ret));
 }
 
 Error HidlComposer::getActiveConfig(Display display, Config* outConfig) {
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     mClient->getActiveConfig(display, [&](const auto& tmpError, const auto& tmpConfig) {
-        error = tmpError;
+        error = static_cast<Error>(tmpError);
         if (error != Error::NONE) {
             return;
         }
@@ -412,11 +413,11 @@
 }
 
 Error HidlComposer::getColorModes(Display display, std::vector<ColorMode>* outModes) {
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
 
     if (mClient_2_3) {
         mClient_2_3->getColorModes_2_3(display, [&](const auto& tmpError, const auto& tmpModes) {
-            error = tmpError;
+            error = static_cast<Error>(tmpError);
             if (error != Error::NONE) {
                 return;
             }
@@ -425,7 +426,7 @@
         });
     } else if (mClient_2_2) {
         mClient_2_2->getColorModes_2_2(display, [&](const auto& tmpError, const auto& tmpModes) {
-            error = tmpError;
+            error = static_cast<Error>(tmpError);
             if (error != Error::NONE) {
                 return;
             }
@@ -436,7 +437,7 @@
         });
     } else {
         mClient->getColorModes(display, [&](const auto& tmpError, const auto& tmpModes) {
-            error = tmpError;
+            error = static_cast<Error>(tmpError);
             if (error != Error::NONE) {
                 return;
             }
@@ -451,7 +452,7 @@
 
 Error HidlComposer::getDisplayAttribute(Display display, Config config,
                                         IComposerClient::Attribute attribute, int32_t* outValue) {
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     if (mClient_2_4) {
         mClient_2_4->getDisplayAttribute_2_4(display, config, attribute,
                                              [&](const auto& tmpError, const auto& tmpValue) {
@@ -466,7 +467,7 @@
         mClient->getDisplayAttribute(display, config,
                                      static_cast<V2_1::IComposerClient::Attribute>(attribute),
                                      [&](const auto& tmpError, const auto& tmpValue) {
-                                         error = tmpError;
+                                         error = static_cast<Error>(tmpError);
                                          if (error != Error::NONE) {
                                              return;
                                          }
@@ -479,9 +480,9 @@
 }
 
 Error HidlComposer::getDisplayConfigs(Display display, std::vector<Config>* outConfigs) {
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     mClient->getDisplayConfigs(display, [&](const auto& tmpError, const auto& tmpConfigs) {
-        error = tmpError;
+        error = static_cast<Error>(tmpError);
         if (error != Error::NONE) {
             return;
         }
@@ -499,9 +500,9 @@
 }
 
 Error HidlComposer::getDisplayName(Display display, std::string* outName) {
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     mClient->getDisplayName(display, [&](const auto& tmpError, const auto& tmpName) {
-        error = tmpError;
+        error = static_cast<Error>(tmpError);
         if (error != Error::NONE) {
             return;
         }
@@ -520,9 +521,9 @@
 }
 
 Error HidlComposer::getDozeSupport(Display display, bool* outSupport) {
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     mClient->getDozeSupport(display, [&](const auto& tmpError, const auto& tmpSupport) {
-        error = tmpError;
+        error = static_cast<Error>(tmpError);
         if (error != Error::NONE) {
             return;
         }
@@ -541,14 +542,14 @@
 Error HidlComposer::getHdrCapabilities(Display display, std::vector<Hdr>* outHdrTypes,
                                        float* outMaxLuminance, float* outMaxAverageLuminance,
                                        float* outMinLuminance) {
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     if (mClient_2_3) {
         mClient_2_3->getHdrCapabilities_2_3(display,
                                             [&](const auto& tmpError, const auto& tmpHdrTypes,
                                                 const auto& tmpMaxLuminance,
                                                 const auto& tmpMaxAverageLuminance,
                                                 const auto& tmpMinLuminance) {
-                                                error = tmpError;
+                                                error = static_cast<Error>(tmpError);
                                                 if (error != Error::NONE) {
                                                     return;
                                                 }
@@ -564,7 +565,7 @@
                                         const auto& tmpMaxLuminance,
                                         const auto& tmpMaxAverageLuminance,
                                         const auto& tmpMinLuminance) {
-                                        error = tmpError;
+                                        error = static_cast<Error>(tmpError);
                                         if (error != Error::NONE) {
                                             return;
                                         }
@@ -606,7 +607,7 @@
 
 Error HidlComposer::setActiveConfig(Display display, Config config) {
     auto ret = mClient->setActiveConfig(display, config);
-    return unwrapRet(ret);
+    return static_cast<Error>(unwrapRet(ret));
 }
 
 Error HidlComposer::setClientTarget(Display display, uint32_t slot, const sp<GraphicBuffer>& target,
@@ -625,7 +626,7 @@
 }
 
 Error HidlComposer::setColorMode(Display display, ColorMode mode, RenderIntent renderIntent) {
-    hardware::Return<Error> ret(kDefaultError);
+    hardware::Return<V2_1::Error> ret(kDefaultError);
     if (mClient_2_3) {
         ret = mClient_2_3->setColorMode_2_3(display, mode, renderIntent);
     } else if (mClient_2_2) {
@@ -634,7 +635,7 @@
     } else {
         ret = mClient->setColorMode(display, static_cast<types::V1_0::ColorMode>(mode));
     }
-    return unwrapRet(ret);
+    return static_cast<Error>(unwrapRet(ret));
 }
 
 Error HidlComposer::setColorTransform(Display display, const float* matrix) {
@@ -654,25 +655,25 @@
 }
 
 Error HidlComposer::setPowerMode(Display display, IComposerClient::PowerMode mode) {
-    Return<Error> ret(Error::UNSUPPORTED);
+    Return<V2_1::Error> ret(V2_1::Error::UNSUPPORTED);
     if (mClient_2_2) {
         ret = mClient_2_2->setPowerMode_2_2(display, mode);
     } else if (mode != IComposerClient::PowerMode::ON_SUSPEND) {
         ret = mClient->setPowerMode(display, static_cast<V2_1::IComposerClient::PowerMode>(mode));
     }
 
-    return unwrapRet(ret);
+    return static_cast<Error>(unwrapRet(ret));
 }
 
 Error HidlComposer::setVsyncEnabled(Display display, IComposerClient::Vsync enabled) {
     auto ret = mClient->setVsyncEnabled(display, enabled);
-    return unwrapRet(ret);
+    return static_cast<Error>(unwrapRet(ret));
 }
 
 Error HidlComposer::setClientTargetSlotCount(Display display) {
     const uint32_t bufferSlotCount = BufferQueue::NUM_BUFFER_SLOTS;
     auto ret = mClient->setClientTargetSlotCount(display, bufferSlotCount);
-    return unwrapRet(ret);
+    return static_cast<Error>(unwrapRet(ret));
 }
 
 Error HidlComposer::validateDisplay(Display display, nsecs_t /*expectedPresentTime*/,
@@ -903,7 +904,7 @@
     // set up new input command queue if necessary
     if (queueChanged) {
         auto ret = mClient->setInputCommandQueue(*mWriter.getMQDescriptor());
-        auto error = unwrapRet(ret);
+        auto error = static_cast<Error>(unwrapRet(ret));
         if (error != Error::NONE) {
             mWriter.reset();
             return error;
@@ -915,17 +916,17 @@
         return Error::NONE;
     }
 
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     hardware::Return<void> ret;
     auto hidl_callback = [&](const auto& tmpError, const auto& tmpOutChanged,
                              const auto& tmpOutLength, const auto& tmpOutHandles) {
-        error = tmpError;
+        error = static_cast<Error>(tmpError);
 
         // set up new output command queue if necessary
         if (error == Error::NONE && tmpOutChanged) {
-            error = kDefaultError;
+            error = static_cast<Error>(kDefaultError);
             mClient->getOutputCommandQueue([&](const auto& tmpError, const auto& tmpDescriptor) {
-                error = tmpError;
+                error = static_cast<Error>(tmpError);
                 if (error != Error::NONE) {
                     return;
                 }
@@ -1000,11 +1001,11 @@
         return keys;
     }
 
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     if (mClient_2_3) {
         mClient_2_3->getPerFrameMetadataKeys_2_3(display,
                                                  [&](const auto& tmpError, const auto& tmpKeys) {
-                                                     error = tmpError;
+                                                     error = static_cast<Error>(tmpError);
                                                      if (error != Error::NONE) {
                                                          ALOGW("getPerFrameMetadataKeys failed "
                                                                "with %d",
@@ -1016,7 +1017,7 @@
     } else {
         mClient_2_2
                 ->getPerFrameMetadataKeys(display, [&](const auto& tmpError, const auto& tmpKeys) {
-                    error = tmpError;
+                    error = static_cast<Error>(tmpError);
                     if (error != Error::NONE) {
                         ALOGW("getPerFrameMetadataKeys failed with %d", tmpError);
                         return;
@@ -1039,10 +1040,10 @@
         return Error::NONE;
     }
 
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
 
     auto getRenderIntentsLambda = [&](const auto& tmpError, const auto& tmpKeys) {
-        error = tmpError;
+        error = static_cast<Error>(tmpError);
         if (error != Error::NONE) {
             return;
         }
@@ -1066,10 +1067,10 @@
         return Error::NONE;
     }
 
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     mClient_2_2->getDataspaceSaturationMatrix(static_cast<types::V1_1::Dataspace>(dataspace),
                                               [&](const auto& tmpError, const auto& tmpMatrix) {
-                                                  error = tmpError;
+                                                  error = static_cast<Error>(tmpError);
                                                   if (error != Error::NONE) {
                                                       return;
                                                   }
@@ -1087,11 +1088,11 @@
         return Error::UNSUPPORTED;
     }
 
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     mClient_2_3->getDisplayIdentificationData(display,
                                               [&](const auto& tmpError, const auto& tmpPort,
                                                   const auto& tmpData) {
-                                                  error = tmpError;
+                                                  error = static_cast<Error>(tmpError);
                                                   if (error != Error::NONE) {
                                                       return;
                                                   }
@@ -1123,13 +1124,13 @@
     if (!mClient_2_3) {
         return Error::UNSUPPORTED;
     }
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     mClient_2_3->getDisplayedContentSamplingAttributes(display,
                                                        [&](const auto tmpError,
                                                            const auto& tmpFormat,
                                                            const auto& tmpDataspace,
                                                            const auto& tmpComponentMask) {
-                                                           error = tmpError;
+                                                           error = static_cast<Error>(tmpError);
                                                            if (error == Error::NONE) {
                                                                *outFormat = tmpFormat;
                                                                *outDataspace = tmpDataspace;
@@ -1149,8 +1150,9 @@
 
     auto enable = enabled ? V2_3::IComposerClient::DisplayedContentSampling::ENABLE
                           : V2_3::IComposerClient::DisplayedContentSampling::DISABLE;
-    return mClient_2_3->setDisplayedContentSamplingEnabled(display, enable, componentMask,
-                                                           maxFrames);
+    auto ret = mClient_2_3->setDisplayedContentSamplingEnabled(display, enable, componentMask,
+                                                               maxFrames);
+    return static_cast<Error>(unwrapRet(ret));
 }
 
 Error HidlComposer::getDisplayedContentSample(Display display, uint64_t maxFrames,
@@ -1161,12 +1163,12 @@
     if (!mClient_2_3) {
         return Error::UNSUPPORTED;
     }
-    Error error = kDefaultError;
+    Error error = static_cast<Error>(kDefaultError);
     mClient_2_3->getDisplayedContentSample(display, maxFrames, timestamp,
                                            [&](const auto tmpError, auto tmpNumFrames,
                                                const auto& tmpSamples0, const auto& tmpSamples1,
                                                const auto& tmpSamples2, const auto& tmpSamples3) {
-                                               error = tmpError;
+                                               error = static_cast<Error>(tmpError);
                                                if (error == Error::NONE) {
                                                    outStats->numFrames = tmpNumFrames;
                                                    outStats->component_0_sample = tmpSamples0;
@@ -1196,7 +1198,8 @@
     if (!mClient_2_3) {
         return Error::UNSUPPORTED;
     }
-    return mClient_2_3->setDisplayBrightness(display, brightness);
+    auto ret = mClient_2_3->setDisplayBrightness(display, brightness);
+    return static_cast<Error>(unwrapRet(ret));
 }
 
 // Composer HAL 2.4
@@ -1273,19 +1276,18 @@
     return error;
 }
 
-V2_4::Error HidlComposer::setActiveConfigWithConstraints(
+Error HidlComposer::setActiveConfigWithConstraints(
         Display display, Config config,
         const IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints,
         VsyncPeriodChangeTimeline* outTimeline) {
-    using Error = V2_4::Error;
     if (!mClient_2_4) {
         return Error::UNSUPPORTED;
     }
 
-    Error error = kDefaultError_2_4;
+    Error error = static_cast<Error>(kDefaultError_2_4);
     mClient_2_4->setActiveConfigWithConstraints(display, config, vsyncPeriodChangeConstraints,
                                                 [&](const auto& tmpError, const auto& tmpTimeline) {
-                                                    error = tmpError;
+                                                    error = static_cast<Error>(tmpError);
                                                     if (error != Error::NONE) {
                                                         return;
                                                     }
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
index 9a89dba..42ba9a9 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -60,7 +60,6 @@
 
 using V2_1::Config;
 using V2_1::Display;
-using V2_1::Error;
 using V2_1::Layer;
 using V2_4::CommandReaderBase;
 using V2_4::CommandWriterBase;
@@ -308,7 +307,7 @@
     V2_4::Error getDisplayConnectionType(Display display,
                                          IComposerClient::DisplayConnectionType* outType) override;
     V2_4::Error getDisplayVsyncPeriod(Display display, VsyncPeriodNanos* outVsyncPeriod) override;
-    V2_4::Error setActiveConfigWithConstraints(
+    Error setActiveConfigWithConstraints(
             Display display, Config config,
             const IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints,
             VsyncPeriodChangeTimeline* outTimeline) override;
diff --git a/services/surfaceflinger/PowerAdvisor/Android.bp b/services/surfaceflinger/PowerAdvisor/Android.bp
index 7352f7a..4efbcb9 100644
--- a/services/surfaceflinger/PowerAdvisor/Android.bp
+++ b/services/surfaceflinger/PowerAdvisor/Android.bp
@@ -19,9 +19,6 @@
 
 aidl_interface {
     name: "android.adpf.sessionmanager_aidl",
-    defaults: [
-        "android.hardware.power-aidl",
-    ],
     srcs: [
         "aidl/android/adpf/*.aidl",
     ],
@@ -39,10 +36,6 @@
             enabled: true,
         },
     },
-    imports: [
-        "android.hardware.common.fmq-V1",
-        "android.hardware.common-V2",
-    ],
 }
 
 cc_defaults {
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index fff4284..c6d7160 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -43,6 +43,7 @@
 #include <utils/Errors.h>
 
 #include <common/FlagManager.h>
+#include <scheduler/FrameRateMode.h>
 #include <scheduler/VsyncConfig.h>
 #include "FrameTimeline.h"
 #include "VSyncDispatch.h"
@@ -102,6 +103,10 @@
                                 to_string(event.header.displayId).c_str(),
                                 event.hdcpLevelsChange.connectedLevel,
                                 event.hdcpLevelsChange.maxLevel);
+        case DisplayEventReceiver::DISPLAY_EVENT_MODE_REJECTION:
+            return StringPrintf("ModeRejected{displayId=%s, modeId=%u}",
+                                to_string(event.header.displayId).c_str(),
+                                event.modeRejection.modeId);
         default:
             return "Event{}";
     }
@@ -186,6 +191,18 @@
     };
 }
 
+DisplayEventReceiver::Event makeModeRejection(PhysicalDisplayId displayId, DisplayModeId modeId) {
+    return DisplayEventReceiver::Event{
+            .header =
+                    DisplayEventReceiver::Event::Header{
+                            .type = DisplayEventReceiver::DISPLAY_EVENT_MODE_REJECTION,
+                            .displayId = displayId,
+                            .timestamp = systemTime(),
+                    },
+            .modeRejection.modeId = ftl::to_underlying(modeId),
+    };
+}
+
 } // namespace
 
 EventThreadConnection::EventThreadConnection(EventThread* eventThread, uid_t callingUid,
@@ -480,6 +497,13 @@
     mCondition.notify_all();
 }
 
+void EventThread::onModeRejected(PhysicalDisplayId displayId, DisplayModeId modeId) {
+    std::lock_guard<std::mutex> lock(mMutex);
+
+    mPendingEvents.push_back(makeModeRejection(displayId, modeId));
+    mCondition.notify_all();
+}
+
 // Merge lists of buffer stuffed Uids
 void EventThread::addBufferStuffedUids(BufferStuffingMap bufferStuffedUids) {
     std::lock_guard<std::mutex> lock(mMutex);
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index 95632c7..18bf416 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -21,6 +21,7 @@
 #include <gui/DisplayEventReceiver.h>
 #include <private/gui/BitTube.h>
 #include <sys/types.h>
+#include <ui/DisplayId.h>
 #include <utils/Errors.h>
 
 #include <scheduler/FrameRateMode.h>
@@ -32,6 +33,7 @@
 #include <thread>
 #include <vector>
 
+#include "DisplayHardware/DisplayMode.h"
 #include "TracedOrdinal.h"
 #include "VSyncDispatch.h"
 #include "VsyncSchedule.h"
@@ -115,6 +117,9 @@
     // called when SF changes the active mode and apps needs to be notified about the change
     virtual void onModeChanged(const scheduler::FrameRateMode&) = 0;
 
+    // called when SF rejects the mode change request
+    virtual void onModeRejected(PhysicalDisplayId displayId, DisplayModeId modeId) = 0;
+
     // called when SF updates the Frame Rate Override list
     virtual void onFrameRateOverridesChanged(PhysicalDisplayId displayId,
                                              std::vector<FrameRateOverride> overrides) = 0;
@@ -179,6 +184,8 @@
 
     void onModeChanged(const scheduler::FrameRateMode&) override;
 
+    void onModeRejected(PhysicalDisplayId displayId, DisplayModeId modeId) override;
+
     void onFrameRateOverridesChanged(PhysicalDisplayId displayId,
                                      std::vector<FrameRateOverride> overrides) override;
 
diff --git a/services/surfaceflinger/Scheduler/ISchedulerCallback.h b/services/surfaceflinger/Scheduler/ISchedulerCallback.h
index 2b9e88c..d02d149 100644
--- a/services/surfaceflinger/Scheduler/ISchedulerCallback.h
+++ b/services/surfaceflinger/Scheduler/ISchedulerCallback.h
@@ -31,7 +31,7 @@
     virtual void onChoreographerAttached() = 0;
     virtual void onExpectedPresentTimePosted(TimePoint, ftl::NonNull<DisplayModePtr>,
                                              Fps renderRate) = 0;
-    virtual void onCommitNotComposited(PhysicalDisplayId pacesetterDisplayId) = 0;
+    virtual void onCommitNotComposited() = 0;
     virtual void vrrDisplayIdle(bool idle) = 0;
 
 protected:
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 171342d..630beb0 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -280,6 +280,9 @@
                     case Layer::FrameRateCompatibility::Exact:
                         return LayerVoteType::ExplicitExact;
                     case Layer::FrameRateCompatibility::Gte:
+                        if (frameRate.isNoVote()) {
+                            return LayerVoteType::NoVote;
+                        }
                         if (isVrrDevice) {
                             return LayerVoteType::ExplicitGte;
                         } else {
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index ff1926e..6e2b943 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -504,7 +504,7 @@
             return FrameRateCompatibility::Exact;
         case ANATIVEWINDOW_FRAME_RATE_MIN:
             return FrameRateCompatibility::Min;
-        case ANATIVEWINDOW_FRAME_RATE_GTE:
+        case ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE:
             return FrameRateCompatibility::Gte;
         case ANATIVEWINDOW_FRAME_RATE_NO_VOTE:
             return FrameRateCompatibility::NoVote;
@@ -562,7 +562,10 @@
 }
 
 bool LayerInfo::FrameRate::isNoVote() const {
-    return vote.type == FrameRateCompatibility::NoVote;
+    // A desired frame rate greater than or equal to 0 is treated as NoVote.
+    bool isNoVoteGte = FlagManager::getInstance().arr_setframerate_gte_enum() &&
+            vote.type == FrameRateCompatibility::Gte && !vote.rate.isValid();
+    return vote.type == FrameRateCompatibility::NoVote || isNoVoteGte;
 }
 
 bool LayerInfo::FrameRate::isValuelessType() const {
@@ -577,7 +580,12 @@
         case FrameRateCompatibility::Default:
         case FrameRateCompatibility::ExactOrMultiple:
         case FrameRateCompatibility::Exact:
+            return false;
         case FrameRateCompatibility::Gte:
+            if (isNoVote()) {
+                // Special case: GTE 0 is same as NoVote.
+                return true;
+            }
             return false;
     }
 }
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 668fa54..97f1f8f 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -1532,6 +1532,9 @@
 
     mPrimaryFrameRates = filterRefreshRates(policy->primaryRanges, "primary");
     mAppRequestFrameRates = filterRefreshRates(policy->appRequestRanges, "app request");
+    mAllFrameRates = filterRefreshRates(FpsRanges(getSupportedFrameRateRangeLocked(),
+                                                  getSupportedFrameRateRangeLocked()),
+                                        "full frame rates");
 }
 
 bool RefreshRateSelector::isVrrDevice() const {
@@ -1560,16 +1563,24 @@
 std::vector<float> RefreshRateSelector::getSupportedFrameRates() const {
     std::scoped_lock lock(mLock);
     // TODO(b/356986687) Remove the limit once we have the anchor list implementation.
-    const size_t frameRatesSize = std::min<size_t>(11, mPrimaryFrameRates.size());
+    const size_t frameRatesSize = std::min<size_t>(11, mAllFrameRates.size());
     std::vector<float> supportedFrameRates;
     supportedFrameRates.reserve(frameRatesSize);
-    std::transform(mPrimaryFrameRates.rbegin(),
-                   mPrimaryFrameRates.rbegin() + static_cast<int>(frameRatesSize),
+    std::transform(mAllFrameRates.rbegin(),
+                   mAllFrameRates.rbegin() + static_cast<int>(frameRatesSize),
                    std::back_inserter(supportedFrameRates),
                    [](FrameRateMode mode) { return mode.fps.getValue(); });
     return supportedFrameRates;
 }
 
+FpsRange RefreshRateSelector::getSupportedFrameRateRangeLocked() const {
+    using fps_approx_ops::operator<;
+    if (mMaxRefreshRateModeIt->second->getPeakFps() < kMinSupportedFrameRate) {
+        return {mMaxRefreshRateModeIt->second->getPeakFps(), kMinSupportedFrameRate};
+    }
+    return {kMinSupportedFrameRate, mMaxRefreshRateModeIt->second->getPeakFps()};
+}
+
 auto RefreshRateSelector::getIdleTimerAction() const -> KernelIdleTimerAction {
     std::lock_guard lock(mLock);
 
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
index 508f9d7..8e173b1 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
@@ -553,6 +553,7 @@
     // Display modes that satisfy the Policy's ranges, filtered and sorted by refresh rate.
     std::vector<FrameRateMode> mPrimaryFrameRates GUARDED_BY(mLock);
     std::vector<FrameRateMode> mAppRequestFrameRates GUARDED_BY(mLock);
+    std::vector<FrameRateMode> mAllFrameRates GUARDED_BY(mLock);
 
     // Caches whether the device is VRR-compatible based on the active display mode.
     std::atomic_bool mIsVrrDevice = false;
@@ -597,6 +598,9 @@
     // Used to detect (lack of) frame activity.
     ftl::Optional<scheduler::OneShotTimer> mIdleTimer;
     std::atomic<bool> mIdleTimerStarted = false;
+
+    // Returns the range of supported frame rates.
+    FpsRange getSupportedFrameRateRangeLocked() const REQUIRES(mLock);
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 2875650..4da76f6 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -242,7 +242,7 @@
             if (FlagManager::getInstance().vrr_config()) {
                 compositor.sendNotifyExpectedPresentHint(pacesetterPtr->displayId);
             }
-            mSchedulerCallback.onCommitNotComposited(pacesetterPtr->displayId);
+            mSchedulerCallback.onCommitNotComposited();
             return;
         }
     }
@@ -465,6 +465,12 @@
 }
 #pragma clang diagnostic pop
 
+void Scheduler::onDisplayModeRejected(PhysicalDisplayId displayId, DisplayModeId modeId) {
+    if (hasEventThreads()) {
+        eventThreadFor(Cycle::Render).onModeRejected(displayId, modeId);
+    }
+}
+
 void Scheduler::emitModeChangeIfNeeded() {
     if (!mPolicy.modeOpt || !mPolicy.emittedModeOpt) {
         ALOGW("No mode change to emit");
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 61c68a9..3fdddac 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -36,12 +36,14 @@
 #include <ftl/non_null.h>
 #include <ftl/optional.h>
 #include <scheduler/Features.h>
+#include <scheduler/FrameRateMode.h>
 #include <scheduler/FrameTargeter.h>
 #include <scheduler/Time.h>
 #include <scheduler/VsyncConfig.h>
 #include <ui/DisplayId.h>
 #include <ui/DisplayMap.h>
 
+#include "DisplayHardware/DisplayMode.h"
 #include "EventThread.h"
 #include "FrameRateOverrideMappings.h"
 #include "ISchedulerCallback.h"
@@ -153,6 +155,8 @@
     bool onDisplayModeChanged(PhysicalDisplayId, const FrameRateMode&,
                               bool clearContentRequirements) EXCLUDES(mPolicyLock);
 
+    void onDisplayModeRejected(PhysicalDisplayId, DisplayModeId);
+
     void enableSyntheticVsync(bool = true) REQUIRES(kMainThreadContext);
     void omitVsyncDispatching(bool) REQUIRES(kMainThreadContext);
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index f9e59b9..59b1917 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -172,6 +172,7 @@
 
 #include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
+#include <aidl/android/hardware/graphics/composer3/OutputType.h>
 #include <aidl/android/hardware/graphics/composer3/RenderIntent.h>
 
 #undef NO_THREAD_SAFETY_ANALYSIS
@@ -649,11 +650,12 @@
     }
 }
 
-VirtualDisplayId SurfaceFlinger::acquireVirtualDisplay(ui::Size resolution,
-                                                       ui::PixelFormat format) {
+VirtualDisplayId SurfaceFlinger::acquireVirtualDisplay(ui::Size resolution, ui::PixelFormat format,
+                                                       const std::string& uniqueId) {
     if (auto& generator = mVirtualDisplayIdGenerators.hal) {
         if (const auto id = generator->generateId()) {
             if (getHwComposer().allocateVirtualDisplay(*id, resolution, &format)) {
+                acquireVirtualDisplaySnapshot(*id, uniqueId);
                 return *id;
             }
 
@@ -667,6 +669,7 @@
 
     const auto id = mVirtualDisplayIdGenerators.gpu.generateId();
     LOG_ALWAYS_FATAL_IF(!id, "Failed to generate ID for GPU virtual display");
+    acquireVirtualDisplaySnapshot(*id, uniqueId);
     return *id;
 }
 
@@ -674,6 +677,7 @@
     if (const auto id = HalVirtualDisplayId::tryCast(displayId)) {
         if (auto& generator = mVirtualDisplayIdGenerators.hal) {
             generator->releaseId(*id);
+            releaseVirtualDisplaySnapshot(*id);
         }
         return;
     }
@@ -681,6 +685,14 @@
     const auto id = GpuVirtualDisplayId::tryCast(displayId);
     LOG_ALWAYS_FATAL_IF(!id);
     mVirtualDisplayIdGenerators.gpu.releaseId(*id);
+    releaseVirtualDisplaySnapshot(*id);
+}
+
+void SurfaceFlinger::releaseVirtualDisplaySnapshot(VirtualDisplayId displayId) {
+    std::lock_guard lock(mVirtualDisplaysMutex);
+    if (!mVirtualDisplays.erase(displayId)) {
+        ALOGW("%s: Virtual display snapshot was not removed", __func__);
+    }
 }
 
 std::vector<PhysicalDisplayId> SurfaceFlinger::getPhysicalDisplayIdsLocked() const {
@@ -1532,8 +1544,15 @@
         constraints.seamlessRequired = false;
         hal::VsyncPeriodChangeTimeline outTimeline;
 
-        if (!mDisplayModeController.initiateModeChange(displayId, std::move(*desiredModeOpt),
-                                                       constraints, outTimeline)) {
+        const auto error =
+                mDisplayModeController.initiateModeChange(displayId, std::move(*desiredModeOpt),
+                                                          constraints, outTimeline);
+        if (error != display::DisplayModeController::ModeChangeResult::Changed) {
+            dropModeRequest(displayId);
+            if (FlagManager::getInstance().display_config_error_hal() &&
+                error == display::DisplayModeController::ModeChangeResult::Rejected) {
+                mScheduler->onDisplayModeRejected(displayId, desiredModeId);
+            }
             continue;
         }
 
@@ -3497,6 +3516,9 @@
     DisplayModes newModes;
     for (const auto& hwcMode : hwcModes) {
         const auto id = nextModeId++;
+        OutputType hdrOutputType = FlagManager::getInstance().connected_display_hdr()
+                ? hwcMode.hdrOutputType
+                : OutputType::INVALID;
         newModes.try_emplace(id,
                              DisplayMode::Builder(hwcMode.hwcId)
                                      .setId(id)
@@ -3507,6 +3529,7 @@
                                      .setDpiX(hwcMode.dpiX)
                                      .setDpiY(hwcMode.dpiY)
                                      .setGroup(hwcMode.configGroup)
+                                     .setHdrOutputType(hdrOutputType)
                                      .build());
     }
 
@@ -3797,7 +3820,7 @@
     if (const auto& physical = state.physical) {
         builder.setId(physical->id);
     } else {
-        builder.setId(acquireVirtualDisplay(resolution, pixelFormat));
+        builder.setId(acquireVirtualDisplay(resolution, pixelFormat, state.uniqueId));
     }
 
     builder.setPixels(resolution);
@@ -4387,7 +4410,7 @@
     scheduleNotifyExpectedPresentHint(displayId);
 }
 
-void SurfaceFlinger::onCommitNotComposited(PhysicalDisplayId pacesetterDisplayId) {
+void SurfaceFlinger::onCommitNotComposited() {
     if (FlagManager::getInstance().commit_not_composited()) {
         mFrameTimeline->onCommitNotComposited();
     }
@@ -5784,6 +5807,14 @@
             utils::Dumper::Section section(dumper,
                                            ftl::Concat("Virtual Display ", displayId.value).str());
             display->dump(dumper);
+
+            if (const auto virtualIdOpt = VirtualDisplayId::tryCast(displayId)) {
+                std::lock_guard lock(mVirtualDisplaysMutex);
+                const auto virtualSnapshotIt = mVirtualDisplays.find(virtualIdOpt.value());
+                if (virtualSnapshotIt != mVirtualDisplays.end()) {
+                    virtualSnapshotIt->second.dump(dumper);
+                }
+            }
         }
     }
 }
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 211f374..1e2c087 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -73,6 +73,7 @@
 #include "BackgroundExecutor.h"
 #include "Display/DisplayModeController.h"
 #include "Display/PhysicalDisplay.h"
+#include "Display/VirtualDisplaySnapshot.h"
 #include "DisplayDevice.h"
 #include "DisplayHardware/HWC2.h"
 #include "DisplayIdGenerator.h"
@@ -700,7 +701,7 @@
     void onChoreographerAttached() override;
     void onExpectedPresentTimePosted(TimePoint expectedPresentTime, ftl::NonNull<DisplayModePtr>,
                                      Fps renderRate) override;
-    void onCommitNotComposited(PhysicalDisplayId pacesetterDisplayId) override
+    void onCommitNotComposited() override
             REQUIRES(kMainThreadContext);
     void vrrDisplayIdle(bool idle) override;
 
@@ -1075,8 +1076,20 @@
     void enableHalVirtualDisplays(bool);
 
     // Virtual display lifecycle for ID generation and HAL allocation.
-    VirtualDisplayId acquireVirtualDisplay(ui::Size, ui::PixelFormat) REQUIRES(mStateLock);
+    VirtualDisplayId acquireVirtualDisplay(ui::Size, ui::PixelFormat, const std::string& uniqueId)
+            REQUIRES(mStateLock);
+    template <typename ID>
+    void acquireVirtualDisplaySnapshot(ID displayId, const std::string& uniqueId) {
+        std::lock_guard lock(mVirtualDisplaysMutex);
+        const bool emplace_success =
+                mVirtualDisplays.try_emplace(displayId, displayId, uniqueId).second;
+        if (!emplace_success) {
+            ALOGW("%s: Virtual display snapshot with the same ID already exists", __func__);
+        }
+    }
+
     void releaseVirtualDisplay(VirtualDisplayId);
+    void releaseVirtualDisplaySnapshot(VirtualDisplayId displayId);
 
     // Returns a display other than `mActiveDisplayId` that can be activated, if any.
     sp<DisplayDevice> getActivatableDisplay() const REQUIRES(mStateLock, kMainThreadContext);
@@ -1277,6 +1290,10 @@
 
     display::PhysicalDisplays mPhysicalDisplays GUARDED_BY(mStateLock);
 
+    mutable std::mutex mVirtualDisplaysMutex;
+    ftl::SmallMap<VirtualDisplayId, const display::VirtualDisplaySnapshot, 2> mVirtualDisplays
+            GUARDED_BY(mVirtualDisplaysMutex);
+
     // The inner or outer display for foldables, while unfolded or folded, respectively.
     std::atomic<PhysicalDisplayId> mActiveDisplayId;
 
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index f257c7c..e28a0c1 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -119,6 +119,7 @@
 
     /// Trunk stable readonly flags ///
     DUMP_ACONFIG_FLAG(adpf_fmq_sf);
+    DUMP_ACONFIG_FLAG(arr_setframerate_gte_enum);
     DUMP_ACONFIG_FLAG(connected_display);
     DUMP_ACONFIG_FLAG(enable_small_area_detection);
     DUMP_ACONFIG_FLAG(stable_edid_ids);
@@ -218,6 +219,7 @@
 
 /// Trunk stable readonly flags ///
 FLAG_MANAGER_ACONFIG_FLAG(adpf_fmq_sf, "")
+FLAG_MANAGER_ACONFIG_FLAG(arr_setframerate_gte_enum, "debug.sf.arr_setframerate_gte_enum")
 FLAG_MANAGER_ACONFIG_FLAG(connected_display, "")
 FLAG_MANAGER_ACONFIG_FLAG(enable_small_area_detection, "")
 FLAG_MANAGER_ACONFIG_FLAG(stable_edid_ids, "debug.sf.stable_edid_ids")
@@ -257,7 +259,7 @@
 FLAG_MANAGER_ACONFIG_FLAG(force_compile_graphite_renderengine, "");
 FLAG_MANAGER_ACONFIG_FLAG(true_hdr_screenshots, "debug.sf.true_hdr_screenshots");
 FLAG_MANAGER_ACONFIG_FLAG(display_config_error_hal, "");
-FLAG_MANAGER_ACONFIG_FLAG(connected_display_hdr, "");
+FLAG_MANAGER_ACONFIG_FLAG(connected_display_hdr, "debug.sf.connected_display_hdr");
 FLAG_MANAGER_ACONFIG_FLAG(deprecate_frame_tracker, "");
 FLAG_MANAGER_ACONFIG_FLAG(skip_invisible_windows_in_input, "");
 FLAG_MANAGER_ACONFIG_FLAG(begone_bright_hlg, "debug.sf.begone_bright_hlg");
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index a461627..1a857c8 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -56,6 +56,7 @@
     bool graphite_renderengine_preview_rollout() const;
 
     /// Trunk stable readonly flags ///
+    bool arr_setframerate_gte_enum() const;
     bool adpf_fmq_sf() const;
     bool connected_display() const;
     bool frame_rate_category_mrr() const;
diff --git a/services/surfaceflinger/surfaceflinger.logtags b/services/surfaceflinger/surfaceflinger.logtags
index e68d9f5..6dbe0dd 100644
--- a/services/surfaceflinger/surfaceflinger.logtags
+++ b/services/surfaceflinger/surfaceflinger.logtags
@@ -31,7 +31,7 @@
 # 6: Percent
 # Default value for data of type int/long is 2 (bytes).
 #
-# See system/core/logcat/event.logtags for the master copy of the tags.
+# See system/logging/logcat/event.logtags for the master copy of the tags.
 
 # 60100 - 60199 reserved for surfaceflinger
 
diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
index dfce722..fc0fabd 100644
--- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
@@ -35,6 +35,14 @@
 } # arr_setframerate_api
 
 flag {
+  name: "arr_setframerate_gte_enum"
+  namespace: "core_graphics"
+  description: "Exposes GTE (greater than or equal to) enum for Android 16"
+  bug: "380949716"
+  is_fixed_read_only: true
+} # arr_setframerate_gte_enum
+
+flag {
   name: "arr_surfacecontrol_setframerate_api"
   namespace: "core_graphics"
   description: "New SDK SurfaceControl.Transaction#setFrameRate API for Android 16"
diff --git a/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp b/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp
index d971150..29a1fab 100644
--- a/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp
@@ -20,6 +20,7 @@
 #include "Display/DisplayModeController.h"
 #include "Display/DisplaySnapshot.h"
 #include "DisplayHardware/HWComposer.h"
+#include "DisplayHardware/Hal.h"
 #include "DisplayIdentificationTestHelpers.h"
 #include "FpsOps.h"
 #include "mock/DisplayHardware/MockComposer.h"
@@ -103,7 +104,7 @@
 
         EXPECT_CALL(*mComposerHal,
                     setActiveConfigWithConstraints(kHwcDisplayId, hwcModeId, constraints, _))
-                .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(hal::V2_4::Error::NONE)));
+                .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(hal::Error::NONE)));
 
         return constraints;
     }
@@ -183,7 +184,8 @@
     hal::VsyncPeriodChangeTimeline timeline;
     const auto constraints = expectModeSet(modeRequest, timeline);
 
-    EXPECT_TRUE(mDmc.initiateModeChange(mDisplayId, std::move(modeRequest), constraints, timeline));
+    EXPECT_EQ(DisplayModeController::ModeChangeResult::Changed,
+              mDmc.initiateModeChange(mDisplayId, std::move(modeRequest), constraints, timeline));
     EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDmc.getPendingMode(mDisplayId));
 
     mDmc.clearDesiredMode(mDisplayId);
@@ -210,7 +212,8 @@
     hal::VsyncPeriodChangeTimeline timeline;
     auto constraints = expectModeSet(modeRequest, timeline);
 
-    EXPECT_TRUE(mDmc.initiateModeChange(mDisplayId, std::move(modeRequest), constraints, timeline));
+    EXPECT_EQ(DisplayModeController::ModeChangeResult::Changed,
+              mDmc.initiateModeChange(mDisplayId, std::move(modeRequest), constraints, timeline));
     EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDmc.getPendingMode(mDisplayId));
 
     // No action since a mode switch has already been initiated.
@@ -223,7 +226,8 @@
     constexpr bool kSubsequent = true;
     constraints = expectModeSet(modeRequest, timeline, kSubsequent);
 
-    EXPECT_TRUE(mDmc.initiateModeChange(mDisplayId, std::move(modeRequest), constraints, timeline));
+    EXPECT_EQ(DisplayModeController::ModeChangeResult::Changed,
+              mDmc.initiateModeChange(mDisplayId, std::move(modeRequest), constraints, timeline));
     EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDmc.getPendingMode(mDisplayId));
 
     mDmc.clearDesiredMode(mDisplayId);
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index e0753a3..ba2d3e2 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -69,7 +69,7 @@
 using ::testing::StrictMock;
 
 struct HWComposerTest : testing::Test {
-    using HalError = hardware::graphics::composer::V2_1::Error;
+    using HalError = hal::Error;
 
     Hwc2::mock::Composer* const mHal = new StrictMock<Hwc2::mock::Composer>();
     impl::HWComposer mHwc{std::unique_ptr<Hwc2::Composer>(mHal)};
@@ -384,6 +384,7 @@
         const ui::Size size = info->preferredDetailedTimingDescriptor->physicalSizeInMm;
         const float expectedDpiX = (kWidth * kMmPerInch / size.width);
         const float expectedDpiY = (kHeight * kMmPerInch / size.height);
+        const OutputType hdrOutputType = OutputType::SYSTEM;
         const hal::VrrConfig vrrConfig =
                 hal::VrrConfig{.minFrameIntervalNs = static_cast<Fps>(120_Hz).getPeriodNsecs(),
                                .notifyExpectedPresentConfig = hal::VrrConfig::
@@ -394,7 +395,8 @@
                                                        .height = kHeight,
                                                        .configGroup = kConfigGroup,
                                                        .vsyncPeriod = kVsyncPeriod,
-                                                       .vrrConfig = vrrConfig};
+                                                       .vrrConfig = vrrConfig,
+                                                       .hdrOutputType = hdrOutputType};
 
         EXPECT_CALL(*mHal, getDisplayConfigurations(kHwcDisplayId, _, _))
                 .WillOnce(DoAll(SetArgPointee<2>(std::vector<hal::DisplayConfiguration>{
@@ -410,6 +412,7 @@
         EXPECT_EQ(modes.front().configGroup, kConfigGroup);
         EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
         EXPECT_EQ(modes.front().vrrConfig, vrrConfig);
+        EXPECT_EQ(modes.front().hdrOutputType, hdrOutputType);
         if (!FlagManager::getInstance().correct_dpi_with_display_size()) {
             EXPECT_EQ(modes.front().dpiX, -1);
             EXPECT_EQ(modes.front().dpiY, -1);
@@ -435,6 +438,7 @@
         EXPECT_EQ(modes.front().configGroup, kConfigGroup);
         EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
         EXPECT_EQ(modes.front().vrrConfig, vrrConfig);
+        EXPECT_EQ(modes.front().hdrOutputType, hdrOutputType);
         EXPECT_EQ(modes.front().dpiX, kDpi);
         EXPECT_EQ(modes.front().dpiY, kDpi);
 
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
index 767000e..53a9062 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
@@ -584,7 +584,7 @@
 
     auto layer = createLegacyAndFrontedEndLayer(1);
     showLayer(1);
-    setFrameRate(1, (33_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_GTE,
+    setFrameRate(1, (33_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE,
                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
     setFrameRateCategory(1, 0);
 
@@ -623,7 +623,7 @@
 
     auto layer = createLegacyAndFrontedEndLayer(1);
     showLayer(1);
-    setFrameRate(1, (33_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_GTE,
+    setFrameRate(1, (33_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE,
                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
     setFrameRateCategory(1, 0);
 
@@ -654,6 +654,72 @@
     EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[0].frameRateCategory);
 }
 
+TEST_F(LayerHistoryIntegrationTest, oneLayerGteNoVote_arr) {
+    SET_FLAG_FOR_TEST(flags::arr_setframerate_gte_enum, true);
+    // Set the test to be on a vrr mode.
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    mSelector->setActiveMode(kVrrModeId, HI_FPS);
+
+    auto layer = createLegacyAndFrontedEndLayer(1);
+    showLayer(1);
+    setFrameRate(1, (0_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    // Layer is active but GTE with 0 should be considered NoVote, thus nothing from summarize.
+    ASSERT_EQ(0u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer became inactive.
+    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_EQ(0u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, oneLayerGteNoVote_mrr) {
+    SET_FLAG_FOR_TEST(flags::arr_setframerate_gte_enum, true);
+    // True by default on MRR devices as well, but the device is not set to VRR mode.
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+    auto layer = createLegacyAndFrontedEndLayer(1);
+    showLayer(1);
+    setFrameRate(1, (0_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_GTE,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+    setFrameRateCategory(1, 0);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    // Layer is active but GTE with 0 should be considered NoVote, thus nothing from summarize.
+    ASSERT_EQ(0u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer became inactive.
+    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_EQ(0u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
 TEST_F(LayerHistoryIntegrationTest, oneLayerExplicitVoteWithCategory_vrrFeatureOff) {
     SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, false);
 
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index 80b2b8d..9b3cba5 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -4667,5 +4667,47 @@
         EXPECT_EQ(120_Hz, primaryRefreshRates[i].modePtr->getPeakFps());
     }
 }
+
+TEST_P(RefreshRateSelectorTest, getSupportedFrameRates) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
+        return;
+    }
+
+    auto selector = createSelector(kModes_60_90, kModeId90);
+    const FpsRange range60 = {0_Hz, 60_Hz};
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId60, {range60, range60}, {range60, range60}}));
+
+    // Irrespective of the policy we get the full range of possible frame rates
+    const std::vector<float> expected = {90.0f, 60.0f, 45.0f, 30.0f, 22.5f, 20.0f};
+
+    const auto allSupportedFrameRates = selector.getSupportedFrameRates();
+    ASSERT_EQ(expected.size(), allSupportedFrameRates.size());
+    for (size_t i = 0; i < expected.size(); i++) {
+        EXPECT_EQ(expected[i], allSupportedFrameRates[i])
+                << "expected " << expected[i] << " received " << allSupportedFrameRates[i];
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, getSupportedFrameRatesArr) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
+        return;
+    }
+
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    const auto selector = createSelector(kVrrMode_120, kModeId120);
+
+    const std::vector<float> expected = {120.0f, 80.0f,   60.0f, 48.0f,   40.0f, 34.285f,
+                                         30.0f,  26.666f, 24.0f, 21.818f, 20.0f};
+
+    const auto allSupportedFrameRates = selector.getSupportedFrameRates();
+    ASSERT_EQ(expected.size(), allSupportedFrameRates.size());
+    constexpr float kEpsilon = 0.001f;
+    for (size_t i = 0; i < expected.size(); i++) {
+        EXPECT_TRUE(std::abs(expected[i] - allSupportedFrameRates[i]) <= kEpsilon)
+                << "expected " << expected[i] << " received " << allSupportedFrameRates[i];
+    }
+}
 } // namespace
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index 8699621..b0dd5c2 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -34,7 +34,7 @@
                                                static_cast<hal::HWConfigId>(        \
                                                        ftl::to_underlying(modeId)), \
                                                _, _))                               \
-            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)))
+            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(hal::Error::NONE)))
 
 namespace android {
 namespace {
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp
index 20a3315..6cc6322 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp
@@ -18,12 +18,13 @@
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
 #include <gui/SurfaceComposerClient.h>
+#include "DisplayHardware/Hal.h"
 #include "DisplayTransactionTestHelpers.h"
 
 namespace android {
 
 using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
-using android::hardware::graphics::composer::V2_1::Error;
+using android::hardware::graphics::composer::hal::Error;
 
 class NotifyExpectedPresentTest : public DisplayTransactionTest {
 public:
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
index c3934e6..3ae5ed9 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
@@ -21,6 +21,8 @@
 
 #include "CommitAndCompositeTest.h"
 
+#include "DisplayHardware/Hal.h"
+
 using namespace std::chrono_literals;
 using testing::_;
 using testing::Return;
@@ -40,7 +42,7 @@
     EXPECT_CALL(*mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _, _)).WillOnce([] {
         constexpr Duration kMockHwcRunTime = 20ms;
         std::this_thread::sleep_for(kMockHwcRunTime);
-        return hardware::graphics::composer::V2_1::Error::NONE;
+        return hardware::graphics::composer::hal::Error::NONE;
     });
     EXPECT_CALL(*mPowerAdvisor, reportActualWorkDuration()).Times(1);
 
@@ -61,7 +63,7 @@
     EXPECT_CALL(*mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _, _)).WillOnce([] {
         constexpr Duration kMockHwcRunTime = 20ms;
         std::this_thread::sleep_for(kMockHwcRunTime);
-        return hardware::graphics::composer::V2_1::Error::NONE;
+        return hardware::graphics::composer::hal::Error::NONE;
     });
     EXPECT_CALL(*mPowerAdvisor, reportActualWorkDuration()).Times(0);
 
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index 88052db..0d5266e 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -20,6 +20,7 @@
 
 #include "DisplayHardware/ComposerHal.h"
 #include "DisplayHardware/HWC2.h"
+#include "DisplayHardware/Hal.h"
 
 namespace android {
 
@@ -34,9 +35,9 @@
 using android::hardware::graphics::common::V1_2::Dataspace;
 using android::hardware::graphics::common::V1_2::PixelFormat;
 
+using android::hardware::graphics::composer::hal::Error;
 using android::hardware::graphics::composer::V2_1::Config;
 using android::hardware::graphics::composer::V2_1::Display;
-using android::hardware::graphics::composer::V2_1::Error;
 using android::hardware::graphics::composer::V2_1::IComposer;
 using android::hardware::graphics::composer::V2_1::Layer;
 using android::hardware::graphics::composer::V2_4::IComposerCallback;
@@ -142,8 +143,8 @@
                  V2_4::Error(Display, Config, std::vector<VsyncPeriodNanos>*));
     MOCK_METHOD2(getDisplayVsyncPeriod, V2_4::Error(Display, VsyncPeriodNanos*));
     MOCK_METHOD4(setActiveConfigWithConstraints,
-                 V2_4::Error(Display, Config, const IComposerClient::VsyncPeriodChangeConstraints&,
-                             VsyncPeriodChangeTimeline*));
+                 Error(Display, Config, const IComposerClient::VsyncPeriodChangeConstraints&,
+                       VsyncPeriodChangeTimeline*));
     MOCK_METHOD2(setAutoLowLatencyMode, V2_4::Error(Display, bool));
     MOCK_METHOD2(setBootDisplayConfig, Error(Display, Config));
     MOCK_METHOD1(clearBootDisplayConfig, Error(Display));
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
index fa74492..ec065a7 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <gmock/gmock.h>
+#include <cstdint>
 
 #include "DisplayHardware/HWC2.h"
 
@@ -85,8 +86,8 @@
     MOCK_METHOD(ftl::Future<hal::Error>, setDisplayBrightness,
                 (float, float, const Hwc2::Composer::DisplayBrightnessOptions &), (override));
     MOCK_METHOD(hal::Error, setActiveConfigWithConstraints,
-                (hal::HWConfigId, const hal::VsyncPeriodChangeConstraints &,
-                 hal::VsyncPeriodChangeTimeline *),
+                (hal::HWConfigId, const hal::VsyncPeriodChangeConstraints&,
+                 hal::VsyncPeriodChangeTimeline*),
                 (override));
     MOCK_METHOD(hal::Error, setBootDisplayConfig, (hal::HWConfigId), (override));
     MOCK_METHOD(hal::Error, clearBootDisplayConfig, (), (override));
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
index c976697..3036fec 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -34,6 +34,8 @@
     MOCK_METHOD(void, onHotplugReceived, (PhysicalDisplayId, bool), (override));
     MOCK_METHOD(void, onHotplugConnectionError, (int32_t), (override));
     MOCK_METHOD(void, onModeChanged, (const scheduler::FrameRateMode&), (override));
+    MOCK_METHOD(void, onModeRejected, (PhysicalDisplayId displayId, DisplayModeId modeId),
+                (override));
     MOCK_METHOD(void, onFrameRateOverridesChanged,
                 (PhysicalDisplayId, std::vector<FrameRateOverride>), (override));
     MOCK_METHOD(void, dump, (std::string&), (const, override));
diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
index d45cc66..25dd68e 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
@@ -29,7 +29,7 @@
     MOCK_METHOD(void, onChoreographerAttached, (), (override));
     MOCK_METHOD(void, onExpectedPresentTimePosted, (TimePoint, ftl::NonNull<DisplayModePtr>, Fps),
                 (override));
-    MOCK_METHOD(void, onCommitNotComposited, (PhysicalDisplayId), (override));
+    MOCK_METHOD(void, onCommitNotComposited, (), (override));
     MOCK_METHOD(void, vrrDisplayIdle, (bool), (override));
 };
 
@@ -39,7 +39,7 @@
     void kernelTimerChanged(bool) override {}
     void onChoreographerAttached() override {}
     void onExpectedPresentTimePosted(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override {}
-    void onCommitNotComposited(PhysicalDisplayId) override {}
+    void onCommitNotComposited() override {}
     void vrrDisplayIdle(bool) override {}
 };