Merge "Update mock classes to reflect powerhal changes" into main
diff --git a/include/ftl/details/function.h b/include/ftl/details/function.h
new file mode 100644
index 0000000..35c5a8b
--- /dev/null
+++ b/include/ftl/details/function.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <type_traits>
+
+namespace android::ftl::details {
+
+// The maximum allowed value for the template argument `N` in
+// `ftl::Function<F, N>`.
+constexpr size_t kFunctionMaximumN = 14;
+
+// Converts a member function pointer type `Ret(Class::*)(Args...)` to an equivalent non-member
+// function type `Ret(Args...)`.
+
+template <typename>
+struct remove_member_function_pointer;
+
+template <typename Class, typename Ret, typename... Args>
+struct remove_member_function_pointer<Ret (Class::*)(Args...)> {
+  using type = Ret(Args...);
+};
+
+template <typename Class, typename Ret, typename... Args>
+struct remove_member_function_pointer<Ret (Class::*)(Args...) const> {
+  using type = Ret(Args...);
+};
+
+template <auto MemberFunction>
+using remove_member_function_pointer_t =
+    typename remove_member_function_pointer<decltype(MemberFunction)>::type;
+
+// Helper functions for binding to the supported targets.
+
+template <typename Ret, typename... Args>
+auto bind_opaque_no_op() -> Ret (*)(void*, Args...) {
+  return [](void*, Args...) -> Ret {
+    if constexpr (!std::is_void_v<Ret>) {
+      return Ret{};
+    }
+  };
+}
+
+template <typename F, typename Ret, typename... Args>
+auto bind_opaque_function_object(const F&) -> Ret (*)(void*, Args...) {
+  return [](void* opaque, Args... args) -> Ret {
+    return std::invoke(*static_cast<F*>(opaque), std::forward<Args>(args)...);
+  };
+}
+
+template <auto MemberFunction, typename Class, typename Ret, typename... Args>
+auto bind_member_function(Class* instance, Ret (*)(Args...) = nullptr) {
+  return [instance](Args... args) -> Ret {
+    return std::invoke(MemberFunction, instance, std::forward<Args>(args)...);
+  };
+}
+
+template <auto FreeFunction, typename Ret, typename... Args>
+auto bind_free_function(Ret (*)(Args...) = nullptr) {
+  return [](Args... args) -> Ret { return std::invoke(FreeFunction, std::forward<Args>(args)...); };
+}
+
+// Traits class for the opaque storage used by Function.
+
+template <std::size_t N>
+struct function_opaque_storage {
+  // The actual type used for the opaque storage. An `N` of zero specifies the minimum useful size,
+  // which allows a lambda with zero or one capture args.
+  using type = std::array<std::intptr_t, N + 1>;
+
+  template <typename S>
+  static constexpr bool require_trivially_copyable = std::is_trivially_copyable_v<S>;
+
+  template <typename S>
+  static constexpr bool require_trivially_destructible = std::is_trivially_destructible_v<S>;
+
+  template <typename S>
+  static constexpr bool require_will_fit_in_opaque_storage = sizeof(S) <= sizeof(type);
+
+  template <typename S>
+  static constexpr bool require_alignment_compatible =
+      std::alignment_of_v<S> <= std::alignment_of_v<type>;
+
+  // Copies `src` into the opaque storage, and returns that storage.
+  template <typename S>
+  static type opaque_copy(const S& src) {
+    // TODO: Replace with C++20 concepts/constraints which can give more details.
+    static_assert(require_trivially_copyable<S>,
+                  "ftl::Function can only store lambdas that capture trivially copyable data.");
+    static_assert(
+        require_trivially_destructible<S>,
+        "ftl::Function can only store lambdas that capture trivially destructible data.");
+    static_assert(require_will_fit_in_opaque_storage<S>,
+                  "ftl::Function has limited storage for lambda captured state. Maybe you need to "
+                  "increase N?");
+    static_assert(require_alignment_compatible<S>);
+
+    type opaque;
+    std::memcpy(opaque.data(), &src, sizeof(S));
+    return opaque;
+  }
+};
+
+// Traits class to help determine the template parameters to use for a ftl::Function, given a
+// function object.
+
+template <typename F, typename = decltype(&F::operator())>
+struct function_traits {
+  // The function type `F` with which to instantiate the `Function<F, N>` template.
+  using type = remove_member_function_pointer_t<&F::operator()>;
+
+  // The (minimum) size `N` with which to instantiate the `Function<F, N>` template.
+  static constexpr std::size_t size =
+      (std::max(sizeof(std::intptr_t), sizeof(F)) - 1) / sizeof(std::intptr_t);
+};
+
+}  // namespace android::ftl::details
diff --git a/include/ftl/function.h b/include/ftl/function.h
new file mode 100644
index 0000000..3538ca4
--- /dev/null
+++ b/include/ftl/function.h
@@ -0,0 +1,297 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+#include <ftl/details/function.h>
+
+namespace android::ftl {
+
+// ftl::Function<F, N> is a container for function object, and can mostly be used in place of
+// std::function<F>.
+//
+// Unlike std::function<F>, a ftl::Function<F, N>:
+//
+//  * Uses a static amount of memory (controlled by N), and never any dynamic allocation.
+//  * Satisfies the std::is_trivially_copyable<> trait.
+//  * Satisfies the std::is_trivially_destructible<> trait.
+//
+// However those same limits are also required from the contained function object in turn.
+//
+// The size of a ftl::Function<F, N> is guaranteed to be:
+//
+//     sizeof(std::intptr_t) * (N + 2)
+//
+// A ftl::Function<F, N> can always be implicitly converted to a larger size ftl::Function<F, M>.
+// Trying to convert the other way leads to a compilation error.
+//
+// A default-constructed ftl::Function is in an empty state. The operator bool() overload returns
+// false in this state. It is undefined behavior to attempt to invoke the function in this state.
+//
+// The ftl::Function<F, N> can also be constructed or assigned from ftl::no_op. This sets up the
+// ftl::Function to be non-empty, with a function that when called does nothing except
+// default-constructs a return value.
+//
+// The ftl::make_function() helpers construct a ftl::Function<F, N>, including deducing the
+// values of F and N from the arguments it is given.
+//
+// The static ftl::Function<F, N>::make() helpers construct a ftl::Function<F, N> without that
+// deduction, and also allow for implicit argument conversion if the target being called needs them.
+//
+// The construction helpers allow any of the following types of functions to be stored:
+//
+//  * Any SMALL function object (as defined by the C++ Standard), such as a lambda with a small
+//    capture, or other "functor". The requirements are:
+//
+//      1) The function object must be trivial to destroy (in fact, the destructor will never
+//         actually be called once copied to the internal storage).
+//      2) The function object must be trivial to copy (the raw bytes will be copied as the
+//         ftl::Function<F, N> is copied/moved).
+//      3) The size of the function object cannot be larger than sizeof(std::intptr_t) * (N + 1),
+//         and it cannot require stricter alignment than alignof(std::intptr_t).
+//
+//    With the default of N=0, a lambda can only capture a single pointer-sized argument. This is
+//    enough to capture `this`, which is why N=0 is the default.
+//
+//  * A member function, with the address passed as the template value argument to the construction
+//    helper function, along with the instance pointer needed to invoke it passed as an ordinary
+//    argument.
+//
+//        ftl::make_function<&Class::member_function>(this);
+//
+//    Note that the indicated member function will be invoked non-virtually. If you need it to be
+//    invoked virtually, you should invoke it yourself with a small lambda like so:
+//
+//        ftl::function([this] { virtual_member_function(); });
+//
+//  * An ordinary function ("free function"), with the address of the function passed as a template
+//    value argument.
+//
+//        ftl::make_function<&std::atoi>();
+//
+//   As with the member function helper, as the function is known at compile time, it will be called
+//   directly.
+//
+// Example usage:
+//
+//   class MyClass {
+//    public:
+//     void on_event() const {}
+//     int on_string(int*, std::string_view) { return 1; }
+//
+//     auto get_function() {
+//       return ftl::function([this] { on_event(); });
+//     }
+//   } cls;
+//
+//   // A function container with no arguments, and returning no value.
+//   ftl::Function<void()> f;
+//
+//   // Construct a ftl::Function containing a small lambda.
+//   f = cls.get_function();
+//
+//   // Construct a ftl::Function that calls `cls.on_event()`.
+//   f = ftl::function<&MyClass::on_event>(&cls);
+//
+//   // Create a do-nothing function.
+//   f = ftl::no_op;
+//
+//   // Invoke the contained function.
+//   f();
+//
+//   // Also invokes it.
+//   std::invoke(f);
+//
+//   // Create a typedef to give a more meaningful name and bound the size.
+//   using MyFunction = ftl::Function<int(std::string_view), 2>;
+//   int* ptr = nullptr;
+//   auto f1 = MyFunction::make_function(
+//       [cls = &cls, ptr](std::string_view sv) {
+//           return cls->on_string(ptr, sv);
+//       });
+//   int r = f1("abc"sv);
+//
+//   // Returns a default-constructed int (0).
+//   f1 = ftl::no_op;
+//   r = f1("abc"sv);
+//   assert(r == 0);
+
+template <typename F, std::size_t N = 0>
+class Function;
+
+// Used to construct a Function that does nothing.
+struct NoOpTag {};
+
+constexpr NoOpTag no_op;
+
+// Detects that a type is a `ftl::Function<F, N>` regardless of what `F` and `N` are.
+template <typename>
+struct is_function : public std::false_type {};
+
+template <typename F, std::size_t N>
+struct is_function<Function<F, N>> : public std::true_type {};
+
+template <typename T>
+constexpr bool is_function_v = is_function<T>::value;
+
+template <typename Ret, typename... Args, std::size_t N>
+class Function<Ret(Args...), N> final {
+  // Enforce a valid size, with an arbitrary maximum allowed size for the container of
+  // sizeof(std::intptr_t) * 16, though that maximum can be relaxed.
+  static_assert(N <= details::kFunctionMaximumN);
+
+  using OpaqueStorageTraits = details::function_opaque_storage<N>;
+
+ public:
+  // Defining result_type allows ftl::Function to be substituted for std::function.
+  using result_type = Ret;
+
+  // Constructs an empty ftl::Function.
+  Function() = default;
+
+  // Constructing or assigning from nullptr_t also creates an empty ftl::Function.
+  Function(std::nullptr_t) {}
+  Function& operator=(std::nullptr_t) { return *this = Function(nullptr); }
+
+  // Constructing from NoOpTag sets up a a special no-op function which is valid to call, and which
+  // returns a default constructed return value.
+  Function(NoOpTag) : function_(details::bind_opaque_no_op<Ret, Args...>()) {}
+  Function& operator=(NoOpTag) { return *this = Function(no_op); }
+
+  // Constructing/assigning from a function object stores a copy of that function object, however:
+  //  * It must be trivially copyable, as the implementation makes a copy with memcpy().
+  //  * It must be trivially destructible, as the implementation doesn't destroy the copy!
+  //  * It must fit in the limited internal storage, which enforces size/alignment restrictions.
+
+  template <typename F, typename = std::enable_if_t<std::is_invocable_r_v<Ret, F, Args...>>>
+  Function(const F& f)
+      : opaque_(OpaqueStorageTraits::opaque_copy(f)),
+        function_(details::bind_opaque_function_object<F, Ret, Args...>(f)) {}
+
+  template <typename F, typename = std::enable_if_t<std::is_invocable_r_v<Ret, F, Args...>>>
+  Function& operator=(const F& f) noexcept {
+    return *this = Function{OpaqueStorageTraits::opaque_copy(f),
+                            details::bind_opaque_function_object<F, Ret, Args...>(f)};
+  }
+
+  // Constructing/assigning from a smaller ftl::Function is allowed, but not anything else.
+
+  template <std::size_t M>
+  Function(const Function<Ret(Args...), M>& other)
+      : opaque_{OpaqueStorageTraits::opaque_copy(other.opaque_)}, function_(other.function_) {}
+
+  template <std::size_t M>
+  auto& operator=(const Function<Ret(Args...), M>& other) {
+    return *this = Function{OpaqueStorageTraits::opaque_copy(other.opaque_), other.function_};
+  }
+
+  // Returns true if a function is set.
+  explicit operator bool() const { return function_ != nullptr; }
+
+  // Checks if the other function has the same contents as this one.
+  bool operator==(const Function& other) const {
+    return other.opaque_ == opaque_ && other.function_ == function_;
+  }
+  bool operator!=(const Function& other) const { return !operator==(other); }
+
+  // Alternative way of testing for a function being set.
+  bool operator==(std::nullptr_t) const { return function_ == nullptr; }
+  bool operator!=(std::nullptr_t) const { return function_ != nullptr; }
+
+  // Invokes the function.
+  Ret operator()(Args... args) const {
+    return std::invoke(function_, opaque_.data(), std::forward<Args>(args)...);
+  }
+
+  // Creation helper for function objects, such as lambdas.
+  template <typename F>
+  static auto make(const F& f) -> decltype(Function{f}) {
+    return Function{f};
+  }
+
+  // Creation helper for a class pointer and a compile-time chosen member function to call.
+  template <auto MemberFunction, typename Class>
+  static auto make(Class* instance) -> decltype(Function{
+      details::bind_member_function<MemberFunction>(instance,
+                                                    static_cast<Ret (*)(Args...)>(nullptr))}) {
+    return Function{details::bind_member_function<MemberFunction>(
+        instance, static_cast<Ret (*)(Args...)>(nullptr))};
+  }
+
+  // Creation helper for a compile-time chosen free function to call.
+  template <auto FreeFunction>
+  static auto make() -> decltype(Function{
+      details::bind_free_function<FreeFunction>(static_cast<Ret (*)(Args...)>(nullptr))}) {
+    return Function{
+        details::bind_free_function<FreeFunction>(static_cast<Ret (*)(Args...)>(nullptr))};
+  }
+
+ private:
+  // Needed so a Function<F, M> can be converted to a Function<F, N>.
+  template <typename, std::size_t>
+  friend class Function;
+
+  // The function pointer type of function stored in `function_`. The first argument is always
+  // `&opaque_`.
+  using StoredFunction = Ret(void*, Args...);
+
+  // The type of the opaque storage, used to hold an appropriate function object.
+  // The type stored here is ONLY known to the StoredFunction.
+  // We always use at least one std::intptr_t worth of storage, and always a multiple of that size.
+  using OpaqueStorage = typename OpaqueStorageTraits::type;
+
+  // Internal constructor for creating from a raw opaque blob + function pointer.
+  Function(const OpaqueStorage& opaque, StoredFunction* function)
+      : opaque_(opaque), function_(function) {}
+
+  // Note: `mutable` so that `operator() const` can use it.
+  mutable OpaqueStorage opaque_{};
+  StoredFunction* function_{nullptr};
+};
+
+// Makes a ftl::Function given a function object `F`.
+template <typename F, typename T = details::function_traits<F>>
+Function(const F&) -> Function<typename T::type, T::size>;
+
+template <typename F>
+auto make_function(const F& f) -> decltype(Function{f}) {
+  return Function{f};
+}
+
+// Makes a ftl::Function given a `MemberFunction` and a instance pointer to the associated `Class`.
+template <auto MemberFunction, typename Class>
+auto make_function(Class* instance)
+    -> decltype(Function{details::bind_member_function<MemberFunction>(
+        instance,
+        static_cast<details::remove_member_function_pointer_t<MemberFunction>*>(nullptr))}) {
+  return Function{details::bind_member_function<MemberFunction>(
+      instance, static_cast<details::remove_member_function_pointer_t<MemberFunction>*>(nullptr))};
+}
+
+// Makes a ftl::Function given an ordinary free function.
+template <auto FreeFunction>
+auto make_function() -> decltype(Function{
+    details::bind_free_function<FreeFunction>(static_cast<decltype(FreeFunction)>(nullptr))}) {
+  return Function{
+      details::bind_free_function<FreeFunction>(static_cast<decltype(FreeFunction)>(nullptr))};
+}
+
+}  // namespace android::ftl
diff --git a/libs/binder/include/binder/IPCThreadState.h b/libs/binder/include/binder/IPCThreadState.h
index 9347ce4..dc5b1a1 100644
--- a/libs/binder/include/binder/IPCThreadState.h
+++ b/libs/binder/include/binder/IPCThreadState.h
@@ -62,8 +62,13 @@
 
             /**
              * Returns the PID of the process which has made the current binder
-             * call. If not in a binder call, this will return getpid. If the
-             * call is oneway, this will return 0.
+             * call. If not in a binder call, this will return getpid.
+             *
+             * Warning: oneway transactions do not receive PID. Even if you expect
+             * a transaction to be synchronous, a misbehaving client could send it
+             * as an asynchronous call and result in a 0 PID here. Additionally, if
+             * there is a race and the calling process dies, the PID may still be
+             * 0 for a synchronous call.
              */
             [[nodiscard]] pid_t getCallingPid() const;
 
diff --git a/libs/binder/ndk/include_ndk/android/binder_ibinder.h b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
index db2d2c1..b1ab7b0 100644
--- a/libs/binder/ndk/include_ndk/android/binder_ibinder.h
+++ b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
@@ -390,6 +390,12 @@
  * calling process dies and is replaced with another process with elevated permissions and the same
  * PID.
  *
+ * Warning: oneway transactions do not receive PID. Even if you expect
+ * a transaction to be synchronous, a misbehaving client could send it
+ * as a synchronous call and result in a 0 PID here. Additionally, if
+ * there is a race and the calling process dies, the PID may still be
+ * 0 for a synchronous call.
+ *
  * Available since API level 29.
  *
  * \return calling pid or the current process's PID if this thread isn't processing a transaction.
diff --git a/libs/binder/rust/src/state.rs b/libs/binder/rust/src/state.rs
index a3a2562..8a06274 100644
--- a/libs/binder/rust/src/state.rs
+++ b/libs/binder/rust/src/state.rs
@@ -101,13 +101,16 @@
     /// dies and is replaced with another process with elevated permissions and
     /// the same PID.
     ///
+    /// Warning: oneway transactions do not receive PID. Even if you expect
+    /// a transaction to be synchronous, a misbehaving client could send it
+    /// as a synchronous call and result in a 0 PID here. Additionally, if
+    /// there is a race and the calling process dies, the PID may still be
+    /// 0 for a synchronous call.
+    ///
     /// Available since API level 29.
     ///
     /// \return calling pid or the current process's PID if this thread isn't
     /// processing a transaction.
-    ///
-    /// If the transaction being processed is a oneway transaction, then this
-    /// method will return 0.
     pub fn get_calling_pid() -> pid_t {
         // Safety: Safe FFI
         unsafe { sys::AIBinder_getCallingPid() }
diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp
index ea1b5e4..918680d 100644
--- a/libs/ftl/Android.bp
+++ b/libs/ftl/Android.bp
@@ -17,6 +17,7 @@
         "enum_test.cpp",
         "fake_guard_test.cpp",
         "flags_test.cpp",
+        "function_test.cpp",
         "future_test.cpp",
         "match_test.cpp",
         "mixins_test.cpp",
diff --git a/libs/ftl/function_test.cpp b/libs/ftl/function_test.cpp
new file mode 100644
index 0000000..91b5e08
--- /dev/null
+++ b/libs/ftl/function_test.cpp
@@ -0,0 +1,379 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ftl/function.h>
+#include <gtest/gtest.h>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <string_view>
+#include <type_traits>
+
+namespace android::test {
+namespace {
+
+// Create an alias to composite requirements defined by the trait class `T` for easier testing.
+template <typename T, typename S>
+inline constexpr bool is_opaquely_storable = (T::template require_trivially_copyable<S> &&
+                                              T::template require_trivially_destructible<S> &&
+                                              T::template require_will_fit_in_opaque_storage<S> &&
+                                              T::template require_alignment_compatible<S>);
+
+// `I` gives a count of sizeof(std::intptr_t) bytes , and `J` gives a raw count of bytes
+template <size_t I, size_t J = 0>
+struct KnownSizeFunctionObject {
+  using Data = std::array<std::byte, sizeof(std::intptr_t) * I + J>;
+  void operator()() const {};
+  Data data{};
+};
+
+}  // namespace
+
+// static_assert the expected type traits
+static_assert(std::is_invocable_r_v<void, ftl::Function<void()>>);
+static_assert(std::is_trivially_copyable_v<ftl::Function<void()>>);
+static_assert(std::is_trivially_destructible_v<ftl::Function<void()>>);
+static_assert(std::is_trivially_copy_constructible_v<ftl::Function<void()>>);
+static_assert(std::is_trivially_move_constructible_v<ftl::Function<void()>>);
+static_assert(std::is_trivially_copy_assignable_v<ftl::Function<void()>>);
+static_assert(std::is_trivially_move_assignable_v<ftl::Function<void()>>);
+
+template <typename T>
+using function_traits = ftl::details::function_traits<T>;
+
+// static_assert that the expected value of N is used for known function object sizes.
+static_assert(function_traits<KnownSizeFunctionObject<0, 0>>::size == 0);
+static_assert(function_traits<KnownSizeFunctionObject<0, 1>>::size == 0);
+static_assert(function_traits<KnownSizeFunctionObject<1, 0>>::size == 0);
+static_assert(function_traits<KnownSizeFunctionObject<1, 1>>::size == 1);
+static_assert(function_traits<KnownSizeFunctionObject<2, 0>>::size == 1);
+static_assert(function_traits<KnownSizeFunctionObject<2, 1>>::size == 2);
+
+// Check that is_function_v works
+static_assert(!ftl::is_function_v<KnownSizeFunctionObject<0>>);
+static_assert(!ftl::is_function_v<std::function<void()>>);
+static_assert(ftl::is_function_v<ftl::Function<void()>>);
+
+// static_assert what can and cannot be stored inside the opaque storage
+
+template <size_t N>
+using function_opaque_storage = ftl::details::function_opaque_storage<N>;
+
+// Function objects can be stored if they fit.
+static_assert(is_opaquely_storable<function_opaque_storage<0>, KnownSizeFunctionObject<0>>);
+static_assert(is_opaquely_storable<function_opaque_storage<0>, KnownSizeFunctionObject<1>>);
+static_assert(!is_opaquely_storable<function_opaque_storage<0>, KnownSizeFunctionObject<2>>);
+
+static_assert(is_opaquely_storable<function_opaque_storage<1>, KnownSizeFunctionObject<2>>);
+static_assert(!is_opaquely_storable<function_opaque_storage<1>, KnownSizeFunctionObject<3>>);
+
+static_assert(is_opaquely_storable<function_opaque_storage<2>, KnownSizeFunctionObject<3>>);
+static_assert(!is_opaquely_storable<function_opaque_storage<2>, KnownSizeFunctionObject<4>>);
+
+// Another opaque storage can be stored if it fits. This property is used to copy smaller
+// ftl::Functions into larger ones.
+static_assert(is_opaquely_storable<function_opaque_storage<2>, function_opaque_storage<0>::type>);
+static_assert(is_opaquely_storable<function_opaque_storage<2>, function_opaque_storage<1>::type>);
+static_assert(is_opaquely_storable<function_opaque_storage<2>, function_opaque_storage<2>::type>);
+static_assert(!is_opaquely_storable<function_opaque_storage<2>, function_opaque_storage<3>::type>);
+
+// Function objects that aren't trivially copyable or destroyable cannot be stored.
+auto lambda_capturing_unique_ptr = [ptr = std::unique_ptr<void*>()] { static_cast<void>(ptr); };
+static_assert(
+    !is_opaquely_storable<function_opaque_storage<2>, decltype(lambda_capturing_unique_ptr)>);
+
+// Keep in sync with "Example usage" in header file.
+TEST(Function, Example) {
+  using namespace std::string_view_literals;
+
+  class MyClass {
+   public:
+    void on_event() const {}
+    int on_string(int*, std::string_view) { return 1; }
+
+    auto get_function() {
+      return ftl::make_function([this] { on_event(); });
+    }
+  } cls;
+
+  // A function container with no arguments, and returning no value.
+  ftl::Function<void()> f;
+
+  // Construct a ftl::Function containing a small lambda.
+  f = cls.get_function();
+
+  // Construct a ftl::Function that calls `cls.on_event()`.
+  f = ftl::make_function<&MyClass::on_event>(&cls);
+
+  // Create a do-nothing function.
+  f = ftl::no_op;
+
+  // Invoke the contained function.
+  f();
+
+  // Also invokes it.
+  std::invoke(f);
+
+  // Create a typedef to give a more meaningful name and bound the size.
+  using MyFunction = ftl::Function<int(std::string_view), 2>;
+  int* ptr = nullptr;
+  auto f1 =
+      MyFunction::make([cls = &cls, ptr](std::string_view sv) { return cls->on_string(ptr, sv); });
+  int r = f1("abc"sv);
+
+  // Returns a default-constructed int (0).
+  f1 = ftl::no_op;
+  r = f1("abc"sv);
+  EXPECT_EQ(r, 0);
+}
+
+TEST(Function, BasicOperations) {
+  // Default constructible.
+  ftl::Function<int()> f;
+
+  // Compares as empty
+  EXPECT_FALSE(f);
+  EXPECT_TRUE(f == nullptr);
+  EXPECT_FALSE(f != nullptr);
+  EXPECT_TRUE(ftl::Function<int()>() == f);
+  EXPECT_FALSE(ftl::Function<int()>() != f);
+
+  // Assigning no_op sets it to not empty.
+  f = ftl::no_op;
+
+  // Verify it can be called, and that it returns a default constructed value.
+  EXPECT_EQ(f(), 0);
+
+  // Comparable when non-empty.
+  EXPECT_TRUE(f);
+  EXPECT_FALSE(f == nullptr);
+  EXPECT_TRUE(f != nullptr);
+  EXPECT_FALSE(ftl::Function<int()>() == f);
+  EXPECT_TRUE(ftl::Function<int()>() != f);
+
+  // Constructing from nullptr means empty.
+  f = ftl::Function<int()>{nullptr};
+  EXPECT_FALSE(f);
+
+  // Assigning nullptr means it is empty.
+  f = nullptr;
+  EXPECT_FALSE(f);
+
+  // Move construction
+  f = ftl::no_op;
+  ftl::Function<int()> g{std::move(f)};
+  EXPECT_TRUE(g != nullptr);
+
+  // Move assignment
+  f = nullptr;
+  f = std::move(g);
+  EXPECT_TRUE(f != nullptr);
+
+  // Copy construction
+  ftl::Function<int()> h{f};
+  EXPECT_TRUE(h != nullptr);
+
+  // Copy assignment
+  g = h;
+  EXPECT_TRUE(g != nullptr);
+}
+
+TEST(Function, CanMoveConstructFromLambda) {
+  auto lambda = [] {};
+  ftl::Function<void()> f{std::move(lambda)};
+}
+
+TEST(Function, TerseDeducedConstructAndAssignFromLambda) {
+  auto f = ftl::Function([] { return 1; });
+  EXPECT_EQ(f(), 1);
+
+  f = [] { return 2; };
+  EXPECT_EQ(f(), 2);
+}
+
+namespace {
+
+struct ImplicitConversionsHelper {
+  auto exact(int) -> int { return 0; }
+  auto inexact(long) -> short { return 0; }
+  // TODO: Switch to `auto templated(auto x)` with C++20
+  template <typename T>
+  T templated(T x) {
+    return x;
+  }
+
+  static auto static_exact(int) -> int { return 0; }
+  static auto static_inexact(long) -> short { return 0; }
+  // TODO: Switch to `static auto static_templated(auto x)` with C++20
+  template <typename T>
+  static T static_templated(T x) {
+    return x;
+  }
+};
+
+}  // namespace
+
+TEST(Function, ImplicitConversions) {
+  using Function = ftl::Function<int(int)>;
+  auto check = [](Function f) { return f(0); };
+  auto exact = [](int) -> int { return 0; };
+  auto inexact = [](long) -> short { return 0; };
+  auto templated = [](auto x) { return x; };
+
+  ImplicitConversionsHelper helper;
+
+  // Note, `check(nullptr)` would crash, so we can only check if it would be invocable.
+  static_assert(std::is_invocable_v<decltype(check), decltype(nullptr)>);
+
+  // Note: We invoke each of these to fully expand all the templates involved.
+  EXPECT_EQ(check(ftl::no_op), 0);
+
+  EXPECT_EQ(check(exact), 0);
+  EXPECT_EQ(check(inexact), 0);
+  EXPECT_EQ(check(templated), 0);
+
+  EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::exact>(&helper)), 0);
+  EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::inexact>(&helper)), 0);
+  EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::templated<int>>(&helper)), 0);
+
+  EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::static_exact>()), 0);
+  EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::static_inexact>()), 0);
+  EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::static_templated<int>>()), 0);
+}
+
+TEST(Function, MakeWithNonConstMemberFunction) {
+  struct Observer {
+    bool called = false;
+    void setCalled() { called = true; }
+  } observer;
+
+  auto f = ftl::make_function<&Observer::setCalled>(&observer);
+
+  f();
+
+  EXPECT_TRUE(observer.called);
+
+  EXPECT_TRUE(f == ftl::Function<void()>::make<&Observer::setCalled>(&observer));
+}
+
+TEST(Function, MakeWithConstMemberFunction) {
+  struct Observer {
+    mutable bool called = false;
+    void setCalled() const { called = true; }
+  } observer;
+
+  const auto f = ftl::make_function<&Observer::setCalled>(&observer);
+
+  f();
+
+  EXPECT_TRUE(observer.called);
+
+  EXPECT_TRUE(f == ftl::Function<void()>::make<&Observer::setCalled>(&observer));
+}
+
+TEST(Function, MakeWithConstClassPointer) {
+  const struct Observer {
+    mutable bool called = false;
+    void setCalled() const { called = true; }
+  } observer;
+
+  const auto f = ftl::make_function<&Observer::setCalled>(&observer);
+
+  f();
+
+  EXPECT_TRUE(observer.called);
+
+  EXPECT_TRUE(f == ftl::Function<void()>::make<&Observer::setCalled>(&observer));
+}
+
+TEST(Function, MakeWithNonCapturingLambda) {
+  auto f = ftl::make_function([](int a, int b) { return a + b; });
+  EXPECT_EQ(f(1, 2), 3);
+}
+
+TEST(Function, MakeWithCapturingLambda) {
+  bool called = false;
+  auto f = ftl::make_function([&called](int a, int b) {
+    called = true;
+    return a + b;
+  });
+  EXPECT_EQ(f(1, 2), 3);
+  EXPECT_TRUE(called);
+}
+
+TEST(Function, MakeWithCapturingMutableLambda) {
+  bool called = false;
+  auto f = ftl::make_function([&called](int a, int b) mutable {
+    called = true;
+    return a + b;
+  });
+  EXPECT_EQ(f(1, 2), 3);
+  EXPECT_TRUE(called);
+}
+
+TEST(Function, MakeWithThreePointerCapturingLambda) {
+  bool my_bool = false;
+  int my_int = 0;
+  float my_float = 0.f;
+
+  auto f = ftl::make_function(
+      [ptr_bool = &my_bool, ptr_int = &my_int, ptr_float = &my_float](int a, int b) mutable {
+        *ptr_bool = true;
+        *ptr_int = 1;
+        *ptr_float = 1.f;
+
+        return a + b;
+      });
+
+  EXPECT_EQ(f(1, 2), 3);
+
+  EXPECT_TRUE(my_bool);
+  EXPECT_EQ(my_int, 1);
+  EXPECT_EQ(my_float, 1.f);
+}
+
+TEST(Function, MakeWithFreeFunction) {
+  auto f = ftl::make_function<&std::make_unique<int, int>>();
+  std::unique_ptr<int> unique_int = f(1);
+  ASSERT_TRUE(unique_int);
+  EXPECT_EQ(*unique_int, 1);
+}
+
+TEST(Function, CopyToLarger) {
+  int counter = 0;
+  ftl::Function<void()> a{[ptr_counter = &counter] { (*ptr_counter)++; }};
+  ftl::Function<void(), 1> b = a;
+  ftl::Function<void(), 2> c = a;
+
+  EXPECT_EQ(counter, 0);
+  a();
+  EXPECT_EQ(counter, 1);
+  b();
+  EXPECT_EQ(counter, 2);
+  c();
+  EXPECT_EQ(counter, 3);
+
+  b = [ptr_counter = &counter] { (*ptr_counter) += 2; };
+  c = [ptr_counter = &counter] { (*ptr_counter) += 3; };
+
+  b();
+  EXPECT_EQ(counter, 5);
+  c();
+  EXPECT_EQ(counter, 8);
+}
+
+}  // namespace android::test
diff --git a/libs/nativewindow/AHardwareBuffer.cpp b/libs/nativewindow/AHardwareBuffer.cpp
index e7b2195..745b6f3 100644
--- a/libs/nativewindow/AHardwareBuffer.cpp
+++ b/libs/nativewindow/AHardwareBuffer.cpp
@@ -113,6 +113,22 @@
                 AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM,
         "HAL and AHardwareBuffer pixel format don't match");
 
+static enum AHardwareBufferStatus filterStatus(status_t status) {
+    switch (status) {
+        case STATUS_OK:
+            return AHARDWAREBUFFER_STATUS_OK;
+        case STATUS_NO_MEMORY:
+            return AHARDWAREBUFFER_STATUS_NO_MEMORY;
+        case STATUS_BAD_VALUE:
+            return AHARDWAREBUFFER_STATUS_BAD_VALUE;
+        case STATUS_UNKNOWN_TRANSACTION:
+        case STATUS_INVALID_OPERATION:
+            return AHARDWAREBUFFER_STATUS_UNSUPPORTED;
+        default:
+            return AHARDWAREBUFFER_STATUS_UNKNOWN_ERROR;
+    }
+}
+
 // ----------------------------------------------------------------------------
 // Public functions
 // ----------------------------------------------------------------------------
@@ -511,6 +527,24 @@
     return AParcel_viewPlatformParcel(parcel)->write(*gb);
 }
 
+ADataSpace AHardwareBuffer_getDataSpace(const AHardwareBuffer* _Nonnull buffer) {
+    const GraphicBuffer* gb = AHardwareBuffer_to_GraphicBuffer(buffer);
+    if (!gb) return ADATASPACE_UNKNOWN;
+    ui::Dataspace dataspace = ui::Dataspace::UNKNOWN;
+    status_t status = gb->getDataspace(&dataspace);
+    if (status != OK) {
+        return ADATASPACE_UNKNOWN;
+    }
+    return static_cast<ADataSpace>(dataspace);
+}
+
+enum AHardwareBufferStatus AHardwareBuffer_setDataSpace(AHardwareBuffer* buffer,
+                                                        ADataSpace dataspace) {
+    GraphicBuffer* gb = AHardwareBuffer_to_GraphicBuffer(buffer);
+    auto& mapper = GraphicBufferMapper::get();
+    return filterStatus(mapper.setDataspace(gb->handle, static_cast<ui::Dataspace>(dataspace)));
+}
+
 // ----------------------------------------------------------------------------
 // VNDK functions
 // ----------------------------------------------------------------------------
@@ -552,6 +586,56 @@
     return NO_ERROR;
 }
 
+enum AHardwareBufferStatus AHardwareBuffer_allocate2(
+        const AHardwareBuffer_Desc* desc, const AHardwareBufferLongOptions* additionalOptions,
+        size_t additionalOptionsSize, AHardwareBuffer** outBuffer) {
+    (void)additionalOptions;
+    (void)additionalOptionsSize;
+    if (!outBuffer || !desc) return AHARDWAREBUFFER_STATUS_BAD_VALUE;
+    if (!AHardwareBuffer_isValidDescription(desc, /*log=*/true)) {
+        return AHARDWAREBUFFER_STATUS_BAD_VALUE;
+    }
+
+    int format = AHardwareBuffer_convertToPixelFormat(desc->format);
+    uint64_t usage = AHardwareBuffer_convertToGrallocUsageBits(desc->usage);
+
+    std::vector<GraphicBufferAllocator::AdditionalOptions> extras;
+    extras.reserve(additionalOptionsSize);
+    for (size_t i = 0; i < additionalOptionsSize; i++) {
+        extras.push_back(GraphicBufferAllocator::AdditionalOptions{additionalOptions[i].name,
+                                                                   additionalOptions[i].value});
+    }
+
+    const auto extrasCount = extras.size();
+    auto gbuffer = sp<GraphicBuffer>::make(GraphicBufferAllocator::AllocationRequest{
+            .importBuffer = true,
+            .width = desc->width,
+            .height = desc->height,
+            .format = format,
+            .layerCount = desc->layers,
+            .usage = usage,
+            .requestorName = std::string("AHardwareBuffer pid [") + std::to_string(getpid()) + "]",
+            .extras = std::move(extras),
+    });
+
+    status_t err = gbuffer->initCheck();
+    if (err != 0 || gbuffer->handle == nullptr) {
+        if (err == NO_MEMORY) {
+        GraphicBuffer::dumpAllocationsToSystemLog();
+        }
+        ALOGE("GraphicBuffer(w=%u, h=%u, lc=%u, extrasCount=%zd) failed (%s), handle=%p",
+              desc->width, desc->height, desc->layers, extrasCount, strerror(-err),
+              gbuffer->handle);
+        return filterStatus(err == 0 ? UNKNOWN_ERROR : err);
+    }
+
+    *outBuffer = AHardwareBuffer_from_GraphicBuffer(gbuffer.get());
+
+    // Ensure the buffer doesn't get destroyed when the sp<> goes away.
+    AHardwareBuffer_acquire(*outBuffer);
+    return AHARDWAREBUFFER_STATUS_OK;
+}
+
 // ----------------------------------------------------------------------------
 // Helpers implementation
 // ----------------------------------------------------------------------------
@@ -652,12 +736,9 @@
     return ahardwarebuffer_format;
 }
 
+// TODO: Remove, this is just to make an overly aggressive ABI checker happy
 int32_t AHardwareBuffer_getDataSpace(AHardwareBuffer* buffer) {
-    GraphicBuffer* gb = AHardwareBuffer_to_GraphicBuffer(buffer);
-    auto& mapper = GraphicBufferMapper::get();
-    ui::Dataspace dataspace = ui::Dataspace::UNKNOWN;
-    mapper.getDataspace(gb->handle, &dataspace);
-    return static_cast<int32_t>(dataspace);
+    return ::AHardwareBuffer_getDataSpace(buffer);
 }
 
 uint64_t AHardwareBuffer_convertToGrallocUsageBits(uint64_t usage) {
diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp
index dd5958d..f97eed5 100644
--- a/libs/nativewindow/ANativeWindow.cpp
+++ b/libs/nativewindow/ANativeWindow.cpp
@@ -152,31 +152,56 @@
 
 int32_t ANativeWindow_setBuffersDataSpace(ANativeWindow* window, int32_t dataSpace) {
     static_assert(static_cast<int>(ADATASPACE_UNKNOWN) == static_cast<int>(HAL_DATASPACE_UNKNOWN));
-    static_assert(static_cast<int>(STANDARD_MASK) == static_cast<int>(HAL_DATASPACE_STANDARD_MASK));
-    static_assert(static_cast<int>(STANDARD_UNSPECIFIED) == static_cast<int>(HAL_DATASPACE_STANDARD_UNSPECIFIED));
-    static_assert(static_cast<int>(STANDARD_BT709) == static_cast<int>(HAL_DATASPACE_STANDARD_BT709));
-    static_assert(static_cast<int>(STANDARD_BT601_625) == static_cast<int>(HAL_DATASPACE_STANDARD_BT601_625));
-    static_assert(static_cast<int>(STANDARD_BT601_625_UNADJUSTED) == static_cast<int>(HAL_DATASPACE_STANDARD_BT601_625_UNADJUSTED));
-    static_assert(static_cast<int>(STANDARD_BT601_525) == static_cast<int>(HAL_DATASPACE_STANDARD_BT601_525));
-    static_assert(static_cast<int>(STANDARD_BT601_525_UNADJUSTED) == static_cast<int>(HAL_DATASPACE_STANDARD_BT601_525_UNADJUSTED));
-    static_assert(static_cast<int>(STANDARD_BT470M) == static_cast<int>(HAL_DATASPACE_STANDARD_BT470M));
-    static_assert(static_cast<int>(STANDARD_FILM) == static_cast<int>(HAL_DATASPACE_STANDARD_FILM));
-    static_assert(static_cast<int>(STANDARD_DCI_P3) == static_cast<int>(HAL_DATASPACE_STANDARD_DCI_P3));
-    static_assert(static_cast<int>(STANDARD_ADOBE_RGB) == static_cast<int>(HAL_DATASPACE_STANDARD_ADOBE_RGB));
-    static_assert(static_cast<int>(TRANSFER_MASK) == static_cast<int>(HAL_DATASPACE_TRANSFER_MASK));
-    static_assert(static_cast<int>(TRANSFER_UNSPECIFIED) == static_cast<int>(HAL_DATASPACE_TRANSFER_UNSPECIFIED));
-    static_assert(static_cast<int>(TRANSFER_LINEAR) == static_cast<int>(HAL_DATASPACE_TRANSFER_LINEAR));
-    static_assert(static_cast<int>(TRANSFER_SMPTE_170M) == static_cast<int>(HAL_DATASPACE_TRANSFER_SMPTE_170M));
-    static_assert(static_cast<int>(TRANSFER_GAMMA2_2) == static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_2));
-    static_assert(static_cast<int>(TRANSFER_GAMMA2_6) == static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_6));
-    static_assert(static_cast<int>(TRANSFER_GAMMA2_8) == static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_8));
-    static_assert(static_cast<int>(TRANSFER_ST2084) == static_cast<int>(HAL_DATASPACE_TRANSFER_ST2084));
-    static_assert(static_cast<int>(TRANSFER_HLG) == static_cast<int>(HAL_DATASPACE_TRANSFER_HLG));
-    static_assert(static_cast<int>(RANGE_MASK) == static_cast<int>(HAL_DATASPACE_RANGE_MASK));
-    static_assert(static_cast<int>(RANGE_UNSPECIFIED) == static_cast<int>(HAL_DATASPACE_RANGE_UNSPECIFIED));
-    static_assert(static_cast<int>(RANGE_FULL) == static_cast<int>(HAL_DATASPACE_RANGE_FULL));
-    static_assert(static_cast<int>(RANGE_LIMITED) == static_cast<int>(HAL_DATASPACE_RANGE_LIMITED));
-    static_assert(static_cast<int>(RANGE_EXTENDED) == static_cast<int>(HAL_DATASPACE_RANGE_EXTENDED));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_MASK) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_MASK));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_UNSPECIFIED) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_UNSPECIFIED));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_BT709) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_BT709));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_BT601_625) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_BT601_625));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_BT601_625_UNADJUSTED) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_BT601_625_UNADJUSTED));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_BT601_525) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_BT601_525));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_BT601_525_UNADJUSTED) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_BT601_525_UNADJUSTED));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_BT470M) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_BT470M));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_FILM) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_FILM));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_DCI_P3) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_DCI_P3));
+    static_assert(static_cast<int>(ADATASPACE_STANDARD_ADOBE_RGB) ==
+                  static_cast<int>(HAL_DATASPACE_STANDARD_ADOBE_RGB));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_MASK) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_MASK));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_UNSPECIFIED) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_UNSPECIFIED));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_LINEAR) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_LINEAR));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_SMPTE_170M) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_SMPTE_170M));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_GAMMA2_2) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_2));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_GAMMA2_6) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_6));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_GAMMA2_8) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_8));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_ST2084) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_ST2084));
+    static_assert(static_cast<int>(ADATASPACE_TRANSFER_HLG) ==
+                  static_cast<int>(HAL_DATASPACE_TRANSFER_HLG));
+    static_assert(static_cast<int>(ADATASPACE_RANGE_MASK) ==
+                  static_cast<int>(HAL_DATASPACE_RANGE_MASK));
+    static_assert(static_cast<int>(ADATASPACE_RANGE_UNSPECIFIED) ==
+                  static_cast<int>(HAL_DATASPACE_RANGE_UNSPECIFIED));
+    static_assert(static_cast<int>(ADATASPACE_RANGE_FULL) ==
+                  static_cast<int>(HAL_DATASPACE_RANGE_FULL));
+    static_assert(static_cast<int>(ADATASPACE_RANGE_LIMITED) ==
+                  static_cast<int>(HAL_DATASPACE_RANGE_LIMITED));
+    static_assert(static_cast<int>(ADATASPACE_RANGE_EXTENDED) ==
+                  static_cast<int>(HAL_DATASPACE_RANGE_EXTENDED));
     static_assert(static_cast<int>(ADATASPACE_SRGB) == static_cast<int>(HAL_DATASPACE_V0_SRGB));
     static_assert(static_cast<int>(ADATASPACE_SCRGB) == static_cast<int>(HAL_DATASPACE_V0_SCRGB));
     static_assert(static_cast<int>(ADATASPACE_DISPLAY_P3) == static_cast<int>(HAL_DATASPACE_DISPLAY_P3));
diff --git a/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h b/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h
index 880c694..f145a2f 100644
--- a/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h
+++ b/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h
@@ -27,8 +27,8 @@
 
 #include <stdint.h>
 
-struct AHardwareBuffer;
-struct AHardwareBuffer_Desc;
+#include <vndk/hardware_buffer.h>
+
 struct ANativeWindowBuffer;
 
 namespace android {
@@ -46,11 +46,6 @@
 // convert HAL format to AHardwareBuffer format (note: this is a no-op)
 uint32_t AHardwareBuffer_convertToPixelFormat(uint32_t format);
 
-// retrieves a dataspace from the AHardwareBuffer metadata, if the device
-// support gralloc metadata. Returns UNKNOWN if gralloc metadata is not
-// supported.
-int32_t AHardwareBuffer_getDataSpace(AHardwareBuffer* buffer);
-
 // convert AHardwareBuffer usage bits to HAL usage bits (note: this is a no-op)
 uint64_t AHardwareBuffer_convertFromGrallocUsageBits(uint64_t usage);
 
diff --git a/libs/nativewindow/include/android/data_space.h b/libs/nativewindow/include/android/data_space.h
index eab21fb..d4278be 100644
--- a/libs/nativewindow/include/android/data_space.h
+++ b/libs/nativewindow/include/android/data_space.h
@@ -37,7 +37,7 @@
 /**
  * ADataSpace.
  */
-enum ADataSpace {
+enum ADataSpace : int32_t {
     /**
      * Default-assumption data space, when not explicitly specified.
      *
@@ -63,7 +63,7 @@
      * Defines the chromaticity coordinates of the source primaries in terms of
      * the CIE 1931 definition of x and y specified in ISO 11664-1.
      */
-    STANDARD_MASK = 63 << 16,
+    ADATASPACE_STANDARD_MASK = 63 << 16,
 
     /**
      * Chromacity coordinates are unknown or are determined by the application.
@@ -78,7 +78,7 @@
      * For all other formats standard is undefined, and implementations should use
      * an appropriate standard for the data represented.
      */
-    STANDARD_UNSPECIFIED = 0 << 16,
+    ADATASPACE_STANDARD_UNSPECIFIED = 0 << 16,
 
     /**
      * <pre>
@@ -91,7 +91,7 @@
      * Use the unadjusted KR = 0.2126, KB = 0.0722 luminance interpretation
      * for RGB conversion.
      */
-    STANDARD_BT709 = 1 << 16,
+    ADATASPACE_STANDARD_BT709 = 1 << 16,
 
     /**
      * <pre>
@@ -106,7 +106,7 @@
      *  to minimize the color shift into RGB space that uses BT.709
      *  primaries.
      */
-    STANDARD_BT601_625 = 2 << 16,
+    ADATASPACE_STANDARD_BT601_625 = 2 << 16,
 
     /**
      * <pre>
@@ -119,7 +119,7 @@
      * Use the unadjusted KR = 0.222, KB = 0.071 luminance interpretation
      * for RGB conversion.
      */
-    STANDARD_BT601_625_UNADJUSTED = 3 << 16,
+    ADATASPACE_STANDARD_BT601_625_UNADJUSTED = 3 << 16,
 
     /**
      * <pre>
@@ -134,7 +134,7 @@
      *  to minimize the color shift into RGB space that uses BT.709
      *  primaries.
      */
-    STANDARD_BT601_525 = 4 << 16,
+    ADATASPACE_STANDARD_BT601_525 = 4 << 16,
 
     /**
      * <pre>
@@ -147,7 +147,7 @@
      * Use the unadjusted KR = 0.212, KB = 0.087 luminance interpretation
      * for RGB conversion (as in SMPTE 240M).
      */
-    STANDARD_BT601_525_UNADJUSTED = 5 << 16,
+    ADATASPACE_STANDARD_BT601_525_UNADJUSTED = 5 << 16,
 
     /**
      * <pre>
@@ -160,7 +160,7 @@
      * Use the unadjusted KR = 0.2627, KB = 0.0593 luminance interpretation
      * for RGB conversion.
      */
-    STANDARD_BT2020 = 6 << 16,
+    ADATASPACE_STANDARD_BT2020 = 6 << 16,
 
     /**
      * <pre>
@@ -173,7 +173,7 @@
      * Use the unadjusted KR = 0.2627, KB = 0.0593 luminance interpretation
      * for RGB conversion using the linear domain.
      */
-    STANDARD_BT2020_CONSTANT_LUMINANCE = 7 << 16,
+    ADATASPACE_STANDARD_BT2020_CONSTANT_LUMINANCE = 7 << 16,
 
     /**
      * <pre>
@@ -186,7 +186,7 @@
      * Use the unadjusted KR = 0.30, KB = 0.11 luminance interpretation
      * for RGB conversion.
      */
-    STANDARD_BT470M = 8 << 16,
+    ADATASPACE_STANDARD_BT470M = 8 << 16,
 
     /**
      * <pre>
@@ -199,7 +199,7 @@
      * Use the unadjusted KR = 0.254, KB = 0.068 luminance interpretation
      * for RGB conversion.
      */
-    STANDARD_FILM = 9 << 16,
+    ADATASPACE_STANDARD_FILM = 9 << 16,
 
     /**
      * SMPTE EG 432-1 and SMPTE RP 431-2. (DCI-P3)
@@ -210,7 +210,7 @@
      *  red             0.680   0.320
      *  white (D65)     0.3127  0.3290</pre>
      */
-    STANDARD_DCI_P3 = 10 << 16,
+    ADATASPACE_STANDARD_DCI_P3 = 10 << 16,
 
     /**
      * Adobe RGB
@@ -221,7 +221,7 @@
      *  red             0.640   0.330
      *  white (D65)     0.3127  0.3290</pre>
      */
-    STANDARD_ADOBE_RGB = 11 << 16,
+    ADATASPACE_STANDARD_ADOBE_RGB = 11 << 16,
 
     /**
      * Transfer aspect
@@ -236,7 +236,7 @@
      * component. Implementation may apply the transfer function in RGB space
      * for all pixel formats if desired.
      */
-    TRANSFER_MASK = 31 << 22,
+    ADATASPACE_TRANSFER_MASK = 31 << 22,
 
     /**
      * Transfer characteristics are unknown or are determined by the
@@ -244,13 +244,13 @@
      *
      * Implementations should use the following transfer functions:
      *
-     * For YCbCr formats: use TRANSFER_SMPTE_170M
-     * For RGB formats: use TRANSFER_SRGB
+     * For YCbCr formats: use ADATASPACE_TRANSFER_SMPTE_170M
+     * For RGB formats: use ADATASPACE_TRANSFER_SRGB
      *
      * For all other formats transfer function is undefined, and implementations
      * should use an appropriate standard for the data represented.
      */
-    TRANSFER_UNSPECIFIED = 0 << 22,
+    ADATASPACE_TRANSFER_UNSPECIFIED = 0 << 22,
 
     /**
      * Linear transfer.
@@ -260,7 +260,7 @@
      *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
      *     E - corresponding electrical signal</pre>
      */
-    TRANSFER_LINEAR = 1 << 22,
+    ADATASPACE_TRANSFER_LINEAR = 1 << 22,
 
     /**
      * sRGB transfer.
@@ -271,7 +271,7 @@
      *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
      *     E - corresponding electrical signal</pre>
      */
-    TRANSFER_SRGB = 2 << 22,
+    ADATASPACE_TRANSFER_SRGB = 2 << 22,
 
     /**
      * SMPTE 170M transfer.
@@ -282,7 +282,7 @@
      *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
      *     E - corresponding electrical signal</pre>
      */
-    TRANSFER_SMPTE_170M = 3 << 22,
+    ADATASPACE_TRANSFER_SMPTE_170M = 3 << 22,
 
     /**
      * Display gamma 2.2.
@@ -292,7 +292,7 @@
      *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
      *     E - corresponding electrical signal</pre>
      */
-    TRANSFER_GAMMA2_2 = 4 << 22,
+    ADATASPACE_TRANSFER_GAMMA2_2 = 4 << 22,
 
     /**
      * Display gamma 2.6.
@@ -302,7 +302,7 @@
      *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
      *     E - corresponding electrical signal</pre>
      */
-    TRANSFER_GAMMA2_6 = 5 << 22,
+    ADATASPACE_TRANSFER_GAMMA2_6 = 5 << 22,
 
     /**
      * Display gamma 2.8.
@@ -312,7 +312,7 @@
      *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
      *     E - corresponding electrical signal</pre>
      */
-    TRANSFER_GAMMA2_8 = 6 << 22,
+    ADATASPACE_TRANSFER_GAMMA2_8 = 6 << 22,
 
     /**
      * SMPTE ST 2084 (Dolby Perceptual Quantizer).
@@ -328,7 +328,7 @@
      *         L = 1 corresponds to 10000 cd/m2
      *     E - corresponding electrical signal</pre>
      */
-    TRANSFER_ST2084 = 7 << 22,
+    ADATASPACE_TRANSFER_ST2084 = 7 << 22,
 
     /**
      * ARIB STD-B67 Hybrid Log Gamma.
@@ -344,7 +344,7 @@
      *          to reference white level of 100 cd/m2
      *      E - corresponding electrical signal</pre>
      */
-    TRANSFER_HLG = 8 << 22,
+    ADATASPACE_TRANSFER_HLG = 8 << 22,
 
     /**
      * Range aspect
@@ -352,7 +352,7 @@
      * Defines the range of values corresponding to the unit range of 0-1.
      * This is defined for YCbCr only, but can be expanded to RGB space.
      */
-    RANGE_MASK = 7 << 27,
+    ADATASPACE_RANGE_MASK = 7 << 27,
 
     /**
      * Range is unknown or are determined by the application.  Implementations
@@ -365,13 +365,13 @@
      * For all other formats range is undefined, and implementations should use
      * an appropriate range for the data represented.
      */
-    RANGE_UNSPECIFIED = 0 << 27,
+    ADATASPACE_RANGE_UNSPECIFIED = 0 << 27,
 
     /**
      * Full range uses all values for Y, Cb and Cr from
      * 0 to 2^b-1, where b is the bit depth of the color format.
      */
-    RANGE_FULL = 1 << 27,
+    ADATASPACE_RANGE_FULL = 1 << 27,
 
     /**
      * Limited range uses values 16/256*2^b to 235/256*2^b for Y, and
@@ -386,7 +386,7 @@
      * Luma (Y) samples should range from 64 to 940, inclusive
      * Chroma (Cb, Cr) samples should range from 64 to 960, inclusive
      */
-    RANGE_LIMITED = 2 << 27,
+    ADATASPACE_RANGE_LIMITED = 2 << 27,
 
     /**
      * Extended range is used for scRGB. Intended for use with
@@ -395,7 +395,7 @@
      * color outside the sRGB gamut.
      * Used to blend / merge multiple dataspaces on a single display.
      */
-    RANGE_EXTENDED = 3 << 27,
+    ADATASPACE_RANGE_EXTENDED = 3 << 27,
 
     /**
      * scRGB linear encoding
@@ -410,7 +410,8 @@
      *
      * Uses extended range, linear transfer and BT.709 standard.
      */
-    ADATASPACE_SCRGB_LINEAR = 406913024, // STANDARD_BT709 | TRANSFER_LINEAR | RANGE_EXTENDED
+    ADATASPACE_SCRGB_LINEAR = 406913024, // ADATASPACE_STANDARD_BT709 | ADATASPACE_TRANSFER_LINEAR |
+                                         // ADATASPACE_RANGE_EXTENDED
 
     /**
      * sRGB gamma encoding
@@ -425,7 +426,8 @@
      *
      * Uses full range, sRGB transfer BT.709 standard.
      */
-    ADATASPACE_SRGB = 142671872, // STANDARD_BT709 | TRANSFER_SRGB | RANGE_FULL
+    ADATASPACE_SRGB = 142671872, // ADATASPACE_STANDARD_BT709 | ADATASPACE_TRANSFER_SRGB |
+                                 // ADATASPACE_RANGE_FULL
 
     /**
      * scRGB
@@ -440,14 +442,16 @@
      *
      * Uses extended range, sRGB transfer and BT.709 standard.
      */
-    ADATASPACE_SCRGB = 411107328, // STANDARD_BT709 | TRANSFER_SRGB | RANGE_EXTENDED
+    ADATASPACE_SCRGB = 411107328, // ADATASPACE_STANDARD_BT709 | ADATASPACE_TRANSFER_SRGB |
+                                  // ADATASPACE_RANGE_EXTENDED
 
     /**
      * Display P3
      *
      * Uses full range, sRGB transfer and D65 DCI-P3 standard.
      */
-    ADATASPACE_DISPLAY_P3 = 143261696, // STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_FULL
+    ADATASPACE_DISPLAY_P3 = 143261696, // ADATASPACE_STANDARD_DCI_P3 | ADATASPACE_TRANSFER_SRGB |
+                                       // ADATASPACE_RANGE_FULL
 
     /**
      * ITU-R Recommendation 2020 (BT.2020)
@@ -456,7 +460,8 @@
      *
      * Uses full range, SMPTE 2084 (PQ) transfer and BT2020 standard.
      */
-    ADATASPACE_BT2020_PQ = 163971072, // STANDARD_BT2020 | TRANSFER_ST2084 | RANGE_FULL
+    ADATASPACE_BT2020_PQ = 163971072, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_ST2084 |
+                                      // ADATASPACE_RANGE_FULL
 
     /**
      * ITU-R Recommendation 2020 (BT.2020)
@@ -465,7 +470,8 @@
      *
      * Uses limited range, SMPTE 2084 (PQ) transfer and BT2020 standard.
      */
-    ADATASPACE_BT2020_ITU_PQ = 298188800, // STANDARD_BT2020 | TRANSFER_ST2084 | RANGE_LIMITED
+    ADATASPACE_BT2020_ITU_PQ = 298188800, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_ST2084
+                                          // | ADATASPACE_RANGE_LIMITED
 
     /**
      * Adobe RGB
@@ -475,7 +481,8 @@
      * Note: Application is responsible for gamma encoding the data as
      * a 2.2 gamma encoding is not supported in HW.
      */
-    ADATASPACE_ADOBE_RGB = 151715840, // STANDARD_ADOBE_RGB | TRANSFER_GAMMA2_2 | RANGE_FULL
+    ADATASPACE_ADOBE_RGB = 151715840, // ADATASPACE_STANDARD_ADOBE_RGB |
+                                      // ADATASPACE_TRANSFER_GAMMA2_2 | ADATASPACE_RANGE_FULL
 
     /**
      * JPEG File Interchange Format (JFIF)
@@ -484,7 +491,8 @@
      *
      * Uses full range, SMPTE 170M transfer and BT.601_625 standard.
      */
-    ADATASPACE_JFIF = 146931712, // STANDARD_BT601_625 | TRANSFER_SMPTE_170M | RANGE_FULL
+    ADATASPACE_JFIF = 146931712, // ADATASPACE_STANDARD_BT601_625 | ADATASPACE_TRANSFER_SMPTE_170M |
+                                 // ADATASPACE_RANGE_FULL
 
     /**
      * ITU-R Recommendation 601 (BT.601) - 625-line
@@ -493,7 +501,8 @@
      *
      * Uses limited range, SMPTE 170M transfer and BT.601_625 standard.
      */
-    ADATASPACE_BT601_625 = 281149440, // STANDARD_BT601_625 | TRANSFER_SMPTE_170M | RANGE_LIMITED
+    ADATASPACE_BT601_625 = 281149440, // ADATASPACE_STANDARD_BT601_625 |
+                                      // ADATASPACE_TRANSFER_SMPTE_170M | ADATASPACE_RANGE_LIMITED
 
     /**
      * ITU-R Recommendation 601 (BT.601) - 525-line
@@ -502,7 +511,8 @@
      *
      * Uses limited range, SMPTE 170M transfer and BT.601_525 standard.
      */
-    ADATASPACE_BT601_525 = 281280512, // STANDARD_BT601_525 | TRANSFER_SMPTE_170M | RANGE_LIMITED
+    ADATASPACE_BT601_525 = 281280512, // ADATASPACE_STANDARD_BT601_525 |
+                                      // ADATASPACE_TRANSFER_SMPTE_170M | ADATASPACE_RANGE_LIMITED
 
     /**
      * ITU-R Recommendation 2020 (BT.2020)
@@ -511,7 +521,8 @@
      *
      * Uses full range, SMPTE 170M transfer and BT2020 standard.
      */
-    ADATASPACE_BT2020 = 147193856, // STANDARD_BT2020 | TRANSFER_SMPTE_170M | RANGE_FULL
+    ADATASPACE_BT2020 = 147193856, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_SMPTE_170M |
+                                   // ADATASPACE_RANGE_FULL
 
     /**
      * ITU-R Recommendation 709 (BT.709)
@@ -520,7 +531,8 @@
      *
      * Uses limited range, SMPTE 170M transfer and BT.709 standard.
      */
-    ADATASPACE_BT709 = 281083904, // STANDARD_BT709 | TRANSFER_SMPTE_170M | RANGE_LIMITED
+    ADATASPACE_BT709 = 281083904, // ADATASPACE_STANDARD_BT709 | ADATASPACE_TRANSFER_SMPTE_170M |
+                                  // ADATASPACE_RANGE_LIMITED
 
     /**
      * SMPTE EG 432-1 and SMPTE RP 431-2
@@ -532,7 +544,8 @@
      * Note: Application is responsible for gamma encoding the data as
      * a 2.6 gamma encoding is not supported in HW.
      */
-    ADATASPACE_DCI_P3 = 155844608, // STANDARD_DCI_P3 | TRANSFER_GAMMA2_6 | RANGE_FULL
+    ADATASPACE_DCI_P3 = 155844608, // ADATASPACE_STANDARD_DCI_P3 | ADATASPACE_TRANSFER_GAMMA2_6 |
+                                   // ADATASPACE_RANGE_FULL
 
     /**
      * sRGB linear encoding
@@ -546,21 +559,24 @@
      *
      * Uses full range, linear transfer and BT.709 standard.
      */
-    ADATASPACE_SRGB_LINEAR = 138477568, // STANDARD_BT709 | TRANSFER_LINEAR | RANGE_FULL
+    ADATASPACE_SRGB_LINEAR = 138477568, // ADATASPACE_STANDARD_BT709 | ADATASPACE_TRANSFER_LINEAR |
+                                        // ADATASPACE_RANGE_FULL
 
     /**
      * Hybrid Log Gamma encoding
      *
      * Uses full range, hybrid log gamma transfer and BT2020 standard.
      */
-    ADATASPACE_BT2020_HLG = 168165376, // STANDARD_BT2020 | TRANSFER_HLG | RANGE_FULL
+    ADATASPACE_BT2020_HLG = 168165376, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_HLG |
+                                       // ADATASPACE_RANGE_FULL
 
     /**
      * ITU Hybrid Log Gamma encoding
      *
      * Uses limited range, hybrid log gamma transfer and BT2020 standard.
      */
-    ADATASPACE_BT2020_ITU_HLG = 302383104, // STANDARD_BT2020 | TRANSFER_HLG | RANGE_LIMITED
+    ADATASPACE_BT2020_ITU_HLG = 302383104, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_HLG |
+                                           // ADATASPACE_RANGE_LIMITED
 
     /**
      * Depth
@@ -574,7 +590,37 @@
      *
      * Embedded depth metadata following the dynamic depth specification.
      */
-    ADATASPACE_DYNAMIC_DEPTH = 4098
+    ADATASPACE_DYNAMIC_DEPTH = 4098,
+
+#ifndef ADATASPACE_SKIP_LEGACY_DEFINES
+    STANDARD_MASK = ADATASPACE_STANDARD_MASK,
+    STANDARD_UNSPECIFIED = ADATASPACE_STANDARD_UNSPECIFIED,
+    STANDARD_BT709 = ADATASPACE_STANDARD_BT709,
+    STANDARD_BT601_625 = ADATASPACE_STANDARD_BT601_625,
+    STANDARD_BT601_625_UNADJUSTED = ADATASPACE_STANDARD_BT601_625_UNADJUSTED,
+    STANDARD_BT601_525 = ADATASPACE_STANDARD_BT601_525,
+    STANDARD_BT601_525_UNADJUSTED = ADATASPACE_STANDARD_BT601_525_UNADJUSTED,
+    STANDARD_BT470M = ADATASPACE_STANDARD_BT470M,
+    STANDARD_BT2020 = ADATASPACE_STANDARD_BT2020,
+    STANDARD_FILM = ADATASPACE_STANDARD_FILM,
+    STANDARD_DCI_P3 = ADATASPACE_STANDARD_DCI_P3,
+    STANDARD_ADOBE_RGB = ADATASPACE_STANDARD_ADOBE_RGB,
+    TRANSFER_MASK = ADATASPACE_TRANSFER_MASK,
+    TRANSFER_UNSPECIFIED = ADATASPACE_TRANSFER_UNSPECIFIED,
+    TRANSFER_LINEAR = ADATASPACE_TRANSFER_LINEAR,
+    TRANSFER_SMPTE_170M = ADATASPACE_TRANSFER_SMPTE_170M,
+    TRANSFER_GAMMA2_2 = ADATASPACE_TRANSFER_GAMMA2_2,
+    TRANSFER_GAMMA2_6 = ADATASPACE_TRANSFER_GAMMA2_6,
+    TRANSFER_GAMMA2_8 = ADATASPACE_TRANSFER_GAMMA2_8,
+    TRANSFER_SRGB = ADATASPACE_TRANSFER_SRGB,
+    TRANSFER_ST2084 = ADATASPACE_TRANSFER_ST2084,
+    TRANSFER_HLG = ADATASPACE_TRANSFER_HLG,
+    RANGE_MASK = ADATASPACE_RANGE_MASK,
+    RANGE_UNSPECIFIED = ADATASPACE_RANGE_UNSPECIFIED,
+    RANGE_FULL = ADATASPACE_RANGE_FULL,
+    RANGE_LIMITED = ADATASPACE_RANGE_LIMITED,
+    RANGE_EXTENDED = ADATASPACE_RANGE_EXTENDED,
+#endif
 };
 
 __END_DECLS
diff --git a/libs/nativewindow/include/android/hardware_buffer.h b/libs/nativewindow/include/android/hardware_buffer.h
index 21798d0..e0e30c3 100644
--- a/libs/nativewindow/include/android/hardware_buffer.h
+++ b/libs/nativewindow/include/android/hardware_buffer.h
@@ -46,6 +46,9 @@
 #define ANDROID_HARDWARE_BUFFER_H
 
 #include <android/rect.h>
+#define ADATASPACE_SKIP_LEGACY_DEFINES
+#include <android/data_space.h>
+#undef ADATASPACE_SKIP_LEGACY_DEFINES
 #include <inttypes.h>
 #include <sys/cdefs.h>
 
diff --git a/libs/nativewindow/include/vndk/hardware_buffer.h b/libs/nativewindow/include/vndk/hardware_buffer.h
index 21931bb..4918601 100644
--- a/libs/nativewindow/include/vndk/hardware_buffer.h
+++ b/libs/nativewindow/include/vndk/hardware_buffer.h
@@ -21,6 +21,7 @@
 #include <android/hardware_buffer.h>
 
 #include <cutils/native_handle.h>
+#include <errno.h>
 
 __BEGIN_DECLS
 
@@ -105,6 +106,76 @@
     AHARDWAREBUFFER_USAGE_CAMERA_MASK               = 6UL << 16,
 };
 
+/**
+ * Additional options for AHardwareBuffer_allocate2. These correspond to
+ * android.hardware.graphics.common.ExtendableType
+ */
+typedef struct {
+    const char* _Nonnull name;
+    int64_t value;
+} AHardwareBufferLongOptions;
+
+enum AHardwareBufferStatus : int32_t {
+    /* Success, no error */
+    AHARDWAREBUFFER_STATUS_OK = 0,
+    /* There's insufficient memory to satisfy the request */
+    AHARDWAREBUFFER_STATUS_NO_MEMORY = -ENOMEM,
+    /* The given argument is invalid */
+    AHARDWAREBUFFER_STATUS_BAD_VALUE = -EINVAL,
+    /* The requested operation is not supported by the device */
+    AHARDWAREBUFFER_STATUS_UNSUPPORTED = -ENOSYS,
+    /* An unknown error occurred */
+    AHARDWAREBUFFER_STATUS_UNKNOWN_ERROR = (-2147483647 - 1),
+};
+
+/**
+ * Allocates a buffer that matches the passed AHardwareBuffer_Desc with additional options
+ *
+ * If allocation succeeds, the buffer can be used according to the
+ * usage flags specified in its description. If a buffer is used in ways
+ * not compatible with its usage flags, the results are undefined and
+ * may include program termination.
+ *
+ * @param desc The AHardwareBuffer_Desc that describes the allocation to request. Note that `stride`
+ *             is ignored.
+ * @param additionalOptions A pointer to an array of AHardwareBufferLongOptions with additional
+ *                          string key + long value options that may be specified. May be null if
+ *                          `additionalOptionsSize` is 0
+ * @param additionalOptionsSize The number of additional options to pass
+ * @param outBuffer The resulting buffer allocation
+ * @return AHARDWAREBUFFER_STATUS_OK on success
+ *         AHARDWAREBUFFER_STATUS_NO_MEMORY if there's insufficient resources for the allocation
+ *         AHARDWAREBUFFER_STATUS_BAD_VALUE if the provided description & options are not supported
+ *         by the device
+ *         AHARDWAREBUFFER_STATUS_UNKNOWN_ERROR for any other error
+ * any reason. The returned buffer has a reference count of 1.
+ */
+enum AHardwareBufferStatus AHardwareBuffer_allocate2(
+        const AHardwareBuffer_Desc* _Nonnull desc,
+        const AHardwareBufferLongOptions* _Nullable additionalOptions, size_t additionalOptionsSize,
+        AHardwareBuffer* _Nullable* _Nonnull outBuffer) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Queries the dataspace of the given AHardwareBuffer.
+ *
+ * @param buffer The non-null buffer for which to query the Dataspace
+ * @return The dataspace of the buffer, or ADATASPACE_UNKNOWN if one hasn't been set
+ */
+enum ADataSpace AHardwareBuffer_getDataSpace(const AHardwareBuffer* _Nonnull buffer)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Sets the dataspace of the given AHardwareBuffer
+ * @param buffer The non-null buffer for which to set the dataspace
+ * @param dataSpace The dataspace to set
+ * @return AHARDWAREBUFFER_STATUS_OK on success,
+ *         AHARDWAREBUFFER_STATUS_UNSUPPORTED if the device doesn't support setting the dataspace,
+ *         AHARDWAREBUFFER_STATUS_UNKNOWN_ERROR for any other failure.
+ */
+enum AHardwareBufferStatus AHardwareBuffer_setDataSpace(AHardwareBuffer* _Nonnull buffer,
+                                                        enum ADataSpace dataSpace)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
 __END_DECLS
 
 #endif /* ANDROID_VNDK_NATIVEWINDOW_AHARDWAREBUFFER_H */
diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt
index dcb5068..a185a59 100644
--- a/libs/nativewindow/libnativewindow.map.txt
+++ b/libs/nativewindow/libnativewindow.map.txt
@@ -2,6 +2,7 @@
   global:
     AHardwareBuffer_acquire;
     AHardwareBuffer_allocate;
+    AHardwareBuffer_allocate2; # llndk # systemapi
     AHardwareBuffer_createFromHandle; # llndk # systemapi
     AHardwareBuffer_describe;
     AHardwareBuffer_getId; # introduced=31
@@ -16,6 +17,8 @@
     AHardwareBuffer_unlock;
     AHardwareBuffer_readFromParcel; # introduced=34
     AHardwareBuffer_writeToParcel; # introduced=34
+    AHardwareBuffer_getDataSpace; # llndk # systemapi
+    AHardwareBuffer_setDataSpace; # llndk # systemapi
     ANativeWindowBuffer_getHardwareBuffer; # llndk
     ANativeWindow_OemStorageGet; # llndk
     ANativeWindow_OemStorageSet; # llndk
diff --git a/libs/nativewindow/tests/AHardwareBufferTest.cpp b/libs/nativewindow/tests/AHardwareBufferTest.cpp
index ef863b6..1f0128a 100644
--- a/libs/nativewindow/tests/AHardwareBufferTest.cpp
+++ b/libs/nativewindow/tests/AHardwareBufferTest.cpp
@@ -17,6 +17,8 @@
 #define LOG_TAG "AHardwareBuffer_test"
 //#define LOG_NDEBUG 0
 
+#include <android-base/properties.h>
+#include <android/data_space.h>
 #include <android/hardware/graphics/common/1.0/types.h>
 #include <gtest/gtest.h>
 #include <private/android/AHardwareBufferHelpers.h>
@@ -26,6 +28,10 @@
 using namespace android;
 using android::hardware::graphics::common::V1_0::BufferUsage;
 
+static bool IsCuttlefish() {
+    return ::android::base::GetProperty("ro.product.board", "") == "cutf";
+}
+
 static ::testing::AssertionResult BuildHexFailureMessage(uint64_t expected,
         uint64_t actual, const char* type) {
     std::ostringstream ss;
@@ -170,3 +176,83 @@
 
     EXPECT_NE(id1, id2);
 }
+
+TEST(AHardwareBufferTest, Allocate2NoExtras) {
+    AHardwareBuffer_Desc desc{
+            .width = 64,
+            .height = 1,
+            .layers = 1,
+            .format = AHARDWAREBUFFER_FORMAT_BLOB,
+            .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN,
+            .stride = 0,
+    };
+
+    AHardwareBuffer* buffer = nullptr;
+    ASSERT_EQ(0, AHardwareBuffer_allocate2(&desc, nullptr, 0, &buffer));
+    uint64_t id = 0;
+    EXPECT_EQ(0, AHardwareBuffer_getId(buffer, &id));
+    EXPECT_NE(0, id);
+    AHardwareBuffer_Desc desc2{};
+    AHardwareBuffer_describe(buffer, &desc2);
+    EXPECT_EQ(desc.width, desc2.width);
+    EXPECT_EQ(desc.height, desc2.height);
+    EXPECT_GE(desc2.stride, desc2.width);
+
+    AHardwareBuffer_release(buffer);
+}
+
+TEST(AHardwareBufferTest, Allocate2WithExtras) {
+    if (!IsCuttlefish()) {
+        GTEST_SKIP() << "Unknown gralloc HAL, cannot test extras";
+    }
+
+    AHardwareBuffer_Desc desc{
+            .width = 64,
+            .height = 48,
+            .layers = 1,
+            .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+            .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN,
+            .stride = 0,
+    };
+
+    AHardwareBuffer* buffer = nullptr;
+    std::array<AHardwareBufferLongOptions, 1> extras = {{
+            {.name = "android.hardware.graphics.common.Dataspace", ADATASPACE_DISPLAY_P3},
+    }};
+    ASSERT_EQ(0, AHardwareBuffer_allocate2(&desc, extras.data(), extras.size(), &buffer));
+    uint64_t id = 0;
+    EXPECT_EQ(0, AHardwareBuffer_getId(buffer, &id));
+    EXPECT_NE(0, id);
+    AHardwareBuffer_Desc desc2{};
+    AHardwareBuffer_describe(buffer, &desc2);
+    EXPECT_EQ(desc.width, desc2.width);
+    EXPECT_EQ(desc.height, desc2.height);
+    EXPECT_GE(desc2.stride, desc2.width);
+
+    EXPECT_EQ(ADATASPACE_DISPLAY_P3, AHardwareBuffer_getDataSpace(buffer));
+
+    AHardwareBuffer_release(buffer);
+}
+
+TEST(AHardwareBufferTest, GetSetDataspace) {
+    AHardwareBuffer_Desc desc{
+            .width = 64,
+            .height = 48,
+            .layers = 1,
+            .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+            .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN,
+            .stride = 0,
+    };
+
+    AHardwareBuffer* buffer = nullptr;
+    ASSERT_EQ(0, AHardwareBuffer_allocate(&desc, &buffer));
+
+    EXPECT_EQ(ADATASPACE_UNKNOWN, AHardwareBuffer_getDataSpace(buffer));
+    AHardwareBufferStatus status = AHardwareBuffer_setDataSpace(buffer, ADATASPACE_DISPLAY_P3);
+    if (status != AHARDWAREBUFFER_STATUS_UNSUPPORTED) {
+        EXPECT_EQ(0, status);
+        EXPECT_EQ(ADATASPACE_DISPLAY_P3, AHardwareBuffer_getDataSpace(buffer));
+    }
+
+    AHardwareBuffer_release(buffer);
+}
\ No newline at end of file
diff --git a/libs/nativewindow/tests/Android.bp b/libs/nativewindow/tests/Android.bp
index 30737c1..d7c7eb3 100644
--- a/libs/nativewindow/tests/Android.bp
+++ b/libs/nativewindow/tests/Android.bp
@@ -31,6 +31,7 @@
         "device-tests",
     ],
     shared_libs: [
+        "libbase",
         "libgui",
         "liblog",
         "libnativewindow",
@@ -44,5 +45,8 @@
         "ANativeWindowTest.cpp",
         "c_compatibility.c",
     ],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 }
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index 2053c6a..d688b51 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -236,7 +236,13 @@
             err = selectEGLConfig(display, format, 0, &config);
             if (err != NO_ERROR) {
                 // this EGL is too lame for android
-                LOG_ALWAYS_FATAL("no suitable EGLConfig found, giving up");
+                LOG_ALWAYS_FATAL("no suitable EGLConfig found, giving up"
+                                 " (format: %d, vendor: %s, version: %s, extensions: %s, Client"
+                                 " API: %s)",
+                                 format, eglQueryString(display, EGL_VENDOR),
+                                 eglQueryString(display, EGL_VERSION),
+                                 eglQueryString(display, EGL_EXTENSIONS),
+                                 eglQueryString(display, EGL_CLIENT_APIS) ?: "Not Supported");
             }
         }
     }
diff --git a/libs/ui/Gralloc2.cpp b/libs/ui/Gralloc2.cpp
index e9b5dec..a5aca99 100644
--- a/libs/ui/Gralloc2.cpp
+++ b/libs/ui/Gralloc2.cpp
@@ -384,8 +384,8 @@
 
 status_t Gralloc2Allocator::allocate(std::string /*requestorName*/, uint32_t width, uint32_t height,
                                      PixelFormat format, uint32_t layerCount, uint64_t usage,
-                                     uint32_t bufferCount, uint32_t* outStride,
-                                     buffer_handle_t* outBufferHandles, bool importBuffers) const {
+                                     uint32_t* outStride, buffer_handle_t* outBufferHandles,
+                                     bool importBuffers) const {
     IMapper::BufferDescriptorInfo descriptorInfo = {};
     descriptorInfo.width = width;
     descriptorInfo.height = height;
@@ -400,6 +400,8 @@
         return error;
     }
 
+    constexpr auto bufferCount = 1;
+
     auto ret = mAllocator->allocate(descriptor, bufferCount,
                                     [&](const auto& tmpError, const auto& tmpStride,
                                         const auto& tmpBuffers) {
diff --git a/libs/ui/Gralloc3.cpp b/libs/ui/Gralloc3.cpp
index 474d381..152b35a 100644
--- a/libs/ui/Gralloc3.cpp
+++ b/libs/ui/Gralloc3.cpp
@@ -371,7 +371,7 @@
 
 status_t Gralloc3Allocator::allocate(std::string /*requestorName*/, uint32_t width, uint32_t height,
                                      android::PixelFormat format, uint32_t layerCount,
-                                     uint64_t usage, uint32_t bufferCount, uint32_t* outStride,
+                                     uint64_t usage, uint32_t* outStride,
                                      buffer_handle_t* outBufferHandles, bool importBuffers) const {
     IMapper::BufferDescriptorInfo descriptorInfo;
     sBufferDescriptorInfo(width, height, format, layerCount, usage, &descriptorInfo);
@@ -383,6 +383,8 @@
         return error;
     }
 
+    constexpr auto bufferCount = 1;
+
     auto ret = mAllocator->allocate(descriptor, bufferCount,
                                     [&](const auto& tmpError, const auto& tmpStride,
                                         const auto& tmpBuffers) {
diff --git a/libs/ui/Gralloc4.cpp b/libs/ui/Gralloc4.cpp
index 03ff58a..d6970e0 100644
--- a/libs/ui/Gralloc4.cpp
+++ b/libs/ui/Gralloc4.cpp
@@ -1069,7 +1069,7 @@
 
 status_t Gralloc4Allocator::allocate(std::string requestorName, uint32_t width, uint32_t height,
                                      android::PixelFormat format, uint32_t layerCount,
-                                     uint64_t usage, uint32_t bufferCount, uint32_t* outStride,
+                                     uint64_t usage, uint32_t* outStride,
                                      buffer_handle_t* outBufferHandles, bool importBuffers) const {
     IMapper::BufferDescriptorInfo descriptorInfo;
     if (auto error = sBufferDescriptorInfo(requestorName, width, height, format, layerCount, usage,
@@ -1084,6 +1084,8 @@
         return error;
     }
 
+    constexpr auto bufferCount = 1;
+
     if (mAidlAllocator) {
         AllocationResult result;
 #pragma clang diagnostic push
diff --git a/libs/ui/Gralloc5.cpp b/libs/ui/Gralloc5.cpp
index 2ec6d18..b07e155 100644
--- a/libs/ui/Gralloc5.cpp
+++ b/libs/ui/Gralloc5.cpp
@@ -19,6 +19,7 @@
 
 #include <ui/Gralloc5.h>
 
+#include <aidl/android/hardware/graphics/allocator/AllocationError.h>
 #include <aidlcommonsupport/NativeHandle.h>
 #include <android/binder_manager.h>
 #include <android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h>
@@ -215,55 +216,75 @@
 
 status_t Gralloc5Allocator::allocate(std::string requestorName, uint32_t width, uint32_t height,
                                      android::PixelFormat format, uint32_t layerCount,
-                                     uint64_t usage, uint32_t bufferCount, uint32_t *outStride,
-                                     buffer_handle_t *outBufferHandles, bool importBuffers) const {
-    auto descriptorInfo = makeDescriptor(requestorName, width, height, format, layerCount, usage);
+                                     uint64_t usage, uint32_t* outStride,
+                                     buffer_handle_t* outBufferHandles, bool importBuffers) const {
+    auto result = allocate(GraphicBufferAllocator::AllocationRequest{
+            .importBuffer = importBuffers,
+            .width = width,
+            .height = height,
+            .format = format,
+            .layerCount = layerCount,
+            .usage = usage,
+            .requestorName = requestorName,
+    });
+
+    *outStride = result.stride;
+    outBufferHandles[0] = result.handle;
+    return result.status;
+}
+
+GraphicBufferAllocator::AllocationResult Gralloc5Allocator::allocate(
+        const GraphicBufferAllocator::AllocationRequest& request) const {
+    auto descriptorInfo = makeDescriptor(request.requestorName, request.width, request.height,
+                                         request.format, request.layerCount, request.usage);
     if (!descriptorInfo) {
-        return BAD_VALUE;
+        return GraphicBufferAllocator::AllocationResult{BAD_VALUE};
+    }
+
+    descriptorInfo->additionalOptions.reserve(request.extras.size());
+    for (const auto& option : request.extras) {
+        ExtendableType type;
+        type.name = option.name;
+        type.value = option.value;
+        descriptorInfo->additionalOptions.push_back(std::move(type));
     }
 
     AllocationResult result;
-    auto status = mAllocator->allocate2(*descriptorInfo, bufferCount, &result);
+    auto status = mAllocator->allocate2(*descriptorInfo, 1, &result);
     if (!status.isOk()) {
         auto error = status.getExceptionCode();
         if (error == EX_SERVICE_SPECIFIC) {
-            error = status.getServiceSpecificError();
+            switch (static_cast<AllocationError>(status.getServiceSpecificError())) {
+                case AllocationError::BAD_DESCRIPTOR:
+                    error = BAD_VALUE;
+                    break;
+                case AllocationError::NO_RESOURCES:
+                    error = NO_MEMORY;
+                    break;
+                default:
+                    error = UNKNOWN_ERROR;
+                    break;
+            }
         }
-        if (error == OK) {
-            error = UNKNOWN_ERROR;
-        }
-        return error;
+        return GraphicBufferAllocator::AllocationResult{error};
     }
 
-    if (importBuffers) {
-        for (uint32_t i = 0; i < bufferCount; i++) {
-            auto handle = makeFromAidl(result.buffers[i]);
-            auto error = mMapper.importBuffer(handle, &outBufferHandles[i]);
-            native_handle_delete(handle);
-            if (error != NO_ERROR) {
-                for (uint32_t j = 0; j < i; j++) {
-                    mMapper.freeBuffer(outBufferHandles[j]);
-                    outBufferHandles[j] = nullptr;
-                }
-                return error;
-            }
+    GraphicBufferAllocator::AllocationResult ret{OK};
+    if (request.importBuffer) {
+        auto handle = makeFromAidl(result.buffers[0]);
+        auto error = mMapper.importBuffer(handle, &ret.handle);
+        native_handle_delete(handle);
+        if (error != NO_ERROR) {
+            return GraphicBufferAllocator::AllocationResult{error};
         }
     } else {
-        for (uint32_t i = 0; i < bufferCount; i++) {
-            outBufferHandles[i] = dupFromAidl(result.buffers[i]);
-            if (!outBufferHandles[i]) {
-                for (uint32_t j = 0; j < i; j++) {
-                    auto buffer = const_cast<native_handle_t *>(outBufferHandles[j]);
-                    native_handle_close(buffer);
-                    native_handle_delete(buffer);
-                    outBufferHandles[j] = nullptr;
-                }
-                return NO_MEMORY;
-            }
+        ret.handle = dupFromAidl(result.buffers[0]);
+        if (!ret.handle) {
+            return GraphicBufferAllocator::AllocationResult{NO_MEMORY};
         }
     }
 
-    *outStride = result.stride;
+    ret.stride = result.stride;
 
     // Release all the resources held by AllocationResult (specifically any remaining FDs)
     result = {};
@@ -272,7 +293,7 @@
     // is marked apex_available (b/214400477) and libbinder isn't (which of course is correct)
     // IPCThreadState::self()->flushCommands();
 
-    return OK;
+    return ret;
 }
 
 void Gralloc5Mapper::preload() {
diff --git a/libs/ui/GraphicBuffer.cpp b/libs/ui/GraphicBuffer.cpp
index 429760f..c007fdb 100644
--- a/libs/ui/GraphicBuffer.cpp
+++ b/libs/ui/GraphicBuffer.cpp
@@ -106,6 +106,26 @@
                                 inUsage, inStride);
 }
 
+GraphicBuffer::GraphicBuffer(const GraphicBufferAllocator::AllocationRequest& request)
+      : GraphicBuffer() {
+    GraphicBufferAllocator& allocator = GraphicBufferAllocator::get();
+    auto result = allocator.allocate(request);
+    mInitCheck = result.status;
+    if (result.status == NO_ERROR) {
+        handle = result.handle;
+        stride = result.stride;
+
+        mBufferMapper.getTransportSize(handle, &mTransportNumFds, &mTransportNumInts);
+
+        width = static_cast<int>(request.width);
+        height = static_cast<int>(request.height);
+        format = request.format;
+        layerCount = request.layerCount;
+        usage = request.usage;
+        usage_deprecated = int(usage);
+    }
+}
+
 GraphicBuffer::~GraphicBuffer()
 {
     ATRACE_CALL();
@@ -143,6 +163,10 @@
             const_cast<GraphicBuffer*>(this));
 }
 
+status_t GraphicBuffer::getDataspace(ui::Dataspace* outDataspace) const {
+    return mBufferMapper.getDataspace(handle, outDataspace);
+}
+
 status_t GraphicBuffer::reallocate(uint32_t inWidth, uint32_t inHeight,
         PixelFormat inFormat, uint32_t inLayerCount, uint64_t inUsage)
 {
diff --git a/libs/ui/GraphicBufferAllocator.cpp b/libs/ui/GraphicBufferAllocator.cpp
index eb0bd4e..98082fb 100644
--- a/libs/ui/GraphicBufferAllocator.cpp
+++ b/libs/ui/GraphicBufferAllocator.cpp
@@ -113,6 +113,79 @@
     ALOGD("%s", s.c_str());
 }
 
+auto GraphicBufferAllocator::allocate(const AllocationRequest& request) -> AllocationResult {
+    ATRACE_CALL();
+    if (!request.width || !request.height) {
+        return AllocationResult(BAD_VALUE);
+    }
+
+    const auto width = request.width;
+    const auto height = request.height;
+
+    const uint32_t bpp = bytesPerPixel(request.format);
+    if (std::numeric_limits<size_t>::max() / width / height < static_cast<size_t>(bpp)) {
+        ALOGE("Failed to allocate (%u x %u) layerCount %u format %d "
+              "usage %" PRIx64 ": Requesting too large a buffer size",
+              request.width, request.height, request.layerCount, request.format, request.usage);
+        return AllocationResult(BAD_VALUE);
+    }
+
+    if (request.layerCount < 1) {
+        return AllocationResult(BAD_VALUE);
+    }
+
+    auto result = mAllocator->allocate(request);
+    if (result.status == UNKNOWN_TRANSACTION) {
+        if (!request.extras.empty()) {
+            ALOGE("Failed to allocate with additional options, allocator version mis-match? "
+                  "gralloc version = %d",
+                  (int)mMapper.getMapperVersion());
+            return result;
+        }
+        // If there's no additional options, fall back to previous allocate version
+        result.status = mAllocator->allocate(request.requestorName, request.width, request.height,
+                                             request.format, request.layerCount, request.usage,
+                                             &result.stride, &result.handle, request.importBuffer);
+    }
+
+    if (result.status != NO_ERROR) {
+        ALOGE("Failed to allocate (%u x %u) layerCount %u format %d "
+              "usage %" PRIx64 ": %d",
+              request.width, request.height, request.layerCount, request.format, request.usage,
+              result.status);
+        return result;
+    }
+
+    if (!request.importBuffer) {
+        return result;
+    }
+    size_t bufSize;
+
+    // if stride has no meaning or is too large,
+    // approximate size with the input width instead
+    if ((result.stride) != 0 &&
+        std::numeric_limits<size_t>::max() / height / (result.stride) < static_cast<size_t>(bpp)) {
+        bufSize = static_cast<size_t>(width) * height * bpp;
+    } else {
+        bufSize = static_cast<size_t>((result.stride)) * height * bpp;
+    }
+
+    Mutex::Autolock _l(sLock);
+    KeyedVector<buffer_handle_t, alloc_rec_t>& list(sAllocList);
+    alloc_rec_t rec;
+    rec.width = width;
+    rec.height = height;
+    rec.stride = result.stride;
+    rec.format = request.format;
+    rec.layerCount = request.layerCount;
+    rec.usage = request.usage;
+    rec.size = bufSize;
+    rec.requestorName = request.requestorName;
+    list.add(result.handle, rec);
+
+    return result;
+}
+
 status_t GraphicBufferAllocator::allocateHelper(uint32_t width, uint32_t height, PixelFormat format,
                                                 uint32_t layerCount, uint64_t usage,
                                                 buffer_handle_t* handle, uint32_t* stride,
@@ -141,7 +214,7 @@
     usage &= ~static_cast<uint64_t>((1 << 10) | (1 << 13));
 
     status_t error = mAllocator->allocate(requestorName, width, height, format, layerCount, usage,
-                                          1, stride, handle, importBuffer);
+                                          stride, handle, importBuffer);
     if (error != NO_ERROR) {
         ALOGE("Failed to allocate (%u x %u) layerCount %u format %d "
               "usage %" PRIx64 ": %d",
diff --git a/libs/ui/include/ui/Gralloc.h b/libs/ui/include/ui/Gralloc.h
index 496ba57..e6015e0 100644
--- a/libs/ui/include/ui/Gralloc.h
+++ b/libs/ui/include/ui/Gralloc.h
@@ -23,6 +23,7 @@
 #include <ui/PixelFormat.h>
 #include <ui/Rect.h>
 #include <utils/StrongPointer.h>
+#include "GraphicBufferAllocator.h"
 
 #include <string>
 
@@ -218,9 +219,13 @@
      */
     virtual status_t allocate(std::string requestorName, uint32_t width, uint32_t height,
                               PixelFormat format, uint32_t layerCount, uint64_t usage,
-                              uint32_t bufferCount, uint32_t* outStride,
-                              buffer_handle_t* outBufferHandles,
+                              uint32_t* outStride, buffer_handle_t* outBufferHandles,
                               bool importBuffers = true) const = 0;
+
+    virtual GraphicBufferAllocator::AllocationResult allocate(
+            const GraphicBufferAllocator::AllocationRequest&) const {
+        return GraphicBufferAllocator::AllocationResult(UNKNOWN_TRANSACTION);
+    }
 };
 
 } // namespace android
diff --git a/libs/ui/include/ui/Gralloc2.h b/libs/ui/include/ui/Gralloc2.h
index a7b6f492..e50bb3a 100644
--- a/libs/ui/include/ui/Gralloc2.h
+++ b/libs/ui/include/ui/Gralloc2.h
@@ -81,9 +81,8 @@
     std::string dumpDebugInfo(bool less = true) const override;
 
     status_t allocate(std::string requestorName, uint32_t width, uint32_t height,
-                      PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t bufferCount,
-                      uint32_t* outStride, buffer_handle_t* outBufferHandles,
-                      bool importBuffers = true) const override;
+                      PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t* outStride,
+                      buffer_handle_t* outBufferHandles, bool importBuffers = true) const override;
 
 private:
     const Gralloc2Mapper& mMapper;
diff --git a/libs/ui/include/ui/Gralloc3.h b/libs/ui/include/ui/Gralloc3.h
index 7367549..035684a 100644
--- a/libs/ui/include/ui/Gralloc3.h
+++ b/libs/ui/include/ui/Gralloc3.h
@@ -82,9 +82,8 @@
     std::string dumpDebugInfo(bool less = true) const override;
 
     status_t allocate(std::string requestorName, uint32_t width, uint32_t height,
-                      PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t bufferCount,
-                      uint32_t* outStride, buffer_handle_t* outBufferHandles,
-                      bool importBuffers = true) const override;
+                      PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t* outStride,
+                      buffer_handle_t* outBufferHandles, bool importBuffers = true) const override;
 
 private:
     const Gralloc3Mapper& mMapper;
diff --git a/libs/ui/include/ui/Gralloc4.h b/libs/ui/include/ui/Gralloc4.h
index df43be8..0f469c0 100644
--- a/libs/ui/include/ui/Gralloc4.h
+++ b/libs/ui/include/ui/Gralloc4.h
@@ -174,9 +174,8 @@
     std::string dumpDebugInfo(bool less = true) const override;
 
     status_t allocate(std::string requestorName, uint32_t width, uint32_t height,
-                      PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t bufferCount,
-                      uint32_t* outStride, buffer_handle_t* outBufferHandles,
-                      bool importBuffers = true) const override;
+                      PixelFormat format, uint32_t layerCount, uint64_t usage, uint32_t* outStride,
+                      buffer_handle_t* outBufferHandles, bool importBuffers = true) const override;
 
 private:
     const Gralloc4Mapper& mMapper;
diff --git a/libs/ui/include/ui/Gralloc5.h b/libs/ui/include/ui/Gralloc5.h
index 44b97d1..f9e8f5e 100644
--- a/libs/ui/include/ui/Gralloc5.h
+++ b/libs/ui/include/ui/Gralloc5.h
@@ -172,10 +172,12 @@
 
     [[nodiscard]] status_t allocate(std::string requestorName, uint32_t width, uint32_t height,
                                     PixelFormat format, uint32_t layerCount, uint64_t usage,
-                                    uint32_t bufferCount, uint32_t *outStride,
-                                    buffer_handle_t *outBufferHandles,
+                                    uint32_t* outStride, buffer_handle_t* outBufferHandles,
                                     bool importBuffers) const override;
 
+    [[nodiscard]] GraphicBufferAllocator::AllocationResult allocate(
+            const GraphicBufferAllocator::AllocationRequest&) const override;
+
 private:
     const Gralloc5Mapper &mMapper;
     std::shared_ptr<aidl::android::hardware::graphics::allocator::IAllocator> mAllocator;
diff --git a/libs/ui/include/ui/GraphicBuffer.h b/libs/ui/include/ui/GraphicBuffer.h
index f859848..652d8ba 100644
--- a/libs/ui/include/ui/GraphicBuffer.h
+++ b/libs/ui/include/ui/GraphicBuffer.h
@@ -26,6 +26,7 @@
 
 #include <android/hardware_buffer.h>
 #include <ui/ANativeObjectBase.h>
+#include <ui/GraphicBufferAllocator.h>
 #include <ui/GraphicBufferMapper.h>
 #include <ui/PixelFormat.h>
 #include <ui/Rect.h>
@@ -103,6 +104,8 @@
             uint32_t inLayerCount, uint64_t inUsage,
             std::string requestorName = "<Unknown>");
 
+    GraphicBuffer(const GraphicBufferAllocator::AllocationRequest&);
+
     // Create a GraphicBuffer from an existing handle.
     enum HandleWrapMethod : uint8_t {
         // Wrap and use the handle directly.  It assumes the handle has been
@@ -169,6 +172,8 @@
         mGenerationNumber = generation;
     }
 
+    status_t getDataspace(ui::Dataspace* outDataspace) const;
+
     // This function is privileged.  It requires access to the allocator
     // device or service, which usually involves adding suitable selinux
     // rules.
diff --git a/libs/ui/include/ui/GraphicBufferAllocator.h b/libs/ui/include/ui/GraphicBufferAllocator.h
index 3ed988c..8f461e1 100644
--- a/libs/ui/include/ui/GraphicBufferAllocator.h
+++ b/libs/ui/include/ui/GraphicBufferAllocator.h
@@ -22,6 +22,7 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include <cutils/native_handle.h>
 
@@ -42,6 +43,35 @@
 public:
     static inline GraphicBufferAllocator& get() { return getInstance(); }
 
+    struct AdditionalOptions {
+        const char* name;
+        int64_t value;
+    };
+
+    struct AllocationRequest {
+        bool importBuffer;
+        uint32_t width;
+        uint32_t height;
+        PixelFormat format;
+        uint32_t layerCount;
+        uint64_t usage;
+        std::string requestorName;
+        std::vector<AdditionalOptions> extras;
+    };
+
+    struct AllocationResult {
+        status_t status;
+        buffer_handle_t handle = nullptr;
+        uint32_t stride = 0;
+
+        explicit AllocationResult(status_t status) : status(status) {}
+
+        explicit AllocationResult(buffer_handle_t handle, uint32_t stride)
+              : status(OK), handle(handle), stride(stride) {}
+    };
+
+    AllocationResult allocate(const AllocationRequest&);
+
     /**
      * Allocates and imports a gralloc buffer.
      *
diff --git a/libs/ui/tests/GraphicBufferAllocator_test.cpp b/libs/ui/tests/GraphicBufferAllocator_test.cpp
index f4c0afa..efca083 100644
--- a/libs/ui/tests/GraphicBufferAllocator_test.cpp
+++ b/libs/ui/tests/GraphicBufferAllocator_test.cpp
@@ -51,7 +51,7 @@
         std::cout << "Setting expected stride to " << stride << std::endl;
         EXPECT_CALL(*(reinterpret_cast<const mock::MockGrallocAllocator*>(mAllocator.get())),
                     allocate)
-                .WillOnce(DoAll(SetArgPointee<7>(stride), Return(err)));
+                .WillOnce(DoAll(SetArgPointee<6>(stride), Return(err)));
     }
     std::unique_ptr<const GrallocAllocator>& getAllocator() { return mAllocator; }
 };
diff --git a/libs/ui/tests/mock/MockGrallocAllocator.h b/libs/ui/tests/mock/MockGrallocAllocator.h
index d62e3e2..d02b387 100644
--- a/libs/ui/tests/mock/MockGrallocAllocator.h
+++ b/libs/ui/tests/mock/MockGrallocAllocator.h
@@ -35,7 +35,7 @@
     MOCK_METHOD(std::string, dumpDebugInfo, (bool less), (const, override));
     MOCK_METHOD(status_t, allocate,
                 (std::string requestorName, uint32_t width, uint32_t height, PixelFormat format,
-                 uint32_t layerCount, uint64_t usage, uint32_t bufferCount, uint32_t* outStride,
+                 uint32_t layerCount, uint64_t usage, uint32_t* outStride,
                  buffer_handle_t* outBufferHandles, bool less),
                 (const, override));
 };
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index 45c9b5c..69f42bc 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -77,6 +77,7 @@
         "InputCommonConverter.cpp",
         "InputDeviceMetricsCollector.cpp",
         "InputFilter.cpp",
+        "InputFilterCallbacks.cpp",
         "InputProcessor.cpp",
         "PointerChoreographer.cpp",
         "PreferStylusOverTouchBlocker.cpp",
diff --git a/services/inputflinger/InputFilter.cpp b/services/inputflinger/InputFilter.cpp
index 9c4a3eb..5d87d34 100644
--- a/services/inputflinger/InputFilter.cpp
+++ b/services/inputflinger/InputFilter.cpp
@@ -44,31 +44,9 @@
     return event;
 }
 
-NotifyKeyArgs keyEventToNotifyKeyArgs(const AidlKeyEvent& event) {
-    return NotifyKeyArgs(event.id, event.eventTime, event.readTime, event.deviceId,
-                         static_cast<uint32_t>(event.source), event.displayId, event.policyFlags,
-                         static_cast<int32_t>(event.action), event.flags, event.keyCode,
-                         event.scanCode, event.metaState, event.downTime);
-}
-
-namespace {
-
-class RustCallbacks : public IInputFilter::BnInputFilterCallbacks {
-public:
-    RustCallbacks(InputListenerInterface& nextListener) : mNextListener(nextListener) {}
-    ndk::ScopedAStatus sendKeyEvent(const AidlKeyEvent& event) override {
-        mNextListener.notifyKey(keyEventToNotifyKeyArgs(event));
-        return ndk::ScopedAStatus::ok();
-    }
-
-private:
-    InputListenerInterface& mNextListener;
-};
-
-} // namespace
-
 InputFilter::InputFilter(InputListenerInterface& listener, IInputFlingerRust& rust)
-      : mNextListener(listener), mCallbacks(ndk::SharedRefBase::make<RustCallbacks>(listener)) {
+      : mNextListener(listener),
+        mCallbacks(ndk::SharedRefBase::make<InputFilterCallbacks>(listener)) {
     LOG_ALWAYS_FATAL_IF(!rust.createInputFilter(mCallbacks, &mInputFilterRust).isOk());
     LOG_ALWAYS_FATAL_IF(!mInputFilterRust);
 }
@@ -92,11 +70,11 @@
 }
 
 void InputFilter::notifyKey(const NotifyKeyArgs& args) {
-    if (!isFilterEnabled()) {
-        mNextListener.notifyKey(args);
+    if (isFilterEnabled()) {
+        LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyKey(notifyKeyArgsToKeyEvent(args)).isOk());
         return;
     }
-    LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyKey(notifyKeyArgsToKeyEvent(args)).isOk());
+    mNextListener.notifyKey(args);
 }
 
 void InputFilter::notifyMotion(const NotifyMotionArgs& args) {
@@ -138,6 +116,15 @@
     }
 }
 
+void InputFilter::setAccessibilityStickyKeysEnabled(bool enabled) {
+    std::scoped_lock _l(mLock);
+
+    if (mConfig.stickyKeysEnabled != enabled) {
+        mConfig.stickyKeysEnabled = enabled;
+        LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyConfigurationChanged(mConfig).isOk());
+    }
+}
+
 void InputFilter::dump(std::string& dump) {
     dump += "InputFilter:\n";
 }
diff --git a/services/inputflinger/InputFilter.h b/services/inputflinger/InputFilter.h
index 06f7d0e..9fa7a87 100644
--- a/services/inputflinger/InputFilter.h
+++ b/services/inputflinger/InputFilter.h
@@ -18,6 +18,7 @@
 
 #include <aidl/com/android/server/inputflinger/IInputFlingerRust.h>
 #include <utils/Mutex.h>
+#include "InputFilterCallbacks.h"
 #include "InputListener.h"
 #include "NotifyArgs.h"
 
@@ -33,6 +34,7 @@
      */
     virtual void dump(std::string& dump) = 0;
     virtual void setAccessibilityBounceKeysThreshold(nsecs_t threshold) = 0;
+    virtual void setAccessibilityStickyKeysEnabled(bool enabled) = 0;
 };
 
 class InputFilter : public InputFilterInterface {
@@ -56,11 +58,12 @@
     void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
     void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
     void setAccessibilityBounceKeysThreshold(nsecs_t threshold) override;
+    void setAccessibilityStickyKeysEnabled(bool enabled) override;
     void dump(std::string& dump) override;
 
 private:
     InputListenerInterface& mNextListener;
-    std::shared_ptr<IInputFilterCallbacks> mCallbacks;
+    std::shared_ptr<InputFilterCallbacks> mCallbacks;
     std::shared_ptr<IInputFilter> mInputFilterRust;
     mutable std::mutex mLock;
     InputFilterConfiguration mConfig GUARDED_BY(mLock);
diff --git a/services/inputflinger/InputFilterCallbacks.cpp b/services/inputflinger/InputFilterCallbacks.cpp
new file mode 100644
index 0000000..8c8f5e8
--- /dev/null
+++ b/services/inputflinger/InputFilterCallbacks.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#define LOG_TAG "InputFilterCallbacks"
+
+#include "InputFilterCallbacks.h"
+
+namespace android {
+
+using AidlKeyEvent = aidl::com::android::server::inputflinger::KeyEvent;
+
+NotifyKeyArgs keyEventToNotifyKeyArgs(const AidlKeyEvent& event) {
+    return NotifyKeyArgs(event.id, event.eventTime, event.readTime, event.deviceId,
+                         static_cast<uint32_t>(event.source), event.displayId, event.policyFlags,
+                         static_cast<int32_t>(event.action), event.flags, event.keyCode,
+                         event.scanCode, event.metaState, event.downTime);
+}
+
+InputFilterCallbacks::InputFilterCallbacks(InputListenerInterface& listener)
+      : mNextListener(listener) {}
+
+ndk::ScopedAStatus InputFilterCallbacks::sendKeyEvent(const AidlKeyEvent& event) {
+    mNextListener.notifyKey(keyEventToNotifyKeyArgs(event));
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus InputFilterCallbacks::onModifierStateChanged(int32_t modifierState,
+                                                                int32_t lockedModifierState) {
+    std::scoped_lock _l(mLock);
+    mStickyModifierState.modifierState = modifierState;
+    mStickyModifierState.lockedModifierState = lockedModifierState;
+    ALOGI("Sticky keys modifier state changed: modifierState=%d, lockedModifierState=%d",
+          modifierState, lockedModifierState);
+    return ndk::ScopedAStatus::ok();
+}
+
+uint32_t InputFilterCallbacks::getModifierState() {
+    std::scoped_lock _l(mLock);
+    return mStickyModifierState.modifierState;
+}
+
+uint32_t InputFilterCallbacks::getLockedModifierState() {
+    std::scoped_lock _l(mLock);
+    return mStickyModifierState.lockedModifierState;
+}
+
+} // namespace android
diff --git a/services/inputflinger/InputFilterCallbacks.h b/services/inputflinger/InputFilterCallbacks.h
new file mode 100644
index 0000000..c0a80fb
--- /dev/null
+++ b/services/inputflinger/InputFilterCallbacks.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/com/android/server/inputflinger/IInputFlingerRust.h>
+#include <android/binder_auto_utils.h>
+#include <utils/Mutex.h>
+#include <mutex>
+#include "InputListener.h"
+#include "NotifyArgs.h"
+
+/**
+ * The C++ component of InputFilter designed as a wrapper around the rust callback implementation.
+ */
+namespace android {
+
+using IInputFilter = aidl::com::android::server::inputflinger::IInputFilter;
+using AidlKeyEvent = aidl::com::android::server::inputflinger::KeyEvent;
+
+class InputFilterCallbacks : public IInputFilter::BnInputFilterCallbacks {
+public:
+    explicit InputFilterCallbacks(InputListenerInterface& listener);
+    ~InputFilterCallbacks() override = default;
+
+    uint32_t getModifierState();
+    uint32_t getLockedModifierState();
+
+private:
+    InputListenerInterface& mNextListener;
+    mutable std::mutex mLock;
+    struct StickyModifierState {
+        uint32_t modifierState;
+        uint32_t lockedModifierState;
+    } mStickyModifierState GUARDED_BY(mLock);
+
+    ndk::ScopedAStatus sendKeyEvent(const AidlKeyEvent& event) override;
+    ndk::ScopedAStatus onModifierStateChanged(int32_t modifierState,
+                                              int32_t lockedModifierState) override;
+};
+
+} // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
index 14b41cd..2921d30 100644
--- a/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
@@ -33,6 +33,9 @@
     interface IInputFilterCallbacks {
         /** Sends back a filtered key event */
         void sendKeyEvent(in KeyEvent event);
+
+        /** Sends back modifier state */
+        void onModifierStateChanged(int modifierState, int lockedModifierState);
     }
 
     /** Returns if InputFilter is enabled */
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl
index 3b2e88b..38b1612 100644
--- a/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl
@@ -22,4 +22,6 @@
 parcelable InputFilterConfiguration {
     // Threshold value for Bounce keys filter (check bounce_keys_filter.rs)
     long bounceKeysThresholdNs;
+    // If sticky keys filter is enabled
+    boolean stickyKeysEnabled;
 }
\ No newline at end of file
diff --git a/services/inputflinger/rust/input_filter.rs b/services/inputflinger/rust/input_filter.rs
index 340ff8e..e94a71f 100644
--- a/services/inputflinger/rust/input_filter.rs
+++ b/services/inputflinger/rust/input_filter.rs
@@ -27,6 +27,7 @@
 };
 
 use crate::bounce_keys_filter::BounceKeysFilter;
+use crate::sticky_keys_filter::StickyKeysFilter;
 use log::{error, info};
 use std::sync::{Arc, Mutex, RwLock};
 
@@ -91,6 +92,14 @@
         let mut state = self.state.lock().unwrap();
         let mut first_filter: Box<dyn Filter + Send + Sync> =
             Box::new(BaseFilter::new(self.callbacks.clone()));
+        if config.stickyKeysEnabled {
+            first_filter = Box::new(StickyKeysFilter::new(
+                first_filter,
+                ModifierStateListener::new(self.callbacks.clone()),
+            ));
+            state.enabled = true;
+            info!("Sticky keys filter is installed");
+        }
         if config.bounceKeysThresholdNs > 0 {
             first_filter =
                 Box::new(BounceKeysFilter::new(first_filter, config.bounceKeysThresholdNs));
@@ -125,34 +134,43 @@
     }
 }
 
+pub struct ModifierStateListener {
+    callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
+}
+
+impl ModifierStateListener {
+    /// Create a new InputFilter instance.
+    pub fn new(callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>) -> ModifierStateListener {
+        Self { callbacks }
+    }
+
+    pub fn modifier_state_changed(&self, modifier_state: u32, locked_modifier_state: u32) {
+        let _ = self
+            .callbacks
+            .read()
+            .unwrap()
+            .onModifierStateChanged(modifier_state as i32, locked_modifier_state as i32);
+    }
+}
+
 #[cfg(test)]
 mod tests {
-    use crate::input_filter::{test_filter::TestFilter, Filter, InputFilter};
+    use crate::input_filter::{
+        test_callbacks::TestCallbacks, test_filter::TestFilter, InputFilter,
+    };
     use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
-    use binder::{Interface, Strong};
+    use binder::Strong;
     use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
         DeviceInfo::DeviceInfo, IInputFilter::IInputFilter,
-        IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks,
         InputFilterConfiguration::InputFilterConfiguration, KeyEvent::KeyEvent,
         KeyEventAction::KeyEventAction,
     };
     use std::sync::{Arc, RwLock};
 
-    struct FakeCallbacks {}
-
-    impl Interface for FakeCallbacks {}
-
-    impl IInputFilterCallbacks for FakeCallbacks {
-        fn sendKeyEvent(&self, _event: &KeyEvent) -> binder::Result<()> {
-            Result::Ok(())
-        }
-    }
-
     #[test]
     fn test_not_enabled_with_default_filter() {
-        let fake_callbacks: Strong<dyn IInputFilterCallbacks> =
-            Strong::new(Box::new(FakeCallbacks {}));
-        let input_filter = InputFilter::new(fake_callbacks);
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks)));
         let result = input_filter.isEnabled();
         assert!(result.is_ok());
         assert!(!result.unwrap());
@@ -160,17 +178,21 @@
 
     #[test]
     fn test_notify_key_with_no_filters() {
-        let fake_callbacks: Strong<dyn IInputFilterCallbacks> =
-            Strong::new(Box::new(FakeCallbacks {}));
-        let input_filter = InputFilter::new(fake_callbacks);
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks.clone())));
         let event = create_key_event();
         assert!(input_filter.notifyKey(&event).is_ok());
+        assert_eq!(test_callbacks.last_event().unwrap(), event);
     }
 
     #[test]
     fn test_notify_key_with_filter() {
         let test_filter = TestFilter::new();
-        let input_filter = create_input_filter(Box::new(test_filter.clone()));
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::create_input_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks)))),
+        );
         let event = create_key_event();
         assert!(input_filter.notifyKey(&event).is_ok());
         assert_eq!(test_filter.last_event().unwrap(), event);
@@ -179,7 +201,11 @@
     #[test]
     fn test_notify_devices_changed() {
         let test_filter = TestFilter::new();
-        let input_filter = create_input_filter(Box::new(test_filter.clone()));
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::create_input_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks)))),
+        );
         assert!(input_filter
             .notifyInputDevicesChanged(&[DeviceInfo { deviceId: 0, external: true }])
             .is_ok());
@@ -188,21 +214,30 @@
 
     #[test]
     fn test_notify_configuration_changed_enabled_bounce_keys() {
-        let fake_callbacks: Strong<dyn IInputFilterCallbacks> =
-            Strong::new(Box::new(FakeCallbacks {}));
-        let input_filter = InputFilter::new(fake_callbacks);
-        let result = input_filter
-            .notifyConfigurationChanged(&InputFilterConfiguration { bounceKeysThresholdNs: 100 });
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks)));
+        let result = input_filter.notifyConfigurationChanged(&InputFilterConfiguration {
+            bounceKeysThresholdNs: 100,
+            stickyKeysEnabled: false,
+        });
         assert!(result.is_ok());
         let result = input_filter.isEnabled();
         assert!(result.is_ok());
         assert!(result.unwrap());
     }
 
-    fn create_input_filter(filter: Box<dyn Filter + Send + Sync>) -> InputFilter {
-        let fake_callbacks: Strong<dyn IInputFilterCallbacks> =
-            Strong::new(Box::new(FakeCallbacks {}));
-        InputFilter::create_input_filter(filter, Arc::new(RwLock::new(fake_callbacks)))
+    #[test]
+    fn test_notify_configuration_changed_enabled_sticky_keys() {
+        let test_callbacks = TestCallbacks::new();
+        let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks)));
+        let result = input_filter.notifyConfigurationChanged(&InputFilterConfiguration {
+            bounceKeysThresholdNs: 0,
+            stickyKeysEnabled: true,
+        });
+        assert!(result.is_ok());
+        let result = input_filter.isEnabled();
+        assert!(result.is_ok());
+        assert!(result.unwrap());
     }
 
     fn create_key_event() -> KeyEvent {
@@ -272,3 +307,69 @@
         }
     }
 }
+
+#[cfg(test)]
+pub mod test_callbacks {
+    use binder::Interface;
+    use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+        IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks, KeyEvent::KeyEvent,
+    };
+    use std::sync::{Arc, RwLock, RwLockWriteGuard};
+
+    #[derive(Default)]
+    struct TestCallbacksInner {
+        last_modifier_state: u32,
+        last_locked_modifier_state: u32,
+        last_event: Option<KeyEvent>,
+    }
+
+    #[derive(Default, Clone)]
+    pub struct TestCallbacks(Arc<RwLock<TestCallbacksInner>>);
+
+    impl Interface for TestCallbacks {}
+
+    impl TestCallbacks {
+        pub fn new() -> Self {
+            Default::default()
+        }
+
+        fn inner(&self) -> RwLockWriteGuard<'_, TestCallbacksInner> {
+            self.0.write().unwrap()
+        }
+
+        pub fn last_event(&self) -> Option<KeyEvent> {
+            self.0.read().unwrap().last_event
+        }
+
+        pub fn clear(&mut self) {
+            self.inner().last_event = None;
+            self.inner().last_modifier_state = 0;
+            self.inner().last_locked_modifier_state = 0;
+        }
+
+        pub fn get_last_modifier_state(&self) -> u32 {
+            self.0.read().unwrap().last_modifier_state
+        }
+
+        pub fn get_last_locked_modifier_state(&self) -> u32 {
+            self.0.read().unwrap().last_locked_modifier_state
+        }
+    }
+
+    impl IInputFilterCallbacks for TestCallbacks {
+        fn sendKeyEvent(&self, event: &KeyEvent) -> binder::Result<()> {
+            self.inner().last_event = Some(*event);
+            Result::Ok(())
+        }
+
+        fn onModifierStateChanged(
+            &self,
+            modifier_state: i32,
+            locked_modifier_state: i32,
+        ) -> std::result::Result<(), binder::Status> {
+            self.inner().last_modifier_state = modifier_state as u32;
+            self.inner().last_locked_modifier_state = locked_modifier_state as u32;
+            Result::Ok(())
+        }
+    }
+}
diff --git a/services/inputflinger/rust/lib.rs b/services/inputflinger/rust/lib.rs
index 68cd480..fa16898 100644
--- a/services/inputflinger/rust/lib.rs
+++ b/services/inputflinger/rust/lib.rs
@@ -21,6 +21,7 @@
 
 mod bounce_keys_filter;
 mod input_filter;
+mod sticky_keys_filter;
 
 use crate::input_filter::InputFilter;
 use binder::{
diff --git a/services/inputflinger/rust/sticky_keys_filter.rs b/services/inputflinger/rust/sticky_keys_filter.rs
new file mode 100644
index 0000000..da581b8
--- /dev/null
+++ b/services/inputflinger/rust/sticky_keys_filter.rs
@@ -0,0 +1,515 @@
+/*
+ * Copyright 2023 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.
+ */
+
+//! Sticky keys input filter implementation.
+//! Sticky keys is an accessibility feature that assists users who have physical disabilities or
+//! helps users reduce repetitive strain injury. It serializes keystrokes instead of pressing
+//! multiple keys at a time, allowing the user to press and release a modifier key, such as Shift,
+//! Ctrl, Alt, or any other modifier key, and have it remain active until any other key is pressed.
+use crate::input_filter::{Filter, ModifierStateListener};
+use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+    DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
+};
+use std::collections::HashSet;
+
+// Modifier keycodes: values are from /frameworks/native/include/android/keycodes.h
+const KEYCODE_ALT_LEFT: i32 = 57;
+const KEYCODE_ALT_RIGHT: i32 = 58;
+const KEYCODE_SHIFT_LEFT: i32 = 59;
+const KEYCODE_SHIFT_RIGHT: i32 = 60;
+const KEYCODE_SYM: i32 = 63;
+const KEYCODE_CTRL_LEFT: i32 = 113;
+const KEYCODE_CTRL_RIGHT: i32 = 114;
+const KEYCODE_CAPS_LOCK: i32 = 115;
+const KEYCODE_SCROLL_LOCK: i32 = 116;
+const KEYCODE_META_LEFT: i32 = 117;
+const KEYCODE_META_RIGHT: i32 = 118;
+const KEYCODE_FUNCTION: i32 = 119;
+const KEYCODE_NUM_LOCK: i32 = 143;
+
+// Modifier states: values are from /frameworks/native/include/android/input.h
+const META_ALT_ON: u32 = 0x02;
+const META_ALT_LEFT_ON: u32 = 0x10;
+const META_ALT_RIGHT_ON: u32 = 0x20;
+const META_SHIFT_ON: u32 = 0x01;
+const META_SHIFT_LEFT_ON: u32 = 0x40;
+const META_SHIFT_RIGHT_ON: u32 = 0x80;
+const META_CTRL_ON: u32 = 0x1000;
+const META_CTRL_LEFT_ON: u32 = 0x2000;
+const META_CTRL_RIGHT_ON: u32 = 0x4000;
+const META_META_ON: u32 = 0x10000;
+const META_META_LEFT_ON: u32 = 0x20000;
+const META_META_RIGHT_ON: u32 = 0x40000;
+
+pub struct StickyKeysFilter {
+    next: Box<dyn Filter + Send + Sync>,
+    listener: ModifierStateListener,
+    /// Tracking devices that contributed to the modifier state.
+    contributing_devices: HashSet<i32>,
+    /// State describing the current enabled modifiers. This contain both locked and non-locked
+    /// modifier state bits.
+    modifier_state: u32,
+    /// State describing the current locked modifiers. These modifiers will not be cleared on a
+    /// non-modifier key press. They will be cleared only if the locked modifier key is pressed
+    /// again.
+    locked_modifier_state: u32,
+}
+
+impl StickyKeysFilter {
+    /// Create a new StickyKeysFilter instance.
+    pub fn new(
+        next: Box<dyn Filter + Send + Sync>,
+        listener: ModifierStateListener,
+    ) -> StickyKeysFilter {
+        Self {
+            next,
+            listener,
+            contributing_devices: HashSet::new(),
+            modifier_state: 0,
+            locked_modifier_state: 0,
+        }
+    }
+}
+
+impl Filter for StickyKeysFilter {
+    fn notify_key(&mut self, event: &KeyEvent) {
+        let up = event.action == KeyEventAction::UP;
+        let mut modifier_state = self.modifier_state;
+        let mut locked_modifier_state = self.locked_modifier_state;
+        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
+            // the KeyEvent.
+            let old_modifier_state = event.metaState as u32;
+            let mut new_event = *event;
+            // Send the current modifier state with the key event before clearing non-locked
+            // modifier state
+            new_event.metaState =
+                (clear_ephemeral_modifier_state(old_modifier_state) | modifier_state) as i32;
+            self.next.notify_key(&new_event);
+            if up && !is_modifier_key(event.keyCode) {
+                modifier_state =
+                    clear_ephemeral_modifier_state(modifier_state) | locked_modifier_state;
+            }
+        } else if up {
+            // Update contributing devices to track keyboards
+            self.contributing_devices.insert(event.deviceId);
+            // If ephemeral modifier key, capture the key and update the sticky modifier states
+            let modifier_key_mask = get_ephemeral_modifier_key_mask(event.keyCode);
+            let symmetrical_modifier_key_mask = get_symmetrical_modifier_key_mask(event.keyCode);
+            if locked_modifier_state & modifier_key_mask != 0 {
+                locked_modifier_state &= !symmetrical_modifier_key_mask;
+                modifier_state &= !symmetrical_modifier_key_mask;
+            } else if modifier_key_mask & modifier_state != 0 {
+                locked_modifier_state |= modifier_key_mask;
+                modifier_state =
+                    (modifier_state & !symmetrical_modifier_key_mask) | modifier_key_mask;
+            } else {
+                modifier_state |= modifier_key_mask;
+            }
+        }
+        if self.modifier_state != modifier_state
+            || self.locked_modifier_state != locked_modifier_state
+        {
+            self.modifier_state = modifier_state;
+            self.locked_modifier_state = locked_modifier_state;
+            self.listener.modifier_state_changed(modifier_state, locked_modifier_state);
+        }
+    }
+
+    fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]) {
+        // Clear state if all contributing devices removed
+        self.contributing_devices.retain(|id| device_infos.iter().any(|x| *id == x.deviceId));
+        if self.contributing_devices.is_empty()
+            && (self.modifier_state != 0 || self.locked_modifier_state != 0)
+        {
+            self.modifier_state = 0;
+            self.locked_modifier_state = 0;
+            self.listener.modifier_state_changed(0, 0);
+        }
+        self.next.notify_devices_changed(device_infos);
+    }
+}
+
+fn is_modifier_key(keycode: i32) -> bool {
+    matches!(
+        keycode,
+        KEYCODE_ALT_LEFT
+            | KEYCODE_ALT_RIGHT
+            | KEYCODE_SHIFT_LEFT
+            | KEYCODE_SHIFT_RIGHT
+            | KEYCODE_CTRL_LEFT
+            | KEYCODE_CTRL_RIGHT
+            | KEYCODE_META_LEFT
+            | KEYCODE_META_RIGHT
+            | KEYCODE_SYM
+            | KEYCODE_FUNCTION
+            | KEYCODE_CAPS_LOCK
+            | KEYCODE_NUM_LOCK
+            | KEYCODE_SCROLL_LOCK
+    )
+}
+
+fn is_ephemeral_modifier_key(keycode: i32) -> bool {
+    matches!(
+        keycode,
+        KEYCODE_ALT_LEFT
+            | KEYCODE_ALT_RIGHT
+            | KEYCODE_SHIFT_LEFT
+            | KEYCODE_SHIFT_RIGHT
+            | KEYCODE_CTRL_LEFT
+            | KEYCODE_CTRL_RIGHT
+            | KEYCODE_META_LEFT
+            | KEYCODE_META_RIGHT
+    )
+}
+
+fn get_ephemeral_modifier_key_mask(keycode: i32) -> u32 {
+    match keycode {
+        KEYCODE_ALT_LEFT => META_ALT_LEFT_ON | META_ALT_ON,
+        KEYCODE_ALT_RIGHT => META_ALT_RIGHT_ON | META_ALT_ON,
+        KEYCODE_SHIFT_LEFT => META_SHIFT_LEFT_ON | META_SHIFT_ON,
+        KEYCODE_SHIFT_RIGHT => META_SHIFT_RIGHT_ON | META_SHIFT_ON,
+        KEYCODE_CTRL_LEFT => META_CTRL_LEFT_ON | META_CTRL_ON,
+        KEYCODE_CTRL_RIGHT => META_CTRL_RIGHT_ON | META_CTRL_ON,
+        KEYCODE_META_LEFT => META_META_LEFT_ON | META_META_ON,
+        KEYCODE_META_RIGHT => META_META_RIGHT_ON | META_META_ON,
+        _ => 0,
+    }
+}
+
+/// Modifier mask including both left and right versions of a modifier key.
+fn get_symmetrical_modifier_key_mask(keycode: i32) -> u32 {
+    match keycode {
+        KEYCODE_ALT_LEFT | KEYCODE_ALT_RIGHT => META_ALT_LEFT_ON | META_ALT_RIGHT_ON | META_ALT_ON,
+        KEYCODE_SHIFT_LEFT | KEYCODE_SHIFT_RIGHT => {
+            META_SHIFT_LEFT_ON | META_SHIFT_RIGHT_ON | META_SHIFT_ON
+        }
+        KEYCODE_CTRL_LEFT | KEYCODE_CTRL_RIGHT => {
+            META_CTRL_LEFT_ON | META_CTRL_RIGHT_ON | META_CTRL_ON
+        }
+        KEYCODE_META_LEFT | KEYCODE_META_RIGHT => {
+            META_META_LEFT_ON | META_META_RIGHT_ON | META_META_ON
+        }
+        _ => 0,
+    }
+}
+
+fn clear_ephemeral_modifier_state(modifier_state: u32) -> u32 {
+    modifier_state
+        & !(META_ALT_LEFT_ON
+            | META_ALT_RIGHT_ON
+            | META_ALT_ON
+            | META_SHIFT_LEFT_ON
+            | META_SHIFT_RIGHT_ON
+            | META_SHIFT_ON
+            | META_CTRL_LEFT_ON
+            | META_CTRL_RIGHT_ON
+            | META_CTRL_ON
+            | META_META_LEFT_ON
+            | META_META_RIGHT_ON
+            | META_META_ON)
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::input_filter::{
+        test_callbacks::TestCallbacks, test_filter::TestFilter, Filter, ModifierStateListener,
+    };
+    use crate::sticky_keys_filter::{
+        StickyKeysFilter, KEYCODE_ALT_LEFT, KEYCODE_ALT_RIGHT, KEYCODE_CAPS_LOCK,
+        KEYCODE_CTRL_LEFT, KEYCODE_CTRL_RIGHT, KEYCODE_FUNCTION, KEYCODE_META_LEFT,
+        KEYCODE_META_RIGHT, KEYCODE_NUM_LOCK, KEYCODE_SCROLL_LOCK, KEYCODE_SHIFT_LEFT,
+        KEYCODE_SHIFT_RIGHT, KEYCODE_SYM, META_ALT_LEFT_ON, META_ALT_ON, META_ALT_RIGHT_ON,
+        META_CTRL_LEFT_ON, META_CTRL_ON, META_CTRL_RIGHT_ON, META_META_LEFT_ON, META_META_ON,
+        META_META_RIGHT_ON, META_SHIFT_LEFT_ON, META_SHIFT_ON, META_SHIFT_RIGHT_ON,
+    };
+    use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
+    use binder::Strong;
+    use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+        DeviceInfo::DeviceInfo, IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks,
+        KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
+    };
+    use std::sync::{Arc, RwLock};
+
+    static DEVICE_ID: i32 = 1;
+    static KEY_A: i32 = 29;
+    static BASE_KEY_DOWN: KeyEvent = KeyEvent {
+        id: 1,
+        deviceId: DEVICE_ID,
+        downTime: 0,
+        readTime: 0,
+        eventTime: 0,
+        source: Source::KEYBOARD,
+        displayId: 0,
+        policyFlags: 0,
+        action: KeyEventAction::DOWN,
+        flags: 0,
+        keyCode: 0,
+        scanCode: 0,
+        metaState: 0,
+    };
+
+    static BASE_KEY_UP: KeyEvent = KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_DOWN };
+
+    #[test]
+    fn test_notify_key_consumes_ephemeral_modifier_keys() {
+        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() {
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: *key_code, ..BASE_KEY_DOWN });
+            assert!(test_filter.last_event().is_none());
+
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: *key_code, ..BASE_KEY_UP });
+            assert!(test_filter.last_event().is_none());
+        }
+    }
+
+    #[test]
+    fn test_notify_key_passes_non_ephemeral_modifier_keys() {
+        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_CAPS_LOCK,
+            KEYCODE_NUM_LOCK,
+            KEYCODE_SCROLL_LOCK,
+            KEYCODE_FUNCTION,
+            KEYCODE_SYM,
+        ];
+        for key_code in key_codes.iter() {
+            let event = KeyEvent { keyCode: *key_code, ..BASE_KEY_DOWN };
+            sticky_keys_filter.notify_key(&event);
+            assert_eq!(test_filter.last_event().unwrap(), event);
+            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_modifier_keys() {
+        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 event = KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN };
+        sticky_keys_filter.notify_key(&event);
+        assert_eq!(test_filter.last_event().unwrap(), event);
+
+        let event = KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP };
+        sticky_keys_filter.notify_key(&event);
+        assert_eq!(test_filter.last_event().unwrap(), event);
+    }
+
+    #[test]
+    fn test_modifier_state_updated_on_modifier_key_press() {
+        let mut test_filter = TestFilter::new();
+        let mut 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 test_states = &[
+            (KEYCODE_ALT_LEFT, META_ALT_ON | META_ALT_LEFT_ON),
+            (KEYCODE_ALT_RIGHT, META_ALT_ON | META_ALT_RIGHT_ON),
+            (KEYCODE_CTRL_LEFT, META_CTRL_ON | META_CTRL_LEFT_ON),
+            (KEYCODE_CTRL_RIGHT, META_CTRL_ON | META_CTRL_RIGHT_ON),
+            (KEYCODE_SHIFT_LEFT, META_SHIFT_ON | META_SHIFT_LEFT_ON),
+            (KEYCODE_SHIFT_RIGHT, META_SHIFT_ON | META_SHIFT_RIGHT_ON),
+            (KEYCODE_META_LEFT, META_META_ON | META_META_LEFT_ON),
+            (KEYCODE_META_RIGHT, META_META_ON | META_META_RIGHT_ON),
+        ];
+        for test_state in test_states.iter() {
+            test_filter.clear();
+            test_callbacks.clear();
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_DOWN });
+            assert_eq!(test_callbacks.get_last_modifier_state(), 0);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP });
+            assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+
+            // Re-send keys to lock it
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_DOWN });
+            assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP });
+            assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), test_state.1);
+
+            // Re-send keys to clear
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_DOWN });
+            assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), test_state.1);
+
+            sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP });
+            assert_eq!(test_callbacks.get_last_modifier_state(), 0);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+        }
+    }
+
+    #[test]
+    fn test_modifier_state_cleared_on_non_modifier_key_press() {
+        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 });
+
+        assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON);
+        assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP });
+
+        assert_eq!(test_callbacks.get_last_modifier_state(), 0);
+        assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+    }
+
+    #[test]
+    fn test_locked_modifier_state_not_cleared_on_non_modifier_key_press() {
+        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: 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: KEYCODE_SHIFT_LEFT, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_SHIFT_LEFT, ..BASE_KEY_UP });
+
+        assert_eq!(
+            test_callbacks.get_last_modifier_state(),
+            META_SHIFT_LEFT_ON | META_SHIFT_ON | META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+        assert_eq!(
+            test_callbacks.get_last_locked_modifier_state(),
+            META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP });
+
+        assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON);
+        assert_eq!(
+            test_callbacks.get_last_locked_modifier_state(),
+            META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+    }
+
+    #[test]
+    fn test_key_events_have_sticky_modifier_state() {
+        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_DOWN });
+        assert_eq!(
+            test_filter.last_event().unwrap().metaState as u32,
+            META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP });
+        assert_eq!(
+            test_filter.last_event().unwrap().metaState as u32,
+            META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+    }
+
+    #[test]
+    fn test_modifier_state_not_cleared_until_all_devices_removed() {
+        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 {
+            deviceId: 1,
+            keyCode: KEYCODE_CTRL_LEFT,
+            ..BASE_KEY_DOWN
+        });
+        sticky_keys_filter.notify_key(&KeyEvent {
+            deviceId: 1,
+            keyCode: KEYCODE_CTRL_LEFT,
+            ..BASE_KEY_UP
+        });
+
+        sticky_keys_filter.notify_key(&KeyEvent {
+            deviceId: 2,
+            keyCode: KEYCODE_CTRL_LEFT,
+            ..BASE_KEY_DOWN
+        });
+        sticky_keys_filter.notify_key(&KeyEvent {
+            deviceId: 2,
+            keyCode: KEYCODE_CTRL_LEFT,
+            ..BASE_KEY_UP
+        });
+
+        sticky_keys_filter.notify_devices_changed(&[DeviceInfo { deviceId: 2, external: true }]);
+        assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON);
+        assert_eq!(
+            test_callbacks.get_last_locked_modifier_state(),
+            META_CTRL_LEFT_ON | META_CTRL_ON
+        );
+
+        sticky_keys_filter.notify_devices_changed(&[]);
+        assert_eq!(test_callbacks.get_last_modifier_state(), 0);
+        assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+    }
+
+    fn setup_filter(
+        next: Box<dyn Filter + Send + Sync>,
+        callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
+    ) -> StickyKeysFilter {
+        StickyKeysFilter::new(next, ModifierStateListener::new(callbacks))
+    }
+}
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 490cf2f..193b84d 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -34,7 +34,9 @@
 
 // Helpers to std::visit with lambdas.
 template <typename... V>
-struct Visitor : V... {};
+struct Visitor : V... {
+    using V::operator()...;
+};
 template <typename... V>
 Visitor(V...) -> Visitor<V...>;
 
diff --git a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
index 0bd8fb3..dc5a213 100644
--- a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
@@ -65,6 +65,27 @@
     std::map<int32_t /*displayId*/, InputVerifier> mVerifiers;
 };
 
+void scrambleWindow(FuzzedDataProvider& fdp, FakeWindowHandle& window) {
+    const int32_t left = fdp.ConsumeIntegralInRange<int32_t>(0, 100);
+    const int32_t top = fdp.ConsumeIntegralInRange<int32_t>(0, 100);
+    const int32_t width = fdp.ConsumeIntegralInRange<int32_t>(0, 100);
+    const int32_t height = fdp.ConsumeIntegralInRange<int32_t>(0, 100);
+
+    window.setFrame(Rect(left, top, left + width, top + height));
+    window.setSlippery(fdp.ConsumeBool());
+    window.setDupTouchToWallpaper(fdp.ConsumeBool());
+    window.setIsWallpaper(fdp.ConsumeBool());
+    window.setVisible(fdp.ConsumeBool());
+    window.setPreventSplitting(fdp.ConsumeBool());
+    const bool isTrustedOverlay = fdp.ConsumeBool();
+    window.setTrustedOverlay(isTrustedOverlay);
+    if (isTrustedOverlay) {
+        window.setSpy(fdp.ConsumeBool());
+    } else {
+        window.setSpy(false);
+    }
+}
+
 } // namespace
 
 sp<FakeWindowHandle> generateFuzzedWindow(FuzzedDataProvider& fdp, InputDispatcher& dispatcher,
@@ -73,17 +94,9 @@
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     std::string windowName = android::base::StringPrintf("Win") + std::to_string(windowNumber++);
     sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, dispatcher, "Fake", displayId);
+            sp<FakeWindowHandle>::make(application, dispatcher, windowName, displayId);
 
-    const int32_t left = fdp.ConsumeIntegralInRange<int32_t>(0, 100);
-    const int32_t top = fdp.ConsumeIntegralInRange<int32_t>(0, 100);
-    const int32_t width = fdp.ConsumeIntegralInRange<int32_t>(0, 100);
-    const int32_t height = fdp.ConsumeIntegralInRange<int32_t>(0, 100);
-
-    window->setFrame(Rect(left, top, left + width, top + height));
-    window->setSlippery(fdp.ConsumeBool());
-    window->setDupTouchToWallpaper(fdp.ConsumeBool());
-    window->setTrustedOverlay(fdp.ConsumeBool());
+    scrambleWindow(fdp, *window);
     return window;
 }
 
@@ -113,7 +126,14 @@
                     windowsPerDisplay.erase(displayId);
                 }
             },
-            // Could also clone a window, change flags, reposition, etc...
+            // Change flags or move some of the existing windows
+            [&]() -> void {
+                for (auto& window : windows) {
+                    if (fdp.ConsumeBool()) {
+                        scrambleWindow(fdp, *window);
+                    }
+                }
+            },
     })();
 }
 
diff --git a/services/sensorservice/Android.bp b/services/sensorservice/Android.bp
index 11c56a8..0dd4dd6 100644
--- a/services/sensorservice/Android.bp
+++ b/services/sensorservice/Android.bp
@@ -7,6 +7,18 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
+aconfig_declarations {
+    name: "sensorservice_flags",
+    package: "com.android.frameworks.sensorservice.flags",
+    srcs: ["senserservice_flags.aconfig"],
+}
+
+cc_aconfig_library {
+    name: "sensorservice_flags_c_lib",
+    aconfig_declarations: "dynamic_sensors_flags",
+    host_supported: true,
+}
+
 cc_library {
     name: "libsensorservice",
 
diff --git a/services/sensorservice/senserservice_flags.aconfig b/services/sensorservice/senserservice_flags.aconfig
new file mode 100644
index 0000000..6b74f6f
--- /dev/null
+++ b/services/sensorservice/senserservice_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.frameworks.sensorservice.flags"
+
+flag {
+  name: "dynamic_sensor_hal_reconnect_handling"
+  namespace: "sensors"
+  description: "This flag controls if the dynamic sensor data will be clean up after HAL is disconnected."
+  bug: "307782607"
+}
\ No newline at end of file
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 8b736be..9e35717 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -148,8 +148,7 @@
     MOCK_METHOD(const aidl::android::hardware::graphics::composer3::OverlayProperties&,
                 getOverlaySupport, (), (const, override));
     MOCK_METHOD(status_t, setRefreshRateChangedCallbackDebugEnabled, (PhysicalDisplayId, bool));
-    MOCK_METHOD(status_t, notifyExpectedPresentIfRequired,
-                (PhysicalDisplayId, Period, TimePoint, Fps, std::optional<Period>));
+    MOCK_METHOD(status_t, notifyExpectedPresent, (PhysicalDisplayId, TimePoint, Fps));
 };
 
 } // namespace mock
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index c25f9dd..64a8ae7 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -271,7 +271,10 @@
             }
         }
     }
-
+    if (getLayerLifecycleBatchCommand()) {
+        mEnableLayerCommandBatchingFlag =
+                FlagManager::getInstance().enable_layer_command_batching();
+    }
     ALOGI("Loaded AIDL composer3 HAL service");
 }
 
@@ -288,7 +291,7 @@
     }
 }
 
-bool AidlComposer::getDisplayConfigurationsSupported() const {
+bool AidlComposer::isVrrSupported() const {
     return mComposerInterfaceVersion >= 3 && FlagManager::getInstance().vrr_config();
 }
 
@@ -407,25 +410,58 @@
 
 Error AidlComposer::createLayer(Display display, Layer* outLayer) {
     int64_t layer;
-    const auto status = mAidlComposerClient->createLayer(translate<int64_t>(display),
-                                                         kMaxLayerBufferCount, &layer);
-    if (!status.isOk()) {
-        ALOGE("createLayer failed %s", status.getDescription().c_str());
-        return static_cast<Error>(status.getServiceSpecificError());
+    Error error = Error::NONE;
+    if (!mEnableLayerCommandBatchingFlag) {
+        const auto status = mAidlComposerClient->createLayer(translate<int64_t>(display),
+                                                             kMaxLayerBufferCount, &layer);
+        if (!status.isOk()) {
+            ALOGE("createLayer failed %s", status.getDescription().c_str());
+            return static_cast<Error>(status.getServiceSpecificError());
+        }
+    } else {
+        // generate a unique layerID. map in AidlComposer with <SF_layerID, HWC_layerID>
+        // Add this as a new displayCommand in execute command.
+        // return the SF generated layerID instead of calling HWC
+        layer = mLayerID++;
+        mMutex.lock_shared();
+        if (auto writer = getWriter(display)) {
+            writer->get().setLayerLifecycleBatchCommandType(translate<int64_t>(display),
+                                                            translate<int64_t>(layer),
+                                                            LayerLifecycleBatchCommandType::CREATE);
+            writer->get().setNewBufferSlotCount(translate<int64_t>(display),
+                                                translate<int64_t>(layer), kMaxLayerBufferCount);
+        } else {
+            error = Error::BAD_DISPLAY;
+        }
+        mMutex.unlock_shared();
     }
-
     *outLayer = translate<Layer>(layer);
-    return Error::NONE;
+    return error;
 }
 
 Error AidlComposer::destroyLayer(Display display, Layer layer) {
-    const auto status = mAidlComposerClient->destroyLayer(translate<int64_t>(display),
-                                                          translate<int64_t>(layer));
-    if (!status.isOk()) {
-        ALOGE("destroyLayer failed %s", status.getDescription().c_str());
-        return static_cast<Error>(status.getServiceSpecificError());
+    Error error = Error::NONE;
+    if (!mEnableLayerCommandBatchingFlag) {
+        const auto status = mAidlComposerClient->destroyLayer(translate<int64_t>(display),
+                                                              translate<int64_t>(layer));
+        if (!status.isOk()) {
+            ALOGE("destroyLayer failed %s", status.getDescription().c_str());
+            return static_cast<Error>(status.getServiceSpecificError());
+        }
+    } else {
+        mMutex.lock_shared();
+        if (auto writer = getWriter(display)) {
+            writer->get()
+                    .setLayerLifecycleBatchCommandType(translate<int64_t>(display),
+                                                       translate<int64_t>(layer),
+                                                       LayerLifecycleBatchCommandType::DESTROY);
+        } else {
+            error = Error::BAD_DISPLAY;
+        }
+        mMutex.unlock_shared();
     }
-    return Error::NONE;
+
+    return error;
 }
 
 Error AidlComposer::getActiveConfig(Display display, Config* outConfig) {
@@ -591,6 +627,13 @@
     return Error::NONE;
 }
 
+bool AidlComposer::getLayerLifecycleBatchCommand() {
+    std::vector<Capability> capabilities = getCapabilities();
+    bool hasCapability = std::find(capabilities.begin(), capabilities.end(),
+                                   Capability::LAYER_LIFECYCLE_BATCH_COMMAND) != capabilities.end();
+    return hasCapability;
+}
+
 Error AidlComposer::getOverlaySupport(AidlOverlayProperties* outProperties) {
     const auto status = mAidlComposerClient->getOverlaySupport(outProperties);
     if (!status.isOk()) {
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
index 51ac1f5..ea0e53a 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -66,7 +66,7 @@
     ~AidlComposer() override;
 
     bool isSupported(OptionalFeature) const;
-    bool getDisplayConfigurationsSupported() const;
+    bool isVrrSupported() const;
 
     std::vector<aidl::android::hardware::graphics::composer3::Capability> getCapabilities()
             override;
@@ -262,7 +262,7 @@
     void removeDisplay(Display) EXCLUDES(mMutex);
     void addReader(Display) REQUIRES(mMutex);
     void removeReader(Display) REQUIRES(mMutex);
-
+    bool getLayerLifecycleBatchCommand();
     bool hasMultiThreadedPresentSupport(Display);
 
     // 64KiB minus a small space for metadata such as read/write pointers
@@ -293,6 +293,8 @@
     ftl::SharedMutex mMutex;
 
     int32_t mComposerInterfaceVersion = 1;
+    bool mEnableLayerCommandBatchingFlag = false;
+    std::atomic<int64_t> mLayerID = 1;
 
     // Buffer slots for layers are cleared by setting the slot buffer to this buffer.
     sp<GraphicBuffer> mClearSlotBuffer;
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index 1a24222..bc067a0 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -105,7 +105,7 @@
     };
 
     virtual bool isSupported(OptionalFeature) const = 0;
-    virtual bool getDisplayConfigurationsSupported() const = 0;
+    virtual bool isVrrSupported() const = 0;
 
     virtual std::vector<aidl::android::hardware::graphics::composer3::Capability>
     getCapabilities() = 0;
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 6b67865..3ffd8ea 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -15,6 +15,7 @@
  */
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
+#include <chrono>
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
 
@@ -78,59 +79,6 @@
 using aidl::android::hardware::graphics::composer3::DisplayCapability;
 namespace hal = android::hardware::graphics::composer::hal;
 
-namespace {
-bool isFrameIntervalOnCadence(android::TimePoint expectedPresentTime,
-                              android::TimePoint lastExpectedPresentTimestamp,
-                              android::Fps lastFrameInterval, android::Period timeout,
-                              android::Duration threshold) {
-    if (lastFrameInterval.getPeriodNsecs() == 0) {
-        return false;
-    }
-
-    const auto expectedPresentTimeDeltaNs =
-            expectedPresentTime.ns() - lastExpectedPresentTimestamp.ns();
-
-    if (expectedPresentTimeDeltaNs > timeout.ns()) {
-        return false;
-    }
-
-    const auto expectedPresentPeriods = static_cast<nsecs_t>(
-            std::round(static_cast<float>(expectedPresentTimeDeltaNs) /
-                       static_cast<float>(lastFrameInterval.getPeriodNsecs())));
-    const auto calculatedPeriodsOutNs = lastFrameInterval.getPeriodNsecs() * expectedPresentPeriods;
-    const auto calculatedExpectedPresentTimeNs =
-            lastExpectedPresentTimestamp.ns() + calculatedPeriodsOutNs;
-    const auto presentTimeDelta =
-            std::abs(expectedPresentTime.ns() - calculatedExpectedPresentTimeNs);
-    return presentTimeDelta < threshold.ns();
-}
-
-bool isExpectedPresentWithinTimeout(android::TimePoint expectedPresentTime,
-                                    android::TimePoint lastExpectedPresentTimestamp,
-                                    std::optional<android::Period> timeoutOpt,
-                                    android::Duration threshold) {
-    if (!timeoutOpt) {
-        // Always within timeout if timeoutOpt is absent and don't send hint
-        // for the timeout
-        return true;
-    }
-
-    if (timeoutOpt->ns() == 0) {
-        // Always outside timeout if timeoutOpt is 0 and always send
-        // the hint for the timeout.
-        return false;
-    }
-
-    if (expectedPresentTime.ns() < lastExpectedPresentTimestamp.ns() + timeoutOpt->ns()) {
-        return true;
-    }
-
-    // Check if within the threshold as it can be just outside the timeout
-    return std::abs(expectedPresentTime.ns() -
-                    (lastExpectedPresentTimestamp.ns() + timeoutOpt->ns())) < threshold.ns();
-}
-} // namespace
-
 namespace android {
 
 HWComposer::~HWComposer() = default;
@@ -321,7 +269,7 @@
 
     const auto hwcDisplayId = mDisplayData.at(displayId).hwcDisplay->getId();
 
-    if (mComposer->getDisplayConfigurationsSupported()) {
+    if (mComposer->isVrrSupported()) {
         return getModesFromDisplayConfigurations(hwcDisplayId, maxFrameIntervalNs);
     }
 
@@ -538,13 +486,6 @@
     }();
 
     displayData.validateWasSkipped = false;
-    {
-        std::scoped_lock lock{displayData.expectedPresentLock};
-        if (expectedPresentTime > displayData.lastExpectedPresentTimestamp.ns()) {
-            displayData.lastExpectedPresentTimestamp = TimePoint::fromNs(expectedPresentTime);
-        }
-    }
-
     ATRACE_FORMAT("NextFrameInterval %d_Hz", frameInterval.getIntValue());
     if (canSkipValidate) {
         sp<Fence> outPresentFence = Fence::NO_FENCE;
@@ -939,55 +880,15 @@
     return NO_ERROR;
 }
 
-status_t HWComposer::notifyExpectedPresentIfRequired(PhysicalDisplayId displayId,
-                                                     Period vsyncPeriod,
-                                                     TimePoint expectedPresentTime,
-                                                     Fps frameInterval,
-                                                     std::optional<Period> timeoutOpt) {
+status_t HWComposer::notifyExpectedPresent(PhysicalDisplayId displayId,
+                                           TimePoint expectedPresentTime, Fps frameInterval) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
-    auto& displayData = mDisplayData[displayId];
-    if (!displayData.hwcDisplay) {
-        // Display setup has not completed yet
-        return BAD_INDEX;
-    }
-    {
-        std::scoped_lock lock{displayData.expectedPresentLock};
-        const auto lastExpectedPresentTimestamp = displayData.lastExpectedPresentTimestamp;
-        const auto lastFrameInterval = displayData.lastFrameInterval;
-        displayData.lastFrameInterval = frameInterval;
-        const auto threshold = Duration::fromNs(vsyncPeriod.ns() / 2);
-
-        const constexpr nsecs_t kOneSecondNs =
-                std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count();
-        const bool frameIntervalIsOnCadence =
-                isFrameIntervalOnCadence(expectedPresentTime, lastExpectedPresentTimestamp,
-                                         lastFrameInterval,
-                                         Period::fromNs(timeoutOpt && timeoutOpt->ns() > 0
-                                                                ? timeoutOpt->ns()
-                                                                : kOneSecondNs),
-                                         threshold);
-
-        const bool expectedPresentWithinTimeout =
-                isExpectedPresentWithinTimeout(expectedPresentTime, lastExpectedPresentTimestamp,
-                                               timeoutOpt, threshold);
-
-        using fps_approx_ops::operator!=;
-        if (frameIntervalIsOnCadence && frameInterval != lastFrameInterval) {
-            displayData.lastExpectedPresentTimestamp = expectedPresentTime;
-        }
-
-        if (expectedPresentWithinTimeout && frameIntervalIsOnCadence) {
-            return NO_ERROR;
-        }
-
-        displayData.lastExpectedPresentTimestamp = expectedPresentTime;
-    }
-    ATRACE_FORMAT("%s ExpectedPresentTime %" PRId64 " frameIntervalNs %d", __func__,
-                  expectedPresentTime, frameInterval.getPeriodNsecs());
-    const auto error = mComposer->notifyExpectedPresent(displayData.hwcDisplay->getId(),
+    ATRACE_FORMAT("%s ExpectedPresentTime in %.2fms frameInterval %.2fms", __func__,
+                  ticks<std::milli, float>(expectedPresentTime - TimePoint::now()),
+                  ticks<std::milli, float>(Duration::fromNs(frameInterval.getPeriodNsecs())));
+    const auto error = mComposer->notifyExpectedPresent(mDisplayData[displayId].hwcDisplay->getId(),
                                                         expectedPresentTime.ns(),
                                                         frameInterval.getPeriodNsecs());
-
     if (error != hal::Error::NONE) {
         ALOGE("Error in notifyExpectedPresent call %s", to_string(error).c_str());
         return INVALID_OPERATION;
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index af62731..4ca528a 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -303,10 +303,8 @@
             aidl::android::hardware::graphics::common::HdrConversionStrategy,
             aidl::android::hardware::graphics::common::Hdr*) = 0;
     virtual status_t setRefreshRateChangedCallbackDebugEnabled(PhysicalDisplayId, bool enabled) = 0;
-    virtual status_t notifyExpectedPresentIfRequired(PhysicalDisplayId, Period vsyncPeriod,
-                                                     TimePoint expectedPresentTime,
-                                                     Fps frameInterval,
-                                                     std::optional<Period> timeoutOpt) = 0;
+    virtual status_t notifyExpectedPresent(PhysicalDisplayId, TimePoint expectedPresentTime,
+                                           Fps frameInterval) = 0;
 };
 
 static inline bool operator==(const android::HWComposer::DeviceRequestedChanges& lhs,
@@ -466,9 +464,8 @@
             aidl::android::hardware::graphics::common::HdrConversionStrategy,
             aidl::android::hardware::graphics::common::Hdr*) override;
     status_t setRefreshRateChangedCallbackDebugEnabled(PhysicalDisplayId, bool enabled) override;
-    status_t notifyExpectedPresentIfRequired(PhysicalDisplayId, Period vsyncPeriod,
-                                             TimePoint expectedPresentTime, Fps frameInterval,
-                                             std::optional<Period> timeoutOpt) override;
+    status_t notifyExpectedPresent(PhysicalDisplayId, TimePoint expectedPresentTime,
+                                   Fps frameInterval) override;
 
     // for debugging ----------------------------------------------------------
     void dump(std::string& out) const override;
@@ -494,7 +491,6 @@
 private:
     // For unit tests
     friend TestableSurfaceFlinger;
-    friend HWComposerTest;
 
     struct DisplayData {
         std::unique_ptr<HWC2::Display> hwcDisplay;
@@ -502,11 +498,6 @@
         sp<Fence> lastPresentFence = Fence::NO_FENCE; // signals when the last set op retires
         nsecs_t lastPresentTimestamp = 0;
 
-        std::mutex expectedPresentLock;
-        TimePoint lastExpectedPresentTimestamp GUARDED_BY(expectedPresentLock) =
-                TimePoint::fromNs(0);
-        Fps lastFrameInterval GUARDED_BY(expectedPresentLock) = Fps::fromValue(0);
-
         std::unordered_map<HWC2::Layer*, sp<Fence>> releaseFences;
 
         bool validateWasSkipped;
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index 5f1d5f8..c4ff9cc 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -276,8 +276,8 @@
     }
 }
 
-bool HidlComposer::getDisplayConfigurationsSupported() const {
-    // getDisplayConfigurations is not supported on the HIDL composer.
+bool HidlComposer::isVrrSupported() const {
+    // VRR is not supported on the HIDL composer.
     return false;
 };
 
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
index c768d27..d78bfb7 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -167,7 +167,7 @@
     ~HidlComposer() override;
 
     bool isSupported(OptionalFeature) const;
-    bool getDisplayConfigurationsSupported() const;
+    bool isVrrSupported() const;
 
     std::vector<aidl::android::hardware::graphics::composer3::Capability> getCapabilities()
             override;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 6a85788..ce59a04 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -630,6 +630,7 @@
 }
 
 void Scheduler::addPresentFence(PhysicalDisplayId id, std::shared_ptr<FenceTime> fence) {
+    ATRACE_NAME(ftl::Concat(__func__, ' ', id.value).c_str());
     const auto scheduleOpt =
             (ftl::FakeGuard(mDisplayLock), mDisplays.get(id)).and_then([](const Display& display) {
                 return display.powerMode == hal::PowerMode::OFF
@@ -640,7 +641,8 @@
     if (!scheduleOpt) return;
     const auto& schedule = scheduleOpt->get();
 
-    if (const bool needMoreSignals = schedule->getController().addPresentFence(std::move(fence))) {
+    const bool needMoreSignals = schedule->getController().addPresentFence(std::move(fence));
+    if (needMoreSignals) {
         schedule->enableHardwareVsync();
     } else {
         constexpr bool kDisallow = false;
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index 7379a46..0d79a39 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -88,6 +88,7 @@
             (timestamp - aValidTimestamp) % idealPeriod() * kMaxPercent / idealPeriod();
     if (percent >= kOutlierTolerancePercent &&
         percent <= (kMaxPercent - kOutlierTolerancePercent)) {
+        ATRACE_FORMAT_INSTANT("timestamp is not aligned with model");
         return false;
     }
 
@@ -98,6 +99,7 @@
     const auto distancePercent = std::abs(*iter - timestamp) * kMaxPercent / idealPeriod();
     if (distancePercent < kOutlierTolerancePercent) {
         // duplicate timestamp
+        ATRACE_FORMAT_INSTANT("duplicate timestamp");
         return false;
     }
     return true;
@@ -126,6 +128,8 @@
 }
 
 bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
+    ATRACE_CALL();
+
     std::lock_guard lock(mMutex);
 
     if (!validate(timestamp)) {
@@ -144,6 +148,8 @@
         } else {
             mKnownTimestamp = timestamp;
         }
+        ATRACE_FORMAT_INSTANT("timestamp rejected. mKnownTimestamp was %.2fms ago",
+            (systemTime() - *mKnownTimestamp) / 1e6f);
         return false;
     }
 
@@ -515,6 +521,8 @@
 }
 
 void VSyncPredictor::clearTimestamps() {
+    ATRACE_CALL();
+
     if (!mTimestamps.empty()) {
         auto const maxRb = *std::max_element(mTimestamps.begin(), mTimestamps.end());
         if (mKnownTimestamp) {
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
index 24737e4..186a2d6 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
@@ -53,6 +53,8 @@
 VSyncReactor::~VSyncReactor() = default;
 
 bool VSyncReactor::addPresentFence(std::shared_ptr<FenceTime> fence) {
+    ATRACE_CALL();
+
     if (!fence) {
         return false;
     }
@@ -64,6 +66,8 @@
 
     std::lock_guard lock(mMutex);
     if (mExternalIgnoreFences || mInternalIgnoreFences) {
+        ATRACE_FORMAT_INSTANT("mExternalIgnoreFences=%d mInternalIgnoreFences=%d",
+            mExternalIgnoreFences, mInternalIgnoreFences);
         return true;
     }
 
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
index ff1c9e9..db6a187 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
@@ -19,6 +19,7 @@
 #include <common/FlagManager.h>
 
 #include <ftl/fake_guard.h>
+#include <gui/TraceUtils.h>
 #include <scheduler/Fps.h>
 #include <scheduler/Timer.h>
 
@@ -179,6 +180,7 @@
 }
 
 void VsyncSchedule::enableHardwareVsyncLocked() {
+    ATRACE_CALL();
     if (mHwVsyncState == HwVsyncState::Disabled) {
         getTracker().resetModel();
         mRequestHardwareVsync(mId, true);
@@ -187,6 +189,7 @@
 }
 
 void VsyncSchedule::disableHardwareVsync(bool disallow) {
+    ATRACE_CALL();
     std::lock_guard<std::mutex> lock(mHwVsyncLock);
     switch (mHwVsyncState) {
         case HwVsyncState::Enabled:
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 9b417c6..2769e5d 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -318,6 +318,53 @@
     return duration > (Clock::now() - updateTime);
 }
 
+bool isFrameIntervalOnCadence(TimePoint expectedPresentTime, TimePoint lastExpectedPresentTimestamp,
+                              Fps lastFrameInterval, Period timeout, Duration threshold) {
+    if (lastFrameInterval.getPeriodNsecs() == 0) {
+        return false;
+    }
+
+    const auto expectedPresentTimeDeltaNs =
+            expectedPresentTime.ns() - lastExpectedPresentTimestamp.ns();
+
+    if (expectedPresentTimeDeltaNs > timeout.ns()) {
+        return false;
+    }
+
+    const auto expectedPresentPeriods = static_cast<nsecs_t>(
+            std::round(static_cast<float>(expectedPresentTimeDeltaNs) /
+                       static_cast<float>(lastFrameInterval.getPeriodNsecs())));
+    const auto calculatedPeriodsOutNs = lastFrameInterval.getPeriodNsecs() * expectedPresentPeriods;
+    const auto calculatedExpectedPresentTimeNs =
+            lastExpectedPresentTimestamp.ns() + calculatedPeriodsOutNs;
+    const auto presentTimeDelta =
+            std::abs(expectedPresentTime.ns() - calculatedExpectedPresentTimeNs);
+    return presentTimeDelta < threshold.ns();
+}
+
+bool isExpectedPresentWithinTimeout(TimePoint expectedPresentTime,
+                                    TimePoint lastExpectedPresentTimestamp,
+                                    std::optional<Period> timeoutOpt, Duration threshold) {
+    if (!timeoutOpt) {
+        // Always within timeout if timeoutOpt is absent and don't send hint
+        // for the timeout
+        return true;
+    }
+
+    if (timeoutOpt->ns() == 0) {
+        // Always outside timeout if timeoutOpt is 0 and always send
+        // the hint for the timeout.
+        return false;
+    }
+
+    if (expectedPresentTime.ns() < lastExpectedPresentTimestamp.ns() + timeoutOpt->ns()) {
+        return true;
+    }
+
+    // Check if within the threshold as it can be just outside the timeout
+    return std::abs(expectedPresentTime.ns() -
+                    (lastExpectedPresentTimestamp.ns() + timeoutOpt->ns())) < threshold.ns();
+}
 }  // namespace anonymous
 
 // ---------------------------------------------------------------------------
@@ -716,7 +763,7 @@
 
     sp<IBinder> input(defaultServiceManager()->waitForService(String16("inputflinger")));
 
-    static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(kMainThreadContext) {
+    static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) {
         if (input == nullptr) {
             ALOGE("Failed to link to input service");
         } else {
@@ -1245,7 +1292,7 @@
     }
 
     const char* const whence = __func__;
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(kMainThreadContext) -> status_t {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) -> status_t {
         const auto displayOpt =
                 FTL_FAKE_GUARD(mStateLock,
                                ftl::find_if(mPhysicalDisplays,
@@ -1432,7 +1479,7 @@
 
 void SurfaceFlinger::disableExpensiveRendering() {
     const char* const whence = __func__;
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) {
         ATRACE_NAME(whence);
         if (mPowerAdvisor->isUsingExpensiveRendering()) {
             for (const auto& [_, display] : mDisplays) {
@@ -1474,7 +1521,7 @@
     }
 
     const char* const whence = __func__;
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) -> status_t {
         const auto displayOpt =
                 ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken))
                         .transform(&ftl::to_mapped_ref<PhysicalDisplays>)
@@ -1555,7 +1602,7 @@
 status_t SurfaceFlinger::setBootDisplayMode(const sp<display::DisplayToken>& displayToken,
                                             DisplayModeId modeId) {
     const char* const whence = __func__;
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) -> status_t {
         const auto snapshotOpt =
                 ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken))
                         .transform(&ftl::to_mapped_ref<PhysicalDisplays>)
@@ -1583,7 +1630,7 @@
 
 status_t SurfaceFlinger::clearBootDisplayMode(const sp<IBinder>& displayToken) {
     const char* const whence = __func__;
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) -> status_t {
         if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
             return getHwComposer().clearBootDisplayMode(*displayId);
         } else {
@@ -1622,7 +1669,7 @@
         ALOGE("hdrOutputConversion is not supported by this device.");
         return INVALID_OPERATION;
     }
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) mutable -> status_t {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) mutable -> status_t {
         using AidlHdrConversionStrategy =
                 aidl::android::hardware::graphics::common::HdrConversionStrategy;
         using GuiHdrConversionStrategyTag = gui::HdrConversionStrategy::Tag;
@@ -1682,7 +1729,7 @@
 
 void SurfaceFlinger::setAutoLowLatencyMode(const sp<IBinder>& displayToken, bool on) {
     const char* const whence = __func__;
-    static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
+    static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) {
         if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
             getHwComposer().setAutoLowLatencyMode(*displayId, on);
         } else {
@@ -1693,7 +1740,7 @@
 
 void SurfaceFlinger::setGameContentType(const sp<IBinder>& displayToken, bool on) {
     const char* const whence = __func__;
-    static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
+    static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) {
         if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
             const auto type = on ? hal::ContentType::GAME : hal::ContentType::NONE;
             getHwComposer().setContentType(*displayId, type);
@@ -1747,7 +1794,7 @@
                                                           bool enable, uint8_t componentMask,
                                                           uint64_t maxFrames) {
     const char* const whence = __func__;
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) -> status_t {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) -> status_t {
         if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
             return getHwComposer().setDisplayContentSamplingEnabled(*displayId, enable,
                                                                     componentMask, maxFrames);
@@ -1800,7 +1847,7 @@
 
 status_t SurfaceFlinger::getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* outLayers) {
     outLayers->clear();
-    auto future = mScheduler->schedule([=] {
+    auto future = mScheduler->schedule([=, this] {
         const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked());
         mDrawingState.traverseInZOrder([&](Layer* layer) {
             outLayers->push_back(layer->getLayerDebugInfo(display.get()));
@@ -1906,7 +1953,7 @@
     }
 
     const char* const whence = __func__;
-    return ftl::Future(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
+    return ftl::Future(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) {
                if (const auto display = getDisplayDeviceLocked(displayToken)) {
                    const bool supportsDisplayBrightnessCommand =
                            getHwComposer().getComposer()->isSupported(
@@ -2160,19 +2207,16 @@
 void SurfaceFlinger::onRefreshRateChangedDebug(const RefreshRateChangedDebugData& data) {
     ATRACE_CALL();
     if (const auto displayId = getHwComposer().toPhysicalDisplayId(data.display); displayId) {
-        const Fps fps = Fps::fromPeriodNsecs(data.vsyncPeriodNanos);
-        ATRACE_FORMAT("%s Fps %d", __func__, fps.getIntValue());
-        static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
-            {
-                {
-                    const auto display = getDisplayDeviceLocked(*displayId);
-                    FTL_FAKE_GUARD(kMainThreadContext,
-                                   display->updateRefreshRateOverlayRate(fps,
-                                                                         display->getActiveMode()
-                                                                                 .fps,
-                                                                         /* setByHwc */ true));
-                }
-            }
+        const char* const whence = __func__;
+        static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) {
+            const Fps fps = Fps::fromPeriodNsecs(getHwComposer().getComposer()->isVrrSupported()
+                                                         ? data.refreshPeriodNanos
+                                                         : data.vsyncPeriodNanos);
+            ATRACE_FORMAT("%s Fps %d", whence, fps.getIntValue());
+            const auto display = getDisplayDeviceLocked(*displayId);
+            FTL_FAKE_GUARD(kMainThreadContext,
+                           display->updateRefreshRateOverlayRate(fps, display->getActiveMode().fps,
+                                                                 /* setByHwc */ true));
         }));
     }
 }
@@ -2710,7 +2754,18 @@
     refreshArgs.scheduledFrameTime = mScheduler->getScheduledFrameTime();
     refreshArgs.expectedPresentTime = expectedPresentTime.ns();
     refreshArgs.hasTrustedPresentationListener = mNumTrustedPresentationListeners > 0;
-
+    {
+        auto& notifyExpectedPresentData = mNotifyExpectedPresentMap[pacesetterId];
+        auto lastExpectedPresentTimestamp = TimePoint::fromNs(
+                notifyExpectedPresentData.lastExpectedPresentTimestamp.load().ns());
+        if (expectedPresentTime > lastExpectedPresentTimestamp) {
+            // If the values are not same, then hint is sent with newer value.
+            // And because composition always follows the notifyExpectedPresentIfRequired, we can
+            // skip updating the lastExpectedPresentTimestamp in this case.
+            notifyExpectedPresentData.lastExpectedPresentTimestamp
+                    .compare_exchange_weak(lastExpectedPresentTimestamp, expectedPresentTime);
+        }
+    }
     // Store the present time just before calling to the composition engine so we could notify
     // the scheduler.
     const auto presentTime = systemTime();
@@ -4064,7 +4119,7 @@
 void SurfaceFlinger::onVsyncGenerated(TimePoint expectedPresentTime,
                                       ftl::NonNull<DisplayModePtr> modePtr, Fps renderRate) {
     const auto vsyncPeriod = modePtr->getVsyncRate().getPeriod();
-    const auto timeout = [&]() -> std::optional<Period> {
+    const auto timeoutOpt = [&]() -> std::optional<Period> {
         const auto vrrConfig = modePtr->getVrrConfig();
         if (!vrrConfig) return std::nullopt;
 
@@ -4074,14 +4129,54 @@
         return Period::fromNs(notifyExpectedPresentConfig->notifyExpectedPresentTimeoutNs);
     }();
 
-    const auto displayId = modePtr->getPhysicalDisplayId();
-    const auto status = getHwComposer().notifyExpectedPresentIfRequired(displayId, vsyncPeriod,
-                                                                        expectedPresentTime,
-                                                                        renderRate, timeout);
-    if (status != NO_ERROR) {
-        ALOGE("%s failed to notifyExpectedPresentHint for display %" PRId64, __func__,
-              displayId.value);
+    notifyExpectedPresentIfRequired(modePtr->getPhysicalDisplayId(), vsyncPeriod,
+                                    expectedPresentTime, renderRate, timeoutOpt);
+}
+
+void SurfaceFlinger::notifyExpectedPresentIfRequired(PhysicalDisplayId displayId,
+                                                     Period vsyncPeriod,
+                                                     TimePoint expectedPresentTime,
+                                                     Fps frameInterval,
+                                                     std::optional<Period> timeoutOpt) {
+    {
+        auto& data = mNotifyExpectedPresentMap[displayId];
+        const auto lastExpectedPresentTimestamp = data.lastExpectedPresentTimestamp.load();
+        const auto lastFrameInterval = data.lastFrameInterval;
+        data.lastFrameInterval = frameInterval;
+        const auto threshold = Duration::fromNs(vsyncPeriod.ns() / 2);
+
+        const constexpr nsecs_t kOneSecondNs =
+                std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count();
+        const auto timeout = Period::fromNs(timeoutOpt && timeoutOpt->ns() > 0 ? timeoutOpt->ns()
+                                                                               : kOneSecondNs);
+        const bool frameIntervalIsOnCadence =
+                isFrameIntervalOnCadence(expectedPresentTime, lastExpectedPresentTimestamp,
+                                         lastFrameInterval, timeout, threshold);
+
+        const bool expectedPresentWithinTimeout =
+                isExpectedPresentWithinTimeout(expectedPresentTime, lastExpectedPresentTimestamp,
+                                               timeoutOpt, threshold);
+
+        using fps_approx_ops::operator!=;
+        if (frameIntervalIsOnCadence && frameInterval != lastFrameInterval) {
+            data.lastExpectedPresentTimestamp = expectedPresentTime;
+        }
+
+        if (expectedPresentWithinTimeout && frameIntervalIsOnCadence) {
+            return;
+        }
+        data.lastExpectedPresentTimestamp = expectedPresentTime;
     }
+
+    const char* const whence = __func__;
+    static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) {
+        const auto status = getHwComposer().notifyExpectedPresent(displayId, expectedPresentTime,
+                                                                  frameInterval);
+        if (status != NO_ERROR) {
+            ALOGE("%s failed to notifyExpectedPresentHint for display %" PRId64, whence,
+                  displayId.value);
+        }
+    }));
 }
 
 void SurfaceFlinger::initScheduler(const sp<const DisplayDevice>& display) {
@@ -4343,7 +4438,7 @@
     if (mNumLayers >= MAX_LAYERS) {
         ALOGE("AddClientLayer failed, mNumLayers (%zu) >= MAX_LAYERS (%zu)", mNumLayers.load(),
               MAX_LAYERS);
-        static_cast<void>(mScheduler->schedule([=] {
+        static_cast<void>(mScheduler->schedule([=, this] {
             ALOGE("Dumping layer keeping > 20 children alive:");
             bool leakingParentLayerFound = false;
             mDrawingState.traverse([&](Layer* layer) {
@@ -5927,7 +6022,7 @@
 }
 
 void SurfaceFlinger::setPowerMode(const sp<IBinder>& displayToken, int mode) {
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(
                                                kMainThreadContext) {
         const auto display = getDisplayDeviceLocked(displayToken);
         if (!display) {
@@ -6376,7 +6471,7 @@
 }
 
 perfetto::protos::LayersProto SurfaceFlinger::dumpProtoFromMainThread(uint32_t traceFlags) {
-    return mScheduler->schedule([=] { return dumpDrawingStateProto(traceFlags); }).get();
+    return mScheduler->schedule([=, this] { return dumpDrawingStateProto(traceFlags); }).get();
 }
 
 void SurfaceFlinger::dumpOffscreenLayers(std::string& result) {
@@ -7297,7 +7392,7 @@
 
     // Update the overlay on the main thread to avoid race conditions with
     // RefreshRateSelector::getActiveMode
-    static_cast<void>(mScheduler->schedule([=] {
+    static_cast<void>(mScheduler->schedule([=, this] {
         const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked());
         if (!display) {
             ALOGW("%s: default display is null", __func__);
@@ -7738,7 +7833,7 @@
     }
 
     bool childrenOnly = args.childrenOnly;
-    RenderAreaFuture renderAreaFuture = ftl::defer([=]() -> std::unique_ptr<RenderArea> {
+    RenderAreaFuture renderAreaFuture = ftl::defer([=, this]() -> std::unique_ptr<RenderArea> {
         ui::Transform layerTransform;
         Rect layerBufferSize;
         if (mLayerLifecycleManagerEnabled) {
@@ -7877,7 +7972,7 @@
     ATRACE_CALL();
 
     auto future = mScheduler->schedule(
-            [=, renderAreaFuture = std::move(renderAreaFuture)]() FTL_FAKE_GUARD(
+            [=, this, renderAreaFuture = std::move(renderAreaFuture)]() FTL_FAKE_GUARD(
                     kMainThreadContext) mutable -> ftl::SharedFuture<FenceResult> {
                 ScreenCaptureResults captureResults;
                 std::shared_ptr<RenderArea> renderArea = renderAreaFuture.get();
@@ -8302,7 +8397,7 @@
         return BAD_VALUE;
     }
 
-    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(kMainThreadContext) -> status_t {
+    auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) -> status_t {
         const auto display = FTL_FAKE_GUARD(mStateLock, getDisplayDeviceLocked(displayToken));
         if (!display) {
             ALOGE("Attempt to set desired display modes for invalid display token %p",
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 6a7b322..6b44401 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -1490,6 +1490,19 @@
     // Map of displayid to mirrorRoot
     ftl::SmallMap<int64_t, sp<SurfaceControl>, 3> mMirrorMapForDebug;
 
+    // NotifyExpectedPresentHint
+    struct NotifyExpectedPresentData {
+        // lastExpectedPresentTimestamp is read and write from multiple threads such as
+        // main thread, EventThread, MessageQueue. And is atomic for that reason.
+        std::atomic<TimePoint> lastExpectedPresentTimestamp{};
+        Fps lastFrameInterval{};
+    };
+    std::unordered_map<PhysicalDisplayId, NotifyExpectedPresentData> mNotifyExpectedPresentMap;
+
+    void notifyExpectedPresentIfRequired(PhysicalDisplayId, Period vsyncPeriod,
+                                         TimePoint expectedPresentTime, Fps frameInterval,
+                                         std::optional<Period> timeoutOpt);
+
     void sfdo_enableRefreshRateOverlay(bool active);
     void sfdo_setDebugFlash(int delay);
     void sfdo_scheduleComposite();
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index 9b77751..cb1faee 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -127,7 +127,7 @@
     DUMP_READ_ONLY_FLAG(display_protected);
     DUMP_READ_ONLY_FLAG(fp16_client_target);
     DUMP_READ_ONLY_FLAG(game_default_frame_rate);
-
+    DUMP_READ_ONLY_FLAG(enable_layer_command_batching);
 #undef DUMP_READ_ONLY_FLAG
 #undef DUMP_SERVER_FLAG
 #undef DUMP_FLAG_INTERVAL
@@ -201,6 +201,7 @@
 FLAG_MANAGER_READ_ONLY_FLAG(display_protected, "")
 FLAG_MANAGER_READ_ONLY_FLAG(fp16_client_target, "debug.sf.fp16_client_target")
 FLAG_MANAGER_READ_ONLY_FLAG(game_default_frame_rate, "")
+FLAG_MANAGER_READ_ONLY_FLAG(enable_layer_command_batching, "")
 
 /// Trunk stable server flags ///
 FLAG_MANAGER_SERVER_FLAG(late_boot_misc2, "")
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index 3a509f6..2e1d6ae 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -66,6 +66,7 @@
     bool display_protected() const;
     bool fp16_client_target() const;
     bool game_default_frame_rate() const;
+    bool enable_layer_command_batching() const;
 
 protected:
     // overridden for unit tests
diff --git a/services/surfaceflinger/surfaceflinger_flags.aconfig b/services/surfaceflinger/surfaceflinger_flags.aconfig
index 3f26448..1a28b81 100644
--- a/services/surfaceflinger/surfaceflinger_flags.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags.aconfig
@@ -32,6 +32,14 @@
 }
 
 flag {
+  name: "enable_layer_command_batching"
+  namespace: "core_graphics"
+  description: "This flag controls batching on createLayer/destroyLayer command with executeCommand."
+  bug: "290685621"
+  is_fixed_read_only: true
+}
+
+flag {
   name: "dont_skip_on_early"
   namespace: "core_graphics"
   description: "This flag is guarding the behaviour where SurfaceFlinger is trying to opportunistically present a frame when the configuration change from late to early"
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index dea0194..c75f902 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -106,6 +106,7 @@
         "SurfaceFlinger_HdrOutputControlTest.cpp",
         "SurfaceFlinger_HotplugTest.cpp",
         "SurfaceFlinger_InitializeDisplaysTest.cpp",
+        "SurfaceFlinger_NotifyExpectedPresentTest.cpp",
         "SurfaceFlinger_NotifyPowerBoostTest.cpp",
         "SurfaceFlinger_PowerHintTest.cpp",
         "SurfaceFlinger_SetDisplayStateTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index d3ce4f2..6edecff 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -82,15 +82,6 @@
         EXPECT_CALL(*mHal, setVsyncEnabled(hwcDisplayId, Hwc2::IComposerClient::Vsync::DISABLE));
         EXPECT_CALL(*mHal, onHotplugConnect(hwcDisplayId));
     }
-
-    void setDisplayData(HalDisplayId displayId, TimePoint lastExpectedPresentTimestamp,
-                        Fps lastFrameInterval) {
-        ASSERT_TRUE(mHwc.mDisplayData.find(displayId) != mHwc.mDisplayData.end());
-        auto& displayData = mHwc.mDisplayData.at(displayId);
-        std::scoped_lock lock{displayData.expectedPresentLock};
-        displayData.lastExpectedPresentTimestamp = lastExpectedPresentTimestamp;
-        displayData.lastFrameInterval = lastFrameInterval;
-    }
 };
 
 TEST_F(HWComposerTest, isHeadless) {
@@ -143,7 +134,7 @@
     const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
     ASSERT_TRUE(info);
 
-    EXPECT_CALL(*mHal, getDisplayConfigurationsSupported()).WillRepeatedly(Return(false));
+    EXPECT_CALL(*mHal, isVrrSupported()).WillRepeatedly(Return(false));
 
     {
         EXPECT_CALL(*mHal, getDisplayConfigs(kHwcDisplayId, _))
@@ -235,7 +226,7 @@
     const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
     ASSERT_TRUE(info);
 
-    EXPECT_CALL(*mHal, getDisplayConfigurationsSupported()).WillRepeatedly(Return(false));
+    EXPECT_CALL(*mHal, isVrrSupported()).WillRepeatedly(Return(false));
 
     {
         EXPECT_CALL(*mHal, getDisplayConfigs(kHwcDisplayId, _))
@@ -324,7 +315,7 @@
     const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
     ASSERT_TRUE(info);
 
-    EXPECT_CALL(*mHal, getDisplayConfigurationsSupported()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*mHal, isVrrSupported()).WillRepeatedly(Return(true));
 
     {
         EXPECT_CALL(*mHal, getDisplayConfigurations(kHwcDisplayId, _, _))
@@ -417,144 +408,6 @@
     EXPECT_FALSE(displayIdOpt);
 }
 
-TEST_F(HWComposerTest, notifyExpectedPresentTimeout) {
-    constexpr hal::HWDisplayId kHwcDisplayId = 2;
-    expectHotplugConnect(kHwcDisplayId);
-    const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
-    ASSERT_TRUE(info);
-
-    auto expectedPresentTime = systemTime() + ms2ns(10);
-    static constexpr Fps Fps60Hz = 60_Hz;
-    static constexpr int32_t kFrameInterval5HzNs = static_cast<Fps>(5_Hz).getPeriodNsecs();
-    static constexpr int32_t kFrameInterval60HzNs = Fps60Hz.getPeriodNsecs();
-    static constexpr int32_t kFrameInterval120HzNs = static_cast<Fps>(120_Hz).getPeriodNsecs();
-    static constexpr Period kVsyncPeriod =
-            Period::fromNs(static_cast<Fps>(240_Hz).getPeriodNsecs());
-    static constexpr Period kTimeoutNs = Period::fromNs(kFrameInterval5HzNs);
-    static constexpr auto kLastExpectedPresentTimestamp = TimePoint::fromNs(0);
-
-    ASSERT_NO_FATAL_FAILURE(setDisplayData(info->id, kLastExpectedPresentTimestamp, Fps60Hz));
-
-    {
-        // Very first ExpectedPresent after idle, no previous timestamp
-        EXPECT_CALL(*mHal,
-                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
-                .WillOnce(Return(HalError::NONE));
-        mHwc.notifyExpectedPresentIfRequired(info->id, kVsyncPeriod,
-                                             TimePoint::fromNs(expectedPresentTime), Fps60Hz,
-                                             kTimeoutNs);
-    }
-    {
-        // Absent timeoutNs
-        expectedPresentTime += 2 * kFrameInterval5HzNs;
-        EXPECT_CALL(*mHal, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
-        mHwc.notifyExpectedPresentIfRequired(info->id, kVsyncPeriod,
-                                             TimePoint::fromNs(expectedPresentTime), Fps60Hz,
-                                             /*timeoutOpt*/ std::nullopt);
-    }
-    {
-        // Timeout is 0
-        expectedPresentTime += kFrameInterval60HzNs;
-        EXPECT_CALL(*mHal,
-                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
-                .WillOnce(Return(HalError::NONE));
-        mHwc.notifyExpectedPresentIfRequired(info->id, kVsyncPeriod,
-                                             TimePoint::fromNs(expectedPresentTime), Fps60Hz,
-                                             Period::fromNs(0));
-    }
-    {
-        // ExpectedPresent is after the timeoutNs
-        expectedPresentTime += 2 * kFrameInterval5HzNs;
-        EXPECT_CALL(*mHal,
-                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
-                .WillOnce(Return(HalError::NONE));
-        mHwc.notifyExpectedPresentIfRequired(info->id, kVsyncPeriod,
-                                             TimePoint::fromNs(expectedPresentTime), Fps60Hz,
-                                             kTimeoutNs);
-    }
-    {
-        // ExpectedPresent has not changed
-        EXPECT_CALL(*mHal, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
-        mHwc.notifyExpectedPresentIfRequired(info->id, kVsyncPeriod,
-                                             TimePoint::fromNs(expectedPresentTime), Fps60Hz,
-                                             kTimeoutNs);
-    }
-    {
-        // ExpectedPresent is after the last reported ExpectedPresent.
-        expectedPresentTime += kFrameInterval60HzNs;
-        EXPECT_CALL(*mHal, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
-        mHwc.notifyExpectedPresentIfRequired(info->id, kVsyncPeriod,
-                                             TimePoint::fromNs(expectedPresentTime), Fps60Hz,
-                                             kTimeoutNs);
-    }
-    {
-        // ExpectedPresent is before the last reported ExpectedPresent but after the timeoutNs,
-        // representing we changed our decision and want to present earlier than previously
-        // reported.
-        expectedPresentTime -= kFrameInterval120HzNs;
-        EXPECT_CALL(*mHal,
-                    notifyExpectedPresent(kHwcDisplayId, expectedPresentTime, kFrameInterval60HzNs))
-                .WillOnce(Return(HalError::NONE));
-        mHwc.notifyExpectedPresentIfRequired(info->id, kVsyncPeriod,
-                                             TimePoint::fromNs(expectedPresentTime), Fps60Hz,
-                                             kTimeoutNs);
-    }
-}
-
-TEST_F(HWComposerTest, notifyExpectedPresentRenderRateChanged) {
-    constexpr hal::HWDisplayId kHwcDisplayId = 2;
-    expectHotplugConnect(kHwcDisplayId);
-    const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
-    ASSERT_TRUE(info);
-
-    const auto now = systemTime();
-    auto expectedPresentTime = now;
-    static constexpr Period kTimeoutNs = Period::fromNs(static_cast<Fps>(1_Hz).getPeriodNsecs());
-
-    ASSERT_NO_FATAL_FAILURE(setDisplayData(info->id, TimePoint::fromNs(now), Fps::fromValue(0)));
-    static constexpr int32_t kFrameIntervalNs120Hz = static_cast<Fps>(120_Hz).getPeriodNsecs();
-    static constexpr int32_t kFrameIntervalNs96Hz = static_cast<Fps>(96_Hz).getPeriodNsecs();
-    static constexpr int32_t kFrameIntervalNs80Hz = static_cast<Fps>(80_Hz).getPeriodNsecs();
-    static constexpr int32_t kFrameIntervalNs60Hz = static_cast<Fps>(60_Hz).getPeriodNsecs();
-    static constexpr int32_t kFrameIntervalNs40Hz = static_cast<Fps>(40_Hz).getPeriodNsecs();
-    static constexpr int32_t kFrameIntervalNs30Hz = static_cast<Fps>(30_Hz).getPeriodNsecs();
-    static constexpr int32_t kFrameIntervalNs24Hz = static_cast<Fps>(24_Hz).getPeriodNsecs();
-    static constexpr int32_t kFrameIntervalNs20Hz = static_cast<Fps>(20_Hz).getPeriodNsecs();
-    static constexpr Period kVsyncPeriod =
-            Period::fromNs(static_cast<Fps>(240_Hz).getPeriodNsecs());
-
-    struct FrameRateIntervalTestData {
-        int32_t frameIntervalNs;
-        bool callExpectedPresent;
-    };
-    const std::vector<FrameRateIntervalTestData> frameIntervals = {
-            {kFrameIntervalNs60Hz, true},  {kFrameIntervalNs96Hz, true},
-            {kFrameIntervalNs80Hz, true},  {kFrameIntervalNs120Hz, true},
-            {kFrameIntervalNs80Hz, true},  {kFrameIntervalNs60Hz, true},
-            {kFrameIntervalNs60Hz, false}, {kFrameIntervalNs30Hz, false},
-            {kFrameIntervalNs24Hz, true},  {kFrameIntervalNs40Hz, true},
-            {kFrameIntervalNs20Hz, false}, {kFrameIntervalNs60Hz, true},
-            {kFrameIntervalNs20Hz, false}, {kFrameIntervalNs120Hz, true},
-    };
-
-    for (const auto& [frameIntervalNs, callExpectedPresent] : frameIntervals) {
-        {
-            expectedPresentTime += frameIntervalNs;
-            if (callExpectedPresent) {
-                EXPECT_CALL(*mHal,
-                            notifyExpectedPresent(kHwcDisplayId, expectedPresentTime,
-                                                  frameIntervalNs))
-                        .WillOnce(Return(HalError::NONE));
-            } else {
-                EXPECT_CALL(*mHal, notifyExpectedPresent(kHwcDisplayId, _, _)).Times(0);
-            }
-            mHwc.notifyExpectedPresentIfRequired(info->id, kVsyncPeriod,
-                                                 TimePoint::fromNs(expectedPresentTime),
-                                                 Fps::fromPeriodNsecs(frameIntervalNs), kTimeoutNs);
-        }
-    }
-}
-
 struct MockHWC2ComposerCallback final : StrictMock<HWC2::ComposerCallback> {
     MOCK_METHOD(void, onComposerHalHotplugEvent, (hal::HWDisplayId, DisplayHotplugEvent),
                 (override));
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp
new file mode 100644
index 0000000..7206e29
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include "DisplayTransactionTestHelpers.h"
+
+namespace android {
+
+using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+
+class NotifyExpectedPresentTest : public DisplayTransactionTest {
+public:
+    void SetUp() override {
+        mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this).inject();
+        FakeHwcDisplayInjector(mDisplay->getPhysicalId(), hal::DisplayType::PHYSICAL, kIsPrimary)
+                .setPowerMode(hal::PowerMode::ON)
+                .inject(&mFlinger, mComposer);
+    }
+
+protected:
+    sp<DisplayDevice> mDisplay;
+    static constexpr bool kIsPrimary = true;
+    static constexpr hal::HWDisplayId HWC_DISPLAY_ID =
+            FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID;
+};
+
+TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentTimeout) {
+    const auto physicDisplayId = mDisplay->getPhysicalId();
+    auto expectedPresentTime = systemTime() + ms2ns(10);
+    static constexpr Fps kFps60Hz = 60_Hz;
+    static constexpr int32_t kFrameInterval5HzNs = static_cast<Fps>(5_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameInterval60HzNs = kFps60Hz.getPeriodNsecs();
+    static constexpr int32_t kFrameInterval120HzNs = static_cast<Fps>(120_Hz).getPeriodNsecs();
+    static constexpr Period kVsyncPeriod =
+            Period::fromNs(static_cast<Fps>(240_Hz).getPeriodNsecs());
+    static constexpr Period kTimeoutNs = Period::fromNs(kFrameInterval5HzNs);
+    static constexpr auto kLastExpectedPresentTimestamp = TimePoint::fromNs(0);
+
+    ASSERT_NO_FATAL_FAILURE(mFlinger.setNotifyExpectedPresentData(physicDisplayId,
+                                                                  kLastExpectedPresentTimestamp,
+                                                                  kFps60Hz));
+
+    {
+        // Very first ExpectedPresent after idle, no previous timestamp
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
+                                          kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+    }
+    {
+        // Absent timeoutNs
+        expectedPresentTime += 2 * kFrameInterval5HzNs;
+        EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
+        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 /*timeoutOpt*/ std::nullopt);
+    }
+    {
+        // Timeout is 0
+        expectedPresentTime += kFrameInterval60HzNs;
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
+                                          kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 Period::fromNs(0));
+    }
+    {
+        // ExpectedPresent is after the timeoutNs
+        expectedPresentTime += 2 * kFrameInterval5HzNs;
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
+                                          kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+    }
+    {
+        // ExpectedPresent has not changed
+        EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
+        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+    }
+    {
+        // ExpectedPresent is after the last reported ExpectedPresent.
+        expectedPresentTime += kFrameInterval60HzNs;
+        EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
+        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+    }
+    {
+        // ExpectedPresent is before the last reported ExpectedPresent but after the timeoutNs,
+        // representing we changed our decision and want to present earlier than previously
+        // reported.
+        expectedPresentTime -= kFrameInterval120HzNs;
+        EXPECT_CALL(*mComposer,
+                    notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
+                                          kFrameInterval60HzNs))
+                .WillOnce(Return(Error::NONE));
+        mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                 TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+                                                 kTimeoutNs);
+    }
+}
+
+TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentRenderRateChanged) {
+    const auto physicDisplayId = mDisplay->getPhysicalId();
+    const auto now = systemTime();
+    auto expectedPresentTime = now;
+    static constexpr Period kTimeoutNs = Period::fromNs(static_cast<Fps>(1_Hz).getPeriodNsecs());
+
+    ASSERT_NO_FATAL_FAILURE(mFlinger.setNotifyExpectedPresentData(physicDisplayId,
+                                                                  TimePoint::fromNs(now),
+                                                                  Fps::fromValue(0)));
+    static constexpr int32_t kFrameIntervalNs120Hz = static_cast<Fps>(120_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameIntervalNs96Hz = static_cast<Fps>(96_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameIntervalNs80Hz = static_cast<Fps>(80_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameIntervalNs60Hz = static_cast<Fps>(60_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameIntervalNs40Hz = static_cast<Fps>(40_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameIntervalNs30Hz = static_cast<Fps>(30_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameIntervalNs24Hz = static_cast<Fps>(24_Hz).getPeriodNsecs();
+    static constexpr int32_t kFrameIntervalNs20Hz = static_cast<Fps>(20_Hz).getPeriodNsecs();
+    static constexpr Period kVsyncPeriod =
+            Period::fromNs(static_cast<Fps>(240_Hz).getPeriodNsecs());
+
+    struct FrameRateIntervalTestData {
+        int32_t frameIntervalNs;
+        bool callExpectedPresent;
+    };
+    const std::vector<FrameRateIntervalTestData> frameIntervals = {
+            {kFrameIntervalNs60Hz, true},  {kFrameIntervalNs96Hz, true},
+            {kFrameIntervalNs80Hz, true},  {kFrameIntervalNs120Hz, true},
+            {kFrameIntervalNs80Hz, true},  {kFrameIntervalNs60Hz, true},
+            {kFrameIntervalNs60Hz, false}, {kFrameIntervalNs30Hz, false},
+            {kFrameIntervalNs24Hz, true},  {kFrameIntervalNs40Hz, true},
+            {kFrameIntervalNs20Hz, false}, {kFrameIntervalNs60Hz, true},
+            {kFrameIntervalNs20Hz, false}, {kFrameIntervalNs120Hz, true},
+    };
+
+    for (const auto& [frameIntervalNs, callExpectedPresent] : frameIntervals) {
+        {
+            expectedPresentTime += frameIntervalNs;
+            if (callExpectedPresent) {
+                EXPECT_CALL(*mComposer,
+                            notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
+                                                  frameIntervalNs))
+                        .WillOnce(Return(Error::NONE));
+            } else {
+                EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
+            }
+            mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+                                                     TimePoint::fromNs(expectedPresentTime),
+                                                     Fps::fromPeriodNsecs(frameIntervalNs),
+                                                     kTimeoutNs);
+        }
+    }
+}
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 8ba6bf8..22cb24b 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -695,6 +695,21 @@
         mFlinger->mLegacyFrontEndEnabled = false;
     }
 
+    void notifyExpectedPresentIfRequired(PhysicalDisplayId displayId, Period vsyncPeriod,
+                                         TimePoint expectedPresentTime, Fps frameInterval,
+                                         std::optional<Period> timeoutOpt) {
+        mFlinger->notifyExpectedPresentIfRequired(displayId, vsyncPeriod, expectedPresentTime,
+                                                  frameInterval, timeoutOpt);
+    }
+
+    void setNotifyExpectedPresentData(PhysicalDisplayId displayId,
+                                      TimePoint lastExpectedPresentTimestamp,
+                                      Fps lastFrameInterval) {
+        auto& displayData = mFlinger->mNotifyExpectedPresentMap[displayId];
+        displayData.lastExpectedPresentTimestamp = lastExpectedPresentTimestamp;
+        displayData.lastFrameInterval = lastFrameInterval;
+    }
+
     ~TestableSurfaceFlinger() {
         // All these pointer and container clears help ensure that GMock does
         // not report a leaked object, since the SurfaceFlinger instance may
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index d649679..184dada 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -51,7 +51,7 @@
     ~Composer() override;
 
     MOCK_METHOD(bool, isSupported, (OptionalFeature), (const, override));
-    MOCK_METHOD(bool, getDisplayConfigurationsSupported, (), (const, override));
+    MOCK_METHOD(bool, isVrrSupported, (), (const, override));
     MOCK_METHOD0(getCapabilities,
                  std::vector<aidl::android::hardware::graphics::composer3::Capability>());
     MOCK_METHOD0(dumpDebugInfo, std::string());
diff --git a/vulkan/include/vulkan/vk_android_native_buffer.h b/vulkan/include/vulkan/vk_android_native_buffer.h
index 7c8e695..159b2d5 100644
--- a/vulkan/include/vulkan/vk_android_native_buffer.h
+++ b/vulkan/include/vulkan/vk_android_native_buffer.h
@@ -63,7 +63,8 @@
 /*
  * NOTE ON VK_ANDROID_NATIVE_BUFFER_SPEC_VERSION 11
  *
- * This version of the extension deprecates the last of grallocusage
+ * This version of the extension deprecates the last of grallocusage and
+ * extends VkNativeBufferANDROID to support passing AHardwareBuffer*
  */
 #define VK_ANDROID_NATIVE_BUFFER_SPEC_VERSION 11
 #define VK_ANDROID_NATIVE_BUFFER_EXTENSION_NAME "VK_ANDROID_native_buffer"
@@ -111,6 +112,9 @@
  * usage: gralloc usage requested when the buffer was allocated
  * usage2: gralloc usage requested when the buffer was allocated
  * usage3: gralloc usage requested when the buffer was allocated
+ * ahb: The AHardwareBuffer* from the actual ANativeWindowBuffer. Caller
+ *      maintains ownership of resource. AHardwareBuffer pointer is only valid
+ *      for the duration of the function call
  */
 typedef struct {
     VkStructureType                   sType;
@@ -121,6 +125,7 @@
     int                               usage; /* DEPRECATED in SPEC_VERSION 6 */
     VkNativeBufferUsage2ANDROID       usage2; /* DEPRECATED in SPEC_VERSION 9 */
     uint64_t                          usage3; /* ADDED in SPEC_VERSION 9 */
+    struct AHardwareBuffer*           ahb; /* ADDED in SPEC_VERSION 11 */
 } VkNativeBufferANDROID;
 
 /*
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 4f7b0f4..0df5e77 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -1970,6 +1970,8 @@
                 &image_native_buffer.usage2.producer,
                 &image_native_buffer.usage2.consumer);
             image_native_buffer.usage3 = img.buffer->usage;
+            image_native_buffer.ahb =
+                ANativeWindowBuffer_getHardwareBuffer(img.buffer.get());
             image_create.pNext = &image_native_buffer;
 
             ATRACE_BEGIN("CreateImage");
@@ -2146,7 +2148,12 @@
                     .stride = buffer->stride,
                     .format = buffer->format,
                     .usage = int(buffer->usage),
+                    .usage3 = buffer->usage,
+                    .ahb = ANativeWindowBuffer_getHardwareBuffer(buffer),
                 };
+                android_convertGralloc0To1Usage(int(buffer->usage),
+                                                &nb.usage2.producer,
+                                                &nb.usage2.consumer);
                 VkBindImageMemoryInfo bimi = {
                     .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO,
                     .pNext = &nb,
@@ -2692,7 +2699,12 @@
             .stride = buffer->stride,
             .format = buffer->format,
             .usage = int(buffer->usage),
+            .usage3 = buffer->usage,
+            .ahb = ANativeWindowBuffer_getHardwareBuffer(buffer),
         };
+        android_convertGralloc0To1Usage(int(buffer->usage),
+                                        &native_buffer.usage2.producer,
+                                        &native_buffer.usage2.consumer);
         // Reserve enough space to avoid letting re-allocation invalidate the
         // addresses of the elements inside.
         out_native_buffers->reserve(bind_info_count);
diff --git a/vulkan/nulldrv/Android.bp b/vulkan/nulldrv/Android.bp
index a6d540b..5112e14 100644
--- a/vulkan/nulldrv/Android.bp
+++ b/vulkan/nulldrv/Android.bp
@@ -46,5 +46,8 @@
         "hwvulkan_headers",
         "vulkan_headers",
     ],
-    shared_libs: ["liblog"],
+    shared_libs: [
+        "liblog",
+        "libnativewindow",
+    ],
 }
diff --git a/vulkan/nulldrv/null_driver.cpp b/vulkan/nulldrv/null_driver.cpp
index 2e87f17..973e71c 100644
--- a/vulkan/nulldrv/null_driver.cpp
+++ b/vulkan/nulldrv/null_driver.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <android/hardware_buffer.h>
 #include <hardware/hwvulkan.h>
 
 #include <errno.h>
diff --git a/vulkan/nulldrv/null_driver_gen.cpp b/vulkan/nulldrv/null_driver_gen.cpp
index 935535f..d34851e 100644
--- a/vulkan/nulldrv/null_driver_gen.cpp
+++ b/vulkan/nulldrv/null_driver_gen.cpp
@@ -16,6 +16,8 @@
 
 // WARNING: This file is generated. See ../README.md for instructions.
 
+#include <android/hardware_buffer.h>
+
 #include <algorithm>
 
 #include "null_driver_gen.h"