Merge "Fix assert error on vibrator benchmarks"
diff --git a/data/etc/cec_config.xml b/data/etc/cec_config.xml
index 42a45e4..8e78ad7 100644
--- a/data/etc/cec_config.xml
+++ b/data/etc/cec_config.xml
@@ -1,14 +1,16 @@
 <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
 <cec-settings>
   <setting name="hdmi_cec_enabled"
+           value-type="int"
            user-configurable="true">
     <allowed-values>
-      <value string-value="0" />
-      <value string-value="1" />
+      <value int-value="0" />
+      <value int-value="1" />
     </allowed-values>
-    <default-value string-value="1" />
+    <default-value int-value="1" />
   </setting>
   <setting name="send_standby_on_sleep"
+           value-type="string"
            user-configurable="true">
     <allowed-values>
       <value string-value="to_tv" />
@@ -18,6 +20,7 @@
     <default-value string-value="to_tv" />
   </setting>
   <setting name="power_state_change_on_active_source_lost"
+           value-type="string"
            user-configurable="false">
     <allowed-values>
       <value string-value="none" />
@@ -26,11 +29,12 @@
     <default-value string-value="none" />
   </setting>
   <setting name="system_audio_mode_muting"
+           value-type="int"
            user-configurable="false">
     <allowed-values>
-      <value string-value="0" />
-      <value string-value="1" />
+      <value int-value="0" />
+      <value int-value="1" />
     </allowed-values>
-    <default-value string-value="1" />
+    <default-value int-value="1" />
   </setting>
 </cec-settings>
diff --git a/include/android/permission_manager.h b/include/android/permission_manager.h
new file mode 100644
index 0000000..7817126
--- /dev/null
+++ b/include/android/permission_manager.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#ifndef ANDROID_PERMISSION_MANAGER_H
+#define ANDROID_PERMISSION_MANAGER_H
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+/**
+ * Permission check results.
+ *
+ * Introduced in API 31.
+ */
+enum {
+    /**
+     * This is returned by APermissionManager_checkPermission()
+     * if the permission has been granted to the given package.
+     */
+    PERMISSION_MANAGER_PERMISSION_GRANTED = 0,
+    /**
+     * This is returned by APermissionManager_checkPermission()
+     * if the permission has not been granted to the given package.
+     */
+    PERMISSION_MANAGER_PERMISSION_DENIED = -1,
+};
+
+/**
+ * Permission check return status values.
+ *
+ * Introduced in API 31.
+ */
+enum {
+    /**
+     * This is returned if the permission check completed without errors.
+     * The output result is valid and contains one of {PERMISSION_MANAGER_PERMISSION_GRANTED,
+     * PERMISSION_MANAGER_PERMISSION_DENIED}.
+     */
+    PERMISSION_MANAGER_STATUS_OK = 0,
+    /**
+     * This is returned if the permission check encountered an unspecified error.
+     * The output result is unmodified.
+     */
+    PERMISSION_MANAGER_STATUS_ERROR_UNKNOWN = -1,
+    /**
+     * This is returned if the permission check failed because the service is
+     * unavailable. The output result is unmodified.
+     */
+    PERMISSION_MANAGER_STATUS_SERVICE_UNAVAILABLE = -2,
+};
+
+#if __ANDROID_API__ >= 31
+
+/**
+ * Checks whether the package with the given pid/uid has been granted a permission.
+ *
+ * Note that the Java API of Context#checkPermission() is usually faster due to caching,
+ * thus is preferred over this API wherever possible.
+ *
+ * @param permission the permission to be checked.
+ * @param pid the process id of the package to be checked.
+ * @param uid the uid of the package to be checked.
+ * @param outResult output of the permission check result.
+ *
+ * @return error codes if any error happened during the check.
+ */
+int32_t APermissionManager_checkPermission(const char* permission,
+                                           pid_t pid,
+                                           uid_t uid,
+                                           int32_t* outResult) __INTRODUCED_IN(31);
+
+#endif // __ANDROID_API__ >= 31
+
+__END_DECLS
+
+#endif  // ANDROID_PERMISSION_MANAGER_H
diff --git a/include/ftl/ArrayTraits.h b/include/ftl/ArrayTraits.h
new file mode 100644
index 0000000..28f717a
--- /dev/null
+++ b/include/ftl/ArrayTraits.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2020 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 <algorithm>
+#include <iterator>
+#include <new>
+
+#define FTL_ARRAY_TRAIT(T, U) using U = typename ArrayTraits<T>::U
+
+namespace android::ftl {
+
+template <typename T>
+struct ArrayTraits {
+    using value_type = T;
+    using size_type = size_t;
+    using difference_type = ptrdiff_t;
+
+    using pointer = value_type*;
+    using reference = value_type&;
+    using iterator = pointer;
+    using reverse_iterator = std::reverse_iterator<iterator>;
+
+    using const_pointer = const value_type*;
+    using const_reference = const value_type&;
+    using const_iterator = const_pointer;
+    using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+    // TODO: Replace with std::construct_at in C++20.
+    template <typename... Args>
+    static pointer construct_at(const_iterator it, Args&&... args) {
+        void* const ptr = const_cast<void*>(static_cast<const void*>(it));
+        return new (ptr) value_type{std::forward<Args>(args)...};
+    }
+};
+
+// CRTP mixin to define iterator functions in terms of non-const Self::begin and Self::end.
+template <typename Self, typename T>
+class ArrayIterators {
+    FTL_ARRAY_TRAIT(T, size_type);
+
+    FTL_ARRAY_TRAIT(T, reference);
+    FTL_ARRAY_TRAIT(T, iterator);
+    FTL_ARRAY_TRAIT(T, reverse_iterator);
+
+    FTL_ARRAY_TRAIT(T, const_reference);
+    FTL_ARRAY_TRAIT(T, const_iterator);
+    FTL_ARRAY_TRAIT(T, const_reverse_iterator);
+
+    Self& self() const { return *const_cast<Self*>(static_cast<const Self*>(this)); }
+
+public:
+    const_iterator begin() const { return cbegin(); }
+    const_iterator cbegin() const { return self().begin(); }
+
+    const_iterator end() const { return cend(); }
+    const_iterator cend() const { return self().end(); }
+
+    reverse_iterator rbegin() { return std::make_reverse_iterator(self().end()); }
+    const_reverse_iterator rbegin() const { return crbegin(); }
+    const_reverse_iterator crbegin() const { return self().rbegin(); }
+
+    reverse_iterator rend() { return std::make_reverse_iterator(self().begin()); }
+    const_reverse_iterator rend() const { return crend(); }
+    const_reverse_iterator crend() const { return self().rend(); }
+
+    iterator last() { return self().end() - 1; }
+    const_iterator last() const { return self().last(); }
+
+    reference front() { return *self().begin(); }
+    const_reference front() const { return self().front(); }
+
+    reference back() { return *last(); }
+    const_reference back() const { return self().back(); }
+
+    reference operator[](size_type i) { return *(self().begin() + i); }
+    const_reference operator[](size_type i) const { return self()[i]; }
+};
+
+// Mixin to define comparison operators for an array-like template.
+// TODO: Replace with operator<=> in C++20.
+template <template <typename, size_t> class Array>
+struct ArrayComparators {
+    template <typename T, size_t N, size_t M>
+    friend bool operator==(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+        return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
+    }
+
+    template <typename T, size_t N, size_t M>
+    friend bool operator<(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+        return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
+    }
+
+    template <typename T, size_t N, size_t M>
+    friend bool operator>(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+        return rhs < lhs;
+    }
+
+    template <typename T, size_t N, size_t M>
+    friend bool operator!=(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+        return !(lhs == rhs);
+    }
+
+    template <typename T, size_t N, size_t M>
+    friend bool operator>=(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+        return !(lhs < rhs);
+    }
+
+    template <typename T, size_t N, size_t M>
+    friend bool operator<=(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+        return !(lhs > rhs);
+    }
+};
+
+} // namespace android::ftl
diff --git a/include/ftl/SmallVector.h b/include/ftl/SmallVector.h
new file mode 100644
index 0000000..cecec7f
--- /dev/null
+++ b/include/ftl/SmallVector.h
@@ -0,0 +1,380 @@
+/*
+ * Copyright 2020 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 <ftl/ArrayTraits.h>
+#include <ftl/StaticVector.h>
+
+#include <algorithm>
+#include <iterator>
+#include <type_traits>
+#include <utility>
+#include <variant>
+#include <vector>
+
+namespace android::ftl {
+
+template <typename>
+struct IsSmallVector;
+
+// ftl::StaticVector that promotes to std::vector when full. SmallVector is a drop-in replacement
+// for std::vector with statically allocated storage for N elements, whose goal is to improve run
+// time by avoiding heap allocation and increasing probability of cache hits. The standard API is
+// augmented by an unstable_erase operation that does not preserve order, and a replace operation
+// that destructively emplaces.
+//
+// SmallVector<T, 0> is a specialization that thinly wraps std::vector.
+//
+// Example usage:
+//
+//     ftl::SmallVector<char, 3> vector;
+//     assert(vector.empty());
+//     assert(!vector.dynamic());
+//
+//     vector = {'a', 'b', 'c'};
+//     assert(vector.size() == 3u);
+//     assert(!vector.dynamic());
+//
+//     vector.push_back('d');
+//     assert(vector.dynamic());
+//
+//     vector.unstable_erase(vector.begin());
+//     assert(vector == (ftl::SmallVector{'d', 'b', 'c'}));
+//
+//     vector.pop_back();
+//     assert(vector.back() == 'b');
+//     assert(vector.dynamic());
+//
+//     const char array[] = "hi";
+//     vector = ftl::SmallVector(array);
+//     assert(vector == (ftl::SmallVector{'h', 'i', '\0'}));
+//     assert(!vector.dynamic());
+//
+template <typename T, size_t N>
+class SmallVector final : ArrayTraits<T>, ArrayComparators<SmallVector> {
+    using Static = StaticVector<T, N>;
+    using Dynamic = SmallVector<T, 0>;
+
+    // TODO: Replace with std::remove_cvref_t in C++20.
+    template <typename U>
+    using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<U>>;
+
+public:
+    FTL_ARRAY_TRAIT(T, value_type);
+    FTL_ARRAY_TRAIT(T, size_type);
+    FTL_ARRAY_TRAIT(T, difference_type);
+
+    FTL_ARRAY_TRAIT(T, pointer);
+    FTL_ARRAY_TRAIT(T, reference);
+    FTL_ARRAY_TRAIT(T, iterator);
+    FTL_ARRAY_TRAIT(T, reverse_iterator);
+
+    FTL_ARRAY_TRAIT(T, const_pointer);
+    FTL_ARRAY_TRAIT(T, const_reference);
+    FTL_ARRAY_TRAIT(T, const_iterator);
+    FTL_ARRAY_TRAIT(T, const_reverse_iterator);
+
+    // Creates an empty vector.
+    SmallVector() = default;
+
+    // Constructs at most N elements. See StaticVector for underlying constructors.
+    template <typename Arg, typename... Args,
+              typename = std::enable_if_t<!IsSmallVector<remove_cvref_t<Arg>>{}>>
+    SmallVector(Arg&& arg, Args&&... args)
+          : mVector(std::in_place_type<Static>, std::forward<Arg>(arg),
+                    std::forward<Args>(args)...) {}
+
+    // Copies at most N elements from a smaller convertible vector.
+    template <typename U, size_t M, typename = std::enable_if_t<M <= N>>
+    SmallVector(const SmallVector<U, M>& other)
+          : SmallVector(IteratorRange, other.begin(), other.end()) {}
+
+    void swap(SmallVector& other) { mVector.swap(other.mVector); }
+
+    // Returns whether the vector is backed by static or dynamic storage.
+    bool dynamic() const { return std::holds_alternative<Dynamic>(mVector); }
+
+    // Avoid std::visit as it generates a dispatch table.
+#define DISPATCH(T, F, ...)                                                                \
+    T F() __VA_ARGS__ {                                                                    \
+        return dynamic() ? std::get<Dynamic>(mVector).F() : std::get<Static>(mVector).F(); \
+    }
+
+    DISPATCH(size_type, max_size, const)
+    DISPATCH(size_type, size, const)
+    DISPATCH(bool, empty, const)
+
+    // noexcept to suppress warning about zero variadic macro arguments.
+    DISPATCH(iterator, begin, noexcept)
+    DISPATCH(const_iterator, begin, const)
+    DISPATCH(const_iterator, cbegin, const)
+
+    DISPATCH(iterator, end, noexcept)
+    DISPATCH(const_iterator, end, const)
+    DISPATCH(const_iterator, cend, const)
+
+    DISPATCH(reverse_iterator, rbegin, noexcept)
+    DISPATCH(const_reverse_iterator, rbegin, const)
+    DISPATCH(const_reverse_iterator, crbegin, const)
+
+    DISPATCH(reverse_iterator, rend, noexcept)
+    DISPATCH(const_reverse_iterator, rend, const)
+    DISPATCH(const_reverse_iterator, crend, const)
+
+    DISPATCH(iterator, last, noexcept)
+    DISPATCH(const_iterator, last, const)
+
+    DISPATCH(reference, front, noexcept)
+    DISPATCH(const_reference, front, const)
+
+    DISPATCH(reference, back, noexcept)
+    DISPATCH(const_reference, back, const)
+
+#undef DISPATCH
+
+    reference operator[](size_type i) {
+        return dynamic() ? std::get<Dynamic>(mVector)[i] : std::get<Static>(mVector)[i];
+    }
+
+    const_reference operator[](size_type i) const { return const_cast<SmallVector&>(*this)[i]; }
+
+    // Replaces an element, and returns a reference to it. The iterator must be dereferenceable, so
+    // replacing at end() is erroneous.
+    //
+    // The element is emplaced via move constructor, so type T does not need to define copy/move
+    // assignment, e.g. its data members may be const.
+    //
+    // The arguments may directly or indirectly refer to the element being replaced.
+    //
+    // Iterators to the replaced element point to its replacement, and others remain valid.
+    //
+    template <typename... Args>
+    reference replace(const_iterator it, Args&&... args) {
+        if (dynamic()) {
+            return std::get<Dynamic>(mVector).replace(it, std::forward<Args>(args)...);
+        } else {
+            return std::get<Static>(mVector).replace(it, std::forward<Args>(args)...);
+        }
+    }
+
+    // Appends an element, and returns a reference to it.
+    //
+    // If the vector reaches its static or dynamic capacity, then all iterators are invalidated.
+    // Otherwise, only the end() iterator is invalidated.
+    //
+    template <typename... Args>
+    reference emplace_back(Args&&... args) {
+        constexpr auto insertStatic = &Static::template emplace_back<Args...>;
+        constexpr auto insertDynamic = &Dynamic::template emplace_back<Args...>;
+        return *insert<insertStatic, insertDynamic>(std::forward<Args>(args)...);
+    }
+
+    // Appends an element.
+    //
+    // If the vector reaches its static or dynamic capacity, then all iterators are invalidated.
+    // Otherwise, only the end() iterator is invalidated.
+    //
+    void push_back(const value_type& v) {
+        constexpr auto insertStatic =
+                static_cast<bool (Static::*)(const value_type&)>(&Static::push_back);
+        constexpr auto insertDynamic =
+                static_cast<bool (Dynamic::*)(const value_type&)>(&Dynamic::push_back);
+        insert<insertStatic, insertDynamic>(v);
+    }
+
+    void push_back(value_type&& v) {
+        constexpr auto insertStatic =
+                static_cast<bool (Static::*)(value_type&&)>(&Static::push_back);
+        constexpr auto insertDynamic =
+                static_cast<bool (Dynamic::*)(value_type&&)>(&Dynamic::push_back);
+        insert<insertStatic, insertDynamic>(std::move(v));
+    }
+
+    // Removes the last element. The vector must not be empty, or the call is erroneous.
+    //
+    // The last() and end() iterators are invalidated.
+    //
+    void pop_back() {
+        if (dynamic()) {
+            std::get<Dynamic>(mVector).pop_back();
+        } else {
+            std::get<Static>(mVector).pop_back();
+        }
+    }
+
+    // Erases an element, but does not preserve order. Rather than shifting subsequent elements,
+    // this moves the last element to the slot of the erased element.
+    //
+    // The last() and end() iterators, as well as those to the erased element, are invalidated.
+    //
+    void unstable_erase(iterator it) {
+        if (dynamic()) {
+            std::get<Dynamic>(mVector).unstable_erase(it);
+        } else {
+            std::get<Static>(mVector).unstable_erase(it);
+        }
+    }
+
+private:
+    template <auto insertStatic, auto insertDynamic, typename... Args>
+    auto insert(Args&&... args) {
+        if (Dynamic* const vector = std::get_if<Dynamic>(&mVector)) {
+            return (vector->*insertDynamic)(std::forward<Args>(args)...);
+        }
+
+        auto& vector = std::get<Static>(mVector);
+        if (vector.full()) {
+            return (promote(vector).*insertDynamic)(std::forward<Args>(args)...);
+        } else {
+            return (vector.*insertStatic)(std::forward<Args>(args)...);
+        }
+    }
+
+    Dynamic& promote(Static& staticVector) {
+        assert(staticVector.full());
+
+        // Allocate double capacity to reduce probability of reallocation.
+        Dynamic vector;
+        vector.reserve(Static::max_size() * 2);
+        std::move(staticVector.begin(), staticVector.end(), std::back_inserter(vector));
+
+        return mVector.template emplace<Dynamic>(std::move(vector));
+    }
+
+    std::variant<Static, Dynamic> mVector;
+};
+
+// Partial specialization without static storage.
+template <typename T>
+class SmallVector<T, 0> final : ArrayTraits<T>,
+                                ArrayIterators<SmallVector<T, 0>, T>,
+                                std::vector<T> {
+    using ArrayTraits<T>::construct_at;
+
+    using Iter = ArrayIterators<SmallVector, T>;
+    using Impl = std::vector<T>;
+
+    friend Iter;
+
+public:
+    FTL_ARRAY_TRAIT(T, value_type);
+    FTL_ARRAY_TRAIT(T, size_type);
+    FTL_ARRAY_TRAIT(T, difference_type);
+
+    FTL_ARRAY_TRAIT(T, pointer);
+    FTL_ARRAY_TRAIT(T, reference);
+    FTL_ARRAY_TRAIT(T, iterator);
+    FTL_ARRAY_TRAIT(T, reverse_iterator);
+
+    FTL_ARRAY_TRAIT(T, const_pointer);
+    FTL_ARRAY_TRAIT(T, const_reference);
+    FTL_ARRAY_TRAIT(T, const_iterator);
+    FTL_ARRAY_TRAIT(T, const_reverse_iterator);
+
+    using Impl::Impl;
+
+    using Impl::empty;
+    using Impl::max_size;
+    using Impl::size;
+
+    using Impl::reserve;
+
+    // std::vector iterators are not necessarily raw pointers.
+    iterator begin() { return Impl::data(); }
+    iterator end() { return Impl::data() + size(); }
+
+    using Iter::begin;
+    using Iter::end;
+
+    using Iter::cbegin;
+    using Iter::cend;
+
+    using Iter::rbegin;
+    using Iter::rend;
+
+    using Iter::crbegin;
+    using Iter::crend;
+
+    using Iter::last;
+
+    using Iter::back;
+    using Iter::front;
+
+    using Iter::operator[];
+
+    template <typename... Args>
+    reference replace(const_iterator it, Args&&... args) {
+        value_type element{std::forward<Args>(args)...};
+        std::destroy_at(it);
+        // This is only safe because exceptions are disabled.
+        return *construct_at(it, std::move(element));
+    }
+
+    template <typename... Args>
+    iterator emplace_back(Args&&... args) {
+        return &Impl::emplace_back(std::forward<Args>(args)...);
+    }
+
+    bool push_back(const value_type& v) {
+        Impl::push_back(v);
+        return true;
+    }
+
+    bool push_back(value_type&& v) {
+        Impl::push_back(std::move(v));
+        return true;
+    }
+
+    using Impl::pop_back;
+
+    void unstable_erase(iterator it) {
+        if (it != last()) std::iter_swap(it, last());
+        pop_back();
+    }
+
+    void swap(SmallVector& other) { Impl::swap(other); }
+};
+
+template <typename>
+struct IsSmallVector : std::false_type {};
+
+template <typename T, size_t N>
+struct IsSmallVector<SmallVector<T, N>> : std::true_type {};
+
+// Deduction guide for array constructor.
+template <typename T, size_t N>
+SmallVector(T (&)[N]) -> SmallVector<std::remove_cv_t<T>, N>;
+
+// Deduction guide for variadic constructor.
+template <typename T, typename... Us, typename V = std::decay_t<T>,
+          typename = std::enable_if_t<(std::is_constructible_v<V, Us> && ...)>>
+SmallVector(T&&, Us&&...) -> SmallVector<V, 1 + sizeof...(Us)>;
+
+// Deduction guide for in-place constructor.
+template <typename T, typename... Us>
+SmallVector(std::in_place_type_t<T>, Us&&...) -> SmallVector<T, sizeof...(Us)>;
+
+// Deduction guide for StaticVector conversion.
+template <typename T, size_t N>
+SmallVector(StaticVector<T, N>&&) -> SmallVector<T, N>;
+
+template <typename T, size_t N>
+inline void swap(SmallVector<T, N>& lhs, SmallVector<T, N>& rhs) {
+    lhs.swap(rhs);
+}
+
+} // namespace android::ftl
diff --git a/include/ftl/StaticVector.h b/include/ftl/StaticVector.h
index b586e91..457095d 100644
--- a/include/ftl/StaticVector.h
+++ b/include/ftl/StaticVector.h
@@ -16,22 +16,25 @@
 
 #pragma once
 
+#include <ftl/ArrayTraits.h>
+
 #include <algorithm>
 #include <cassert>
 #include <iterator>
 #include <memory>
-#include <new>
 #include <type_traits>
 #include <utility>
 
 namespace android::ftl {
 
+constexpr struct IteratorRangeTag {} IteratorRange;
+
 // Fixed-capacity, statically allocated counterpart of std::vector. Akin to std::array, StaticVector
 // allocates contiguous storage for N elements of type T at compile time, but stores at most (rather
 // than exactly) N elements. Unlike std::array, its default constructor does not require T to have a
 // default constructor, since elements are constructed in-place as the vector grows. Operations that
-// insert an element, such as push_back and emplace, fail when the vector is full. The API otherwise
-// adheres to standard containers, except the unstable_erase operation that does not shift elements,
+// insert an element (emplace_back, push_back, etc.) fail when the vector is full. The API otherwise
+// adheres to standard containers, except the unstable_erase operation that does not preserve order,
 // and the replace operation that destructively emplaces.
 //
 // StaticVector<T, 1> is analogous to an iterable std::optional, but StaticVector<T, 0> is an error.
@@ -61,46 +64,70 @@
 //     assert(vector == (ftl::StaticVector{'h', 'i', '\0'}));
 //
 template <typename T, size_t N>
-class StaticVector {
+class StaticVector final : ArrayTraits<T>,
+                           ArrayIterators<StaticVector<T, N>, T>,
+                           ArrayComparators<StaticVector> {
     static_assert(N > 0);
 
-    template <typename I>
-    using IsInputIterator = std::is_base_of<std::input_iterator_tag,
-                                            typename std::iterator_traits<I>::iterator_category>;
+    using ArrayTraits<T>::construct_at;
+
+    using Iter = ArrayIterators<StaticVector, T>;
+    friend Iter;
+
+    // There is ambiguity when constructing from two iterator-like elements like pointers:
+    // they could be an iterator range, or arguments for in-place construction. Assume the
+    // latter unless they are input iterators and cannot be used to construct elements. If
+    // the former is intended, the caller can pass an IteratorRangeTag to disambiguate.
+    template <typename I, typename Traits = std::iterator_traits<I>>
+    using IsInputIterator = std::conjunction<
+            std::is_base_of<std::input_iterator_tag, typename Traits::iterator_category>,
+            std::negation<std::is_constructible<T, I>>>;
 
 public:
-    using value_type = T;
-    using size_type = size_t;
-    using difference_type = ptrdiff_t;
+    FTL_ARRAY_TRAIT(T, value_type);
+    FTL_ARRAY_TRAIT(T, size_type);
+    FTL_ARRAY_TRAIT(T, difference_type);
 
-    using pointer = value_type*;
-    using reference = value_type&;
-    using iterator = pointer;
-    using reverse_iterator = std::reverse_iterator<iterator>;
+    FTL_ARRAY_TRAIT(T, pointer);
+    FTL_ARRAY_TRAIT(T, reference);
+    FTL_ARRAY_TRAIT(T, iterator);
+    FTL_ARRAY_TRAIT(T, reverse_iterator);
 
-    using const_pointer = const value_type*;
-    using const_reference = const value_type&;
-    using const_iterator = const_pointer;
-    using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+    FTL_ARRAY_TRAIT(T, const_pointer);
+    FTL_ARRAY_TRAIT(T, const_reference);
+    FTL_ARRAY_TRAIT(T, const_iterator);
+    FTL_ARRAY_TRAIT(T, const_reverse_iterator);
 
     // Creates an empty vector.
     StaticVector() = default;
 
     // Copies and moves a vector, respectively.
-    StaticVector(const StaticVector& other) : StaticVector(other.begin(), other.end()) {}
+    StaticVector(const StaticVector& other)
+          : StaticVector(IteratorRange, other.begin(), other.end()) {}
     StaticVector(StaticVector&& other) { swap<Empty>(other); }
 
     // Copies at most N elements from a smaller convertible vector.
     template <typename U, size_t M, typename = std::enable_if_t<M <= N>>
-    StaticVector(const StaticVector<U, M>& other) : StaticVector(other.begin(), other.end()) {}
+    StaticVector(const StaticVector<U, M>& other)
+          : StaticVector(IteratorRange, other.begin(), other.end()) {}
 
     // Copies at most N elements from an array.
     template <typename U, size_t M>
-    explicit StaticVector(U (&array)[M]) : StaticVector(std::begin(array), std::end(array)) {}
+    explicit StaticVector(U (&array)[M])
+          : StaticVector(IteratorRange, std::begin(array), std::end(array)) {}
 
     // Copies at most N elements from the range [first, last).
+    //
+    // IteratorRangeTag disambiguates with initialization from two iterator-like elements.
+    //
     template <typename Iterator, typename = std::enable_if_t<IsInputIterator<Iterator>{}>>
-    StaticVector(Iterator first, Iterator last)
+    StaticVector(Iterator first, Iterator last) : StaticVector(IteratorRange, first, last) {
+        using V = typename std::iterator_traits<Iterator>::value_type;
+        static_assert(std::is_constructible_v<value_type, V>, "Incompatible iterator range");
+    }
+
+    template <typename Iterator>
+    StaticVector(IteratorRangeTag, Iterator first, Iterator last)
           : mSize(std::min(max_size(), static_cast<size_type>(std::distance(first, last)))) {
         std::uninitialized_copy(first, first + mSize, begin());
     }
@@ -154,53 +181,57 @@
     template <typename = void>
     void swap(StaticVector&);
 
-    size_type max_size() const { return N; }
+    static constexpr size_type max_size() { return N; }
     size_type size() const { return mSize; }
 
     bool empty() const { return size() == 0; }
     bool full() const { return size() == max_size(); }
 
     iterator begin() { return std::launder(reinterpret_cast<pointer>(mData)); }
-    const_iterator begin() const { return cbegin(); }
-    const_iterator cbegin() const { return mut().begin(); }
-
     iterator end() { return begin() + size(); }
-    const_iterator end() const { return cend(); }
-    const_iterator cend() const { return mut().end(); }
 
-    reverse_iterator rbegin() { return std::make_reverse_iterator(end()); }
-    const_reverse_iterator rbegin() const { return crbegin(); }
-    const_reverse_iterator crbegin() const { return mut().rbegin(); }
+    using Iter::begin;
+    using Iter::end;
 
-    reverse_iterator rend() { return std::make_reverse_iterator(begin()); }
-    const_reverse_iterator rend() const { return crend(); }
-    const_reverse_iterator crend() const { return mut().rend(); }
+    using Iter::cbegin;
+    using Iter::cend;
 
-    iterator last() { return end() - 1; }
-    const_iterator last() const { return mut().last(); }
+    using Iter::rbegin;
+    using Iter::rend;
 
-    reference front() { return *begin(); }
-    const_reference front() const { return mut().front(); }
+    using Iter::crbegin;
+    using Iter::crend;
 
-    reference back() { return *last(); }
-    const_reference back() const { return mut().back(); }
+    using Iter::last;
 
-    reference operator[](size_type i) { return *(begin() + i); }
-    const_reference operator[](size_type i) const { return mut()[i]; }
+    using Iter::back;
+    using Iter::front;
 
-    // Replaces an element, and returns an iterator to it. If the vector is full, the element is not
-    // replaced, and the end iterator is returned.
+    using Iter::operator[];
+
+    // Replaces an element, and returns a reference to it. The iterator must be dereferenceable, so
+    // replacing at end() is erroneous.
+    //
+    // The element is emplaced via move constructor, so type T does not need to define copy/move
+    // assignment, e.g. its data members may be const.
+    //
+    // The arguments may directly or indirectly refer to the element being replaced.
+    //
+    // Iterators to the replaced element point to its replacement, and others remain valid.
+    //
     template <typename... Args>
-    iterator replace(const_iterator cit, Args&&... args) {
-        if (full()) return end();
-        // Append element, and move into place if not last.
-        emplace_back(std::forward<Args>(args)...);
-        if (cit != last()) unstable_erase(cit);
-        return const_cast<iterator>(cit);
+    reference replace(const_iterator it, Args&&... args) {
+        value_type element{std::forward<Args>(args)...};
+        std::destroy_at(it);
+        // This is only safe because exceptions are disabled.
+        return *construct_at(it, std::move(element));
     }
 
     // Appends an element, and returns an iterator to it. If the vector is full, the element is not
-    // inserted, and the end iterator is returned.
+    // inserted, and the end() iterator is returned.
+    //
+    // On success, the end() iterator is invalidated.
+    //
     template <typename... Args>
     iterator emplace_back(Args&&... args) {
         if (full()) return end();
@@ -209,31 +240,47 @@
         return it;
     }
 
+    // Appends an element unless the vector is full, and returns whether the element was inserted.
+    //
+    // On success, the end() iterator is invalidated.
+    //
+    bool push_back(const value_type& v) {
+        // Two statements for sequence point.
+        const iterator it = emplace_back(v);
+        return it != end();
+    }
+
+    bool push_back(value_type&& v) {
+        // Two statements for sequence point.
+        const iterator it = emplace_back(std::move(v));
+        return it != end();
+    }
+
+    // Removes the last element. The vector must not be empty, or the call is erroneous.
+    //
+    // The last() and end() iterators are invalidated.
+    //
+    void pop_back() { unstable_erase(last()); }
+
     // Erases an element, but does not preserve order. Rather than shifting subsequent elements,
     // this moves the last element to the slot of the erased element.
+    //
+    // The last() and end() iterators, as well as those to the erased element, are invalidated.
+    //
     void unstable_erase(const_iterator it) {
         std::destroy_at(it);
         if (it != last()) {
-            // Move last element and destroy its source for destructor side effects.
+            // Move last element and destroy its source for destructor side effects. This is only
+            // safe because exceptions are disabled.
             construct_at(it, std::move(back()));
             std::destroy_at(last());
         }
         --mSize;
     }
 
-    bool push_back(value_type v) {
-        // Two statements for sequence point.
-        const iterator it = emplace_back(std::move(v));
-        return it != end();
-    }
-
-    void pop_back() { unstable_erase(last()); }
-
 private:
     struct Empty {};
 
-    StaticVector& mut() const { return *const_cast<StaticVector*>(this); }
-
     // Recursion for variadic constructor.
     template <size_t I, typename E, typename... Es>
     StaticVector(std::index_sequence<I>, E&& element, Es&&... elements)
@@ -245,13 +292,6 @@
     template <size_t I>
     explicit StaticVector(std::index_sequence<I>) : mSize(I) {}
 
-    // TODO: Replace with std::construct_at in C++20.
-    template <typename... Args>
-    static pointer construct_at(const_iterator it, Args&&... args) {
-        void* const ptr = const_cast<void*>(static_cast<const void*>(it));
-        return new (ptr) value_type{std::forward<Args>(args)...};
-    }
-
     size_type mSize = 0;
     std::aligned_storage_t<sizeof(value_type), alignof(value_type)> mData[N];
 };
@@ -307,35 +347,4 @@
     lhs.swap(rhs);
 }
 
-// TODO: Replace with operator<=> in C++20.
-template <typename T, size_t N, size_t M>
-inline bool operator==(const StaticVector<T, N>& lhs, const StaticVector<T, M>& rhs) {
-    return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
-}
-
-template <typename T, size_t N, size_t M>
-inline bool operator<(const StaticVector<T, N>& lhs, const StaticVector<T, M>& rhs) {
-    return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
-}
-
-template <typename T, size_t N, size_t M>
-inline bool operator>(const StaticVector<T, N>& lhs, const StaticVector<T, M>& rhs) {
-    return rhs < lhs;
-}
-
-template <typename T, size_t N, size_t M>
-inline bool operator!=(const StaticVector<T, N>& lhs, const StaticVector<T, M>& rhs) {
-    return !(lhs == rhs);
-}
-
-template <typename T, size_t N, size_t M>
-inline bool operator>=(const StaticVector<T, N>& lhs, const StaticVector<T, M>& rhs) {
-    return !(lhs < rhs);
-}
-
-template <typename T, size_t N, size_t M>
-inline bool operator<=(const StaticVector<T, N>& lhs, const StaticVector<T, M>& rhs) {
-    return !(rhs < lhs);
-}
-
 } // namespace android::ftl
diff --git a/include/input/DisplayViewport.h b/include/input/DisplayViewport.h
index 334fe34..b90d57e 100644
--- a/include/input/DisplayViewport.h
+++ b/include/input/DisplayViewport.h
@@ -19,6 +19,7 @@
 
 #include <android-base/stringprintf.h>
 #include <input/Input.h>
+#include <input/NamedEnum.h>
 
 #include <cinttypes>
 #include <optional>
@@ -44,18 +45,6 @@
     VIRTUAL = 3,
 };
 
-static const char* viewportTypeToString(ViewportType type) {
-    switch (type) {
-        case ViewportType::INTERNAL:
-            return "INTERNAL";
-        case ViewportType::EXTERNAL:
-            return "EXTERNAL";
-        case ViewportType::VIRTUAL:
-            return "VIRTUAL";
-    }
-    return "UNKNOWN";
-}
-
 /*
  * Describes how coordinates are mapped on a physical display.
  * See com.android.server.display.DisplayViewport.
@@ -142,7 +131,7 @@
                             "physicalFrame=[%d, %d, %d, %d], "
                             "deviceSize=[%d, %d], "
                             "isActive=[%d]",
-                            viewportTypeToString(type), displayId, uniqueId.c_str(),
+                            NamedEnum::string(type).c_str(), displayId, uniqueId.c_str(),
                             physicalPort ? StringPrintf("%" PRIu8, *physicalPort).c_str()
                                          : "<none>",
                             orientation, logicalLeft, logicalTop, logicalRight, logicalBottom,
diff --git a/include/input/Input.h b/include/input/Input.h
index d3a9694..3facfa5 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -177,7 +177,9 @@
 
 namespace android {
 
+#ifdef __linux__
 class Parcel;
+#endif
 
 const char* inputEventTypeToString(int32_t type);
 
@@ -344,8 +346,10 @@
         return getAxisValue(AMOTION_EVENT_AXIS_Y);
     }
 
+#ifdef __linux__
     status_t readFromParcel(Parcel* parcel);
     status_t writeToParcel(Parcel* parcel) const;
+#endif
 
     bool operator==(const PointerCoords& other) const;
     inline bool operator!=(const PointerCoords& other) const {
@@ -704,8 +708,10 @@
     // Matrix is in row-major form and compatible with SkMatrix.
     void transform(const std::array<float, 9>& matrix);
 
+#ifdef __linux__
     status_t readFromParcel(Parcel* parcel);
     status_t writeToParcel(Parcel* parcel) const;
+#endif
 
     static bool isTouchEvent(uint32_t source, int32_t action);
     inline bool isTouchEvent() const {
diff --git a/include/input/KeyCharacterMap.h b/include/input/KeyCharacterMap.h
index 339c7eb..23f8ddf 100644
--- a/include/input/KeyCharacterMap.h
+++ b/include/input/KeyCharacterMap.h
@@ -19,7 +19,9 @@
 
 #include <stdint.h>
 
+#ifdef __linux__
 #include <binder/IBinder.h>
+#endif
 
 #include <android-base/result.h>
 #include <input/Input.h>
@@ -42,27 +44,27 @@
  */
 class KeyCharacterMap {
 public:
-    enum KeyboardType {
-        KEYBOARD_TYPE_UNKNOWN = 0,
-        KEYBOARD_TYPE_NUMERIC = 1,
-        KEYBOARD_TYPE_PREDICTIVE = 2,
-        KEYBOARD_TYPE_ALPHA = 3,
-        KEYBOARD_TYPE_FULL = 4,
+    enum class KeyboardType : int32_t {
+        UNKNOWN = 0,
+        NUMERIC = 1,
+        PREDICTIVE = 2,
+        ALPHA = 3,
+        FULL = 4,
         /**
          * Deprecated. Set 'keyboard.specialFunction' to '1' in the device's IDC file instead.
          */
-        KEYBOARD_TYPE_SPECIAL_FUNCTION = 5,
-        KEYBOARD_TYPE_OVERLAY = 6,
+        SPECIAL_FUNCTION = 5,
+        OVERLAY = 6,
     };
 
-    enum Format {
+    enum class Format {
         // Base keyboard layout, may contain device-specific options, such as "type" declaration.
-        FORMAT_BASE = 0,
+        BASE = 0,
         // Overlay keyboard layout, more restrictive, may be published by applications,
         // cannot override device-specific options.
-        FORMAT_OVERLAY = 1,
+        OVERLAY = 1,
         // Either base or overlay layout ok.
-        FORMAT_ANY = 2,
+        ANY = 2,
     };
 
     // Substitute key code and meta state for fallback action.
@@ -86,7 +88,7 @@
     void combine(const KeyCharacterMap& overlay);
 
     /* Gets the keyboard type. */
-    int32_t getKeyboardType() const;
+    KeyboardType getKeyboardType() const;
 
     /* Gets the primary character for this key as in the label physically printed on it.
      * Returns 0 if none (eg. for non-printing keys). */
@@ -132,11 +134,13 @@
     void tryRemapKey(int32_t scanCode, int32_t metaState,
             int32_t* outKeyCode, int32_t* outMetaState) const;
 
+#ifdef __linux__
     /* Reads a key map from a parcel. */
     static std::shared_ptr<KeyCharacterMap> readFromParcel(Parcel* parcel);
 
     /* Writes a key map to a parcel. */
     void writeToParcel(Parcel* parcel) const;
+#endif
 
     KeyCharacterMap(const KeyCharacterMap& other);
 
@@ -222,7 +226,7 @@
     };
 
     KeyedVector<int32_t, Key*> mKeys;
-    int mType;
+    KeyboardType mType;
     std::string mLoadFileName;
 
     KeyedVector<int32_t, int32_t> mKeysByScanCode;
diff --git a/include/input/NamedEnum.h b/include/input/NamedEnum.h
index 42cfb12..1d987fe 100644
--- a/include/input/NamedEnum.h
+++ b/include/input/NamedEnum.h
@@ -115,10 +115,10 @@
     // Do not specialize it to a large number to avoid performance issues.
     // The recommended maximum enum number to specialize is 64.
     template <typename E>
-    static const std::string string(E val) {
+    static const std::string string(E val, const char* fallbackFormat = "0x%08x") {
         std::string result;
         std::optional<std::string_view> enumString = enum_name(val);
-        result += enumString ? enumString.value() : base::StringPrintf("0x%08x", val);
+        result += enumString ? enumString.value() : base::StringPrintf(fallbackFormat, val);
         return result;
     }
 };
diff --git a/libs/android_runtime_lazy/Android.bp b/libs/android_runtime_lazy/Android.bp
index 09a5f39..cdd7764 100644
--- a/libs/android_runtime_lazy/Android.bp
+++ b/libs/android_runtime_lazy/Android.bp
@@ -35,6 +35,11 @@
     vendor_available: true,
     double_loadable: true,
     host_supported: true,
+    target: {
+        darwin: {
+            enabled: false,
+        }
+    },
 
     cflags: [
         "-Wall",
diff --git a/libs/binder/ActivityManager.cpp b/libs/binder/ActivityManager.cpp
index 5e4c98f..727ea60 100644
--- a/libs/binder/ActivityManager.cpp
+++ b/libs/binder/ActivityManager.cpp
@@ -17,6 +17,7 @@
 #include <mutex>
 #include <unistd.h>
 
+#include <android/permission_manager.h>
 #include <binder/ActivityManager.h>
 #include <binder/Binder.h>
 #include <binder/IServiceManager.h>
@@ -98,6 +99,18 @@
     return PROCESS_STATE_UNKNOWN;
 }
 
+status_t ActivityManager::checkPermission(const String16& permission,
+                                     const pid_t pid,
+                                     const uid_t uid,
+                                     int32_t* outResult) {
+    sp<IActivityManager> service = getService();
+    if (service != nullptr) {
+        return service->checkPermission(permission, pid, uid, outResult);
+    }
+    // ActivityManagerService appears dead. Return usual error code for dead service.
+    return DEAD_OBJECT;
+}
+
 status_t ActivityManager::linkToDeath(const sp<IBinder::DeathRecipient>& recipient) {
     sp<IActivityManager> service = getService();
     if (service != nullptr) {
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index d363ee9..e6071a0 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -31,6 +31,11 @@
         "libutils_headers",
     ],
     min_sdk_version: "29",
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
 }
 
 // These interfaces are android-specific implementation unrelated to binder
@@ -122,6 +127,9 @@
         vendor: {
             exclude_srcs: libbinder_device_interface_sources,
         },
+        darwin: {
+            enabled: false,
+        },
     },
 
     aidl: {
diff --git a/libs/binder/IActivityManager.cpp b/libs/binder/IActivityManager.cpp
index 1eb5363..e9f5aae 100644
--- a/libs/binder/IActivityManager.cpp
+++ b/libs/binder/IActivityManager.cpp
@@ -17,9 +17,11 @@
 #include <unistd.h>
 #include <fcntl.h>
 
+#include <android/permission_manager.h>
 #include <binder/ActivityManager.h>
 #include <binder/IActivityManager.h>
 #include <binder/Parcel.h>
+#include <utils/Errors.h>
 
 namespace android {
 
@@ -104,6 +106,23 @@
         }
         return reply.readInt32();
     }
+
+    virtual status_t checkPermission(const String16& permission,
+                                    const pid_t pid,
+                                    const uid_t uid,
+                                    int32_t* outResult) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor());
+        data.writeString16(permission);
+        data.writeInt32(pid);
+        data.writeInt32(uid);
+        status_t err = remote()->transact(CHECK_PERMISSION_TRANSACTION, data, &reply);
+        if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
+            return err;
+        }
+        *outResult = reply.readInt32();
+        return NO_ERROR;
+    }
 };
 
 // ------------------------------------------------------------------------------------
diff --git a/libs/binder/LazyServiceRegistrar.cpp b/libs/binder/LazyServiceRegistrar.cpp
index 325e204..2e15e50 100644
--- a/libs/binder/LazyServiceRegistrar.cpp
+++ b/libs/binder/LazyServiceRegistrar.cpp
@@ -29,16 +29,12 @@
 
 using AidlServiceManager = android::os::IServiceManager;
 
-class ClientCounterCallback : public ::android::os::BnClientCallback {
+class ClientCounterCallbackImpl : public ::android::os::BnClientCallback {
 public:
-    ClientCounterCallback() : mNumConnectedServices(0), mForcePersist(false) {}
+    ClientCounterCallbackImpl() : mNumConnectedServices(0), mForcePersist(false) {}
 
     bool registerService(const sp<IBinder>& service, const std::string& name,
                          bool allowIsolated, int dumpFlags);
-
-    /**
-     * Set a flag to prevent services from automatically shutting down
-     */
     void forcePersist(bool persist);
 
 protected:
@@ -75,7 +71,23 @@
     bool mForcePersist;
 };
 
-bool ClientCounterCallback::registerService(const sp<IBinder>& service, const std::string& name,
+class ClientCounterCallback {
+public:
+    ClientCounterCallback();
+
+    bool registerService(const sp<IBinder>& service, const std::string& name,
+                                            bool allowIsolated, int dumpFlags);
+
+    /**
+     * Set a flag to prevent services from automatically shutting down
+     */
+    void forcePersist(bool persist);
+
+private:
+    sp<ClientCounterCallbackImpl> mImpl;
+};
+
+bool ClientCounterCallbackImpl::registerService(const sp<IBinder>& service, const std::string& name,
                                             bool allowIsolated, int dumpFlags) {
     auto manager = interface_cast<AidlServiceManager>(asBinder(defaultServiceManager()));
 
@@ -89,7 +101,7 @@
     }
 
     if (!reRegister) {
-        if (!manager->registerClientCallback(name, service, this).isOk()) {
+        if(!manager->registerClientCallback(name, service, this).isOk()) {
             ALOGE("Failed to add client callback for service %s", name.c_str());
             return false;
         }
@@ -105,7 +117,7 @@
     return true;
 }
 
-std::map<std::string, ClientCounterCallback::Service>::iterator ClientCounterCallback::assertRegisteredService(const sp<IBinder>& service) {
+std::map<std::string, ClientCounterCallbackImpl::Service>::iterator ClientCounterCallbackImpl::assertRegisteredService(const sp<IBinder>& service) {
     LOG_ALWAYS_FATAL_IF(service == nullptr, "Got onClients callback for null service");
     for (auto it = mRegisteredServices.begin(); it != mRegisteredServices.end(); ++it) {
         auto const& [name, registered] = *it;
@@ -117,7 +129,7 @@
     __builtin_unreachable();
 }
 
-void ClientCounterCallback::forcePersist(bool persist) {
+void ClientCounterCallbackImpl::forcePersist(bool persist) {
     mForcePersist = persist;
     if(!mForcePersist) {
         // Attempt a shutdown in case the number of clients hit 0 while the flag was on
@@ -129,7 +141,7 @@
  * onClients is oneway, so no need to worry about multi-threading. Note that this means multiple
  * invocations could occur on different threads however.
  */
-Status ClientCounterCallback::onClients(const sp<IBinder>& service, bool clients) {
+Status ClientCounterCallbackImpl::onClients(const sp<IBinder>& service, bool clients) {
     auto & [name, registered] = *assertRegisteredService(service);
     if (registered.clients == clients) {
         LOG_ALWAYS_FATAL("Process already thought %s had clients: %d but servicemanager has "
@@ -154,7 +166,7 @@
     return Status::ok();
 }
 
-void ClientCounterCallback::tryShutdown() {
+void ClientCounterCallbackImpl::tryShutdown() {
     if(mNumConnectedServices > 0) {
         // Should only shut down if there are no clients
         return;
@@ -175,7 +187,6 @@
 
         bool success = manager->tryUnregisterService(entry.first, entry.second.service).isOk();
 
-
         if (!success) {
             ALOGI("Failed to unregister service %s", entry.first.c_str());
             break;
@@ -200,6 +211,19 @@
     }
 }
 
+ClientCounterCallback::ClientCounterCallback() {
+      mImpl = sp<ClientCounterCallbackImpl>::make();
+}
+
+bool ClientCounterCallback::registerService(const sp<IBinder>& service, const std::string& name,
+                                            bool allowIsolated, int dumpFlags) {
+    return mImpl->registerService(service, name, allowIsolated, dumpFlags);
+}
+
+void ClientCounterCallback::forcePersist(bool persist) {
+    mImpl->forcePersist(persist);
+}
+
 }  // namespace internal
 
 LazyServiceRegistrar::LazyServiceRegistrar() {
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index ddd9f9b..4381386 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -1068,6 +1068,7 @@
 {
     if (str == nullptr) return writeInt32(-1);
 
+    // NOTE: Keep this logic in sync with android_os_Parcel.cpp
     status_t err = writeInt32(len);
     if (err == NO_ERROR) {
         uint8_t* data = (uint8_t*)writeInplace(len+sizeof(char));
@@ -1108,6 +1109,7 @@
 {
     if (str == nullptr) return writeInt32(-1);
 
+    // NOTE: Keep this logic in sync with android_os_Parcel.cpp
     status_t err = writeInt32(len);
     if (err == NO_ERROR) {
         len *= sizeof(char16_t);
diff --git a/libs/binder/include/binder/ActivityManager.h b/libs/binder/include/binder/ActivityManager.h
index 9108e31..6796723 100644
--- a/libs/binder/include/binder/ActivityManager.h
+++ b/libs/binder/include/binder/ActivityManager.h
@@ -77,9 +77,9 @@
     void unregisterUidObserver(const sp<IUidObserver>& observer);
     bool isUidActive(const uid_t uid, const String16& callingPackage);
     int getUidProcessState(const uid_t uid, const String16& callingPackage);
+    status_t checkPermission(const String16& permission, const pid_t pid, const uid_t uid, int32_t* outResult);
 
-
-  status_t linkToDeath(const sp<IBinder::DeathRecipient>& recipient);
+    status_t linkToDeath(const sp<IBinder::DeathRecipient>& recipient);
     status_t unlinkToDeath(const sp<IBinder::DeathRecipient>& recipient);
 
 private:
diff --git a/libs/binder/include/binder/IActivityManager.h b/libs/binder/include/binder/IActivityManager.h
index e0248f6..4573347 100644
--- a/libs/binder/include/binder/IActivityManager.h
+++ b/libs/binder/include/binder/IActivityManager.h
@@ -39,13 +39,18 @@
     virtual void unregisterUidObserver(const sp<IUidObserver>& observer) = 0;
     virtual bool isUidActive(const uid_t uid, const String16& callingPackage) = 0;
     virtual int32_t getUidProcessState(const uid_t uid, const String16& callingPackage) = 0;
+    virtual status_t checkPermission(const String16& permission,
+                                    const pid_t pid,
+                                    const uid_t uid,
+                                    int32_t* outResult) = 0;
 
     enum {
         OPEN_CONTENT_URI_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
         REGISTER_UID_OBSERVER_TRANSACTION,
         UNREGISTER_UID_OBSERVER_TRANSACTION,
         IS_UID_ACTIVE_TRANSACTION,
-        GET_UID_PROCESS_STATE_TRANSACTION
+        GET_UID_PROCESS_STATE_TRANSACTION,
+        CHECK_PERMISSION_TRANSACTION,
     };
 };
 
diff --git a/libs/binder/ndk/Android.bp b/libs/binder/ndk/Android.bp
index e4d86ae..cecc759 100644
--- a/libs/binder/ndk/Android.bp
+++ b/libs/binder/ndk/Android.bp
@@ -26,6 +26,9 @@
                 "-D__ANDROID_API__=10000",
             ],
         },
+        darwin: {
+            enabled: false,
+        },
     },
 }
 
@@ -84,6 +87,9 @@
         linux: {
             version_script: "libbinder_ndk.map.txt",
         },
+        darwin: {
+            enabled: false,
+        },
     },
     stubs: {
         symbol_file: "libbinder_ndk.map.txt",
diff --git a/libs/binder/ndk/ibinder.cpp b/libs/binder/ndk/ibinder.cpp
index 4610ba9..d35debc 100644
--- a/libs/binder/ndk/ibinder.cpp
+++ b/libs/binder/ndk/ibinder.cpp
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-#include <android/binder_context.h>
 #include <android/binder_ibinder.h>
 #include <android/binder_ibinder_platform.h>
+#include <android/binder_libbinder.h>
 #include "ibinder_internal.h"
 
 #include <android/binder_stability.h>
diff --git a/libs/binder/ndk/include_cpp/android/binder_auto_utils.h b/libs/binder/ndk/include_cpp/android/binder_auto_utils.h
index 18877af..8d60226 100644
--- a/libs/binder/ndk/include_cpp/android/binder_auto_utils.h
+++ b/libs/binder/ndk/include_cpp/android/binder_auto_utils.h
@@ -313,7 +313,8 @@
     /**
      * Takes ownership of a.
      */
-    explicit ScopedFileDescriptor(int a = -1) : ScopedAResource(a) {}
+    ScopedFileDescriptor() : ScopedFileDescriptor(-1) {}
+    explicit ScopedFileDescriptor(int a) : ScopedAResource(a) {}
     ~ScopedFileDescriptor() {}
     ScopedFileDescriptor(ScopedFileDescriptor&&) = default;
     ScopedFileDescriptor& operator=(ScopedFileDescriptor&&) = default;
diff --git a/libs/binder/ndk/include_platform/android/binder_context.h b/libs/binder/ndk/include_platform/android/binder_context.h
deleted file mode 100644
index a99d555..0000000
--- a/libs/binder/ndk/include_platform/android/binder_context.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <android/binder_ibinder.h>
-
-__BEGIN_DECLS
-
-/**
- * Makes calls to AIBinder_getCallingSid work if the kernel supports it. This
- * must be called on a local binder server before it is sent out to any othe
- * process. If this is a remote binder, it will abort. If the kernel doesn't
- * support this feature, you'll always get null from AIBinder_getCallingSid.
- *
- * \param binder local server binder to request security contexts on
- */
-__attribute__((weak)) void AIBinder_setRequestingSid(AIBinder* binder, bool requestingSid)
-        __INTRODUCED_IN(31);
-
-/**
- * Returns the selinux context of the callee.
- *
- * In order for this to work, the following conditions must be met:
- * - The kernel must be new enough to support this feature.
- * - The server must have called AIBinder_setRequestingSid.
- * - The callee must be a remote process.
- *
- * \return security context or null if unavailable. The lifetime of this context
- * is the lifetime of the transaction.
- */
-__attribute__((weak, warn_unused_result)) const char* AIBinder_getCallingSid() __INTRODUCED_IN(31);
-
-__END_DECLS
diff --git a/libs/binder/ndk/include_platform/android/binder_ibinder_platform.h b/libs/binder/ndk/include_platform/android/binder_ibinder_platform.h
index 2af65cf..a99d555 100644
--- a/libs/binder/ndk/include_platform/android/binder_ibinder_platform.h
+++ b/libs/binder/ndk/include_platform/android/binder_ibinder_platform.h
@@ -16,39 +16,32 @@
 
 #pragma once
 
-// binder_context.h used to be part of this header and is included for backwards
-// compatibility.
-#include <android/binder_context.h>
-
-#if !defined(__ANDROID_APEX__) && !defined(__ANDROID_VNDK__)
-
 #include <android/binder_ibinder.h>
-#include <binder/IBinder.h>
+
+__BEGIN_DECLS
 
 /**
- * Get libbinder version of binder from AIBinder.
+ * Makes calls to AIBinder_getCallingSid work if the kernel supports it. This
+ * must be called on a local binder server before it is sent out to any othe
+ * process. If this is a remote binder, it will abort. If the kernel doesn't
+ * support this feature, you'll always get null from AIBinder_getCallingSid.
  *
- * WARNING: function calls to a local object on the other side of this function
- * will parcel. When converting between binders, keep in mind it is not as
- * efficient as a direct function call.
- *
- * \param binder binder with ownership retained by the client
- * \return platform binder object
+ * \param binder local server binder to request security contexts on
  */
-android::sp<android::IBinder> AIBinder_toPlatformBinder(AIBinder* binder);
+__attribute__((weak)) void AIBinder_setRequestingSid(AIBinder* binder, bool requestingSid)
+        __INTRODUCED_IN(31);
 
 /**
- * Get libbinder_ndk version of binder from platform binder.
+ * Returns the selinux context of the callee.
  *
- * WARNING: function calls to a local object on the other side of this function
- * will parcel. When converting between binders, keep in mind it is not as
- * efficient as a direct function call.
+ * In order for this to work, the following conditions must be met:
+ * - The kernel must be new enough to support this feature.
+ * - The server must have called AIBinder_setRequestingSid.
+ * - The callee must be a remote process.
  *
- * \param binder platform binder which may be from anywhere (doesn't have to be
- * created with libbinder_ndK)
- * \return binder with one reference count of ownership given to the client. See
- * AIBinder_decStrong
+ * \return security context or null if unavailable. The lifetime of this context
+ * is the lifetime of the transaction.
  */
-AIBinder* AIBinder_fromPlatformBinder(const android::sp<android::IBinder>& binder);
+__attribute__((weak, warn_unused_result)) const char* AIBinder_getCallingSid() __INTRODUCED_IN(31);
 
-#endif
+__END_DECLS
diff --git a/libs/binder/ndk/include_platform/android/binder_libbinder.h b/libs/binder/ndk/include_platform/android/binder_libbinder.h
new file mode 100644
index 0000000..f0c00e8
--- /dev/null
+++ b/libs/binder/ndk/include_platform/android/binder_libbinder.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 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
+
+#if !defined(__ANDROID_APEX__) && !defined(__ANDROID_VNDK__)
+
+#include <android/binder_ibinder.h>
+#include <binder/IBinder.h>
+
+/**
+ * Get libbinder version of binder from AIBinder.
+ *
+ * WARNING: function calls to a local object on the other side of this function
+ * will parcel. When converting between binders, keep in mind it is not as
+ * efficient as a direct function call.
+ *
+ * \param binder binder with ownership retained by the client
+ * \return platform binder object
+ */
+android::sp<android::IBinder> AIBinder_toPlatformBinder(AIBinder* binder);
+
+/**
+ * Get libbinder_ndk version of binder from platform binder.
+ *
+ * WARNING: function calls to a local object on the other side of this function
+ * will parcel. When converting between binders, keep in mind it is not as
+ * efficient as a direct function call.
+ *
+ * \param binder platform binder which may be from anywhere (doesn't have to be
+ * created with libbinder_ndK)
+ * \return binder with one reference count of ownership given to the client. See
+ * AIBinder_decStrong
+ */
+AIBinder* AIBinder_fromPlatformBinder(const android::sp<android::IBinder>& binder);
+
+#endif
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index 160b9f2..f84d9d3 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -18,9 +18,9 @@
 #include <aidl/BnBinderNdkUnitTest.h>
 #include <aidl/BnEmpty.h>
 #include <android-base/logging.h>
-#include <android/binder_context.h>
 #include <android/binder_ibinder_jni.h>
 #include <android/binder_ibinder_platform.h>
+#include <android/binder_libbinder.h>
 #include <android/binder_manager.h>
 #include <android/binder_process.h>
 #include <gtest/gtest.h>
diff --git a/libs/binder/parcel_fuzzer/Android.bp b/libs/binder/parcel_fuzzer/Android.bp
index c5b3d80..3e6fe99 100644
--- a/libs/binder/parcel_fuzzer/Android.bp
+++ b/libs/binder/parcel_fuzzer/Android.bp
@@ -52,6 +52,11 @@
 cc_library_static {
     name: "libbinder_random_parcel",
     host_supported: true,
+    target: {
+        darwin: {
+            enabled: false,
+        }
+    },
     srcs: [
         "random_fd.cpp",
         "random_parcel.cpp",
diff --git a/libs/binder/rust/Android.bp b/libs/binder/rust/Android.bp
index dc8270e..fd5f2f5 100644
--- a/libs/binder/rust/Android.bp
+++ b/libs/binder/rust/Android.bp
@@ -10,6 +10,11 @@
         "libbinder_ndk_sys",
     ],
     host_supported: true,
+    target: {
+        darwin: {
+            enabled: false,
+        }
+    }
 }
 
 rust_library {
@@ -23,6 +28,11 @@
         "libbinder_ndk",
     ],
     host_supported: true,
+    target: {
+        darwin: {
+            enabled: false,
+        }
+    }
 }
 
 rust_bindgen {
@@ -64,6 +74,9 @@
                 "-D__ANDROID_API__=10000",
             ],
         },
+        darwin: {
+            enabled: false,
+        },
     },
 }
 
diff --git a/libs/binder/rust/sys/BinderBindings.hpp b/libs/binder/rust/sys/BinderBindings.hpp
index 303f4a5..3f20a4f 100644
--- a/libs/binder/rust/sys/BinderBindings.hpp
+++ b/libs/binder/rust/sys/BinderBindings.hpp
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#include <android/binder_context.h>
 #include <android/binder_ibinder.h>
+#include <android/binder_ibinder_platform.h>
 #include <android/binder_manager.h>
 #include <android/binder_parcel.h>
 #include <android/binder_process.h>
diff --git a/libs/binder/tests/fuzzers/Android.bp b/libs/binder/tests/fuzzers/Android.bp
index 46379fc..c465bed 100644
--- a/libs/binder/tests/fuzzers/Android.bp
+++ b/libs/binder/tests/fuzzers/Android.bp
@@ -26,6 +26,11 @@
         "libutils",
         "libbase",
     ],
+    target: {
+        darwin: {
+            enabled: false,
+        }
+    }
 }
 
 cc_fuzz {
diff --git a/libs/binderthreadstate/Android.bp b/libs/binderthreadstate/Android.bp
index 88752ee..08c62df 100644
--- a/libs/binderthreadstate/Android.bp
+++ b/libs/binderthreadstate/Android.bp
@@ -19,6 +19,11 @@
     double_loadable: true,
     vendor_available: true,
     host_supported: true,
+    target: {
+        darwin: {
+            enabled: false,
+        }
+    },
 
     shared_libs: [
         "libbinder",
diff --git a/libs/fakeservicemanager/Android.bp b/libs/fakeservicemanager/Android.bp
index 6909637..76518c1 100644
--- a/libs/fakeservicemanager/Android.bp
+++ b/libs/fakeservicemanager/Android.bp
@@ -9,6 +9,11 @@
         "libbinder",
         "libutils",
     ],
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
 }
 
 cc_library {
diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp
index e11be57..fb32382 100644
--- a/libs/ftl/Android.bp
+++ b/libs/ftl/Android.bp
@@ -5,6 +5,7 @@
         address: true,
     },
     srcs: [
+        "SmallVector_test.cpp",
         "StaticVector_test.cpp",
     ],
     cflags: [
diff --git a/libs/ftl/SmallVector_test.cpp b/libs/ftl/SmallVector_test.cpp
new file mode 100644
index 0000000..c41e622
--- /dev/null
+++ b/libs/ftl/SmallVector_test.cpp
@@ -0,0 +1,455 @@
+/*
+ * Copyright 2020 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/SmallVector.h>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <iterator>
+#include <string>
+#include <utility>
+
+using namespace std::string_literals;
+
+namespace android::test {
+
+using ftl::SmallVector;
+
+// Keep in sync with example usage in header file.
+TEST(SmallVector, Example) {
+    ftl::SmallVector<char, 3> vector;
+    EXPECT_TRUE(vector.empty());
+    EXPECT_FALSE(vector.dynamic());
+
+    vector = {'a', 'b', 'c'};
+    EXPECT_EQ(vector.size(), 3u);
+    EXPECT_FALSE(vector.dynamic());
+
+    vector.push_back('d');
+    EXPECT_TRUE(vector.dynamic());
+
+    vector.unstable_erase(vector.begin());
+    EXPECT_EQ(vector, (ftl::SmallVector{'d', 'b', 'c'}));
+
+    vector.pop_back();
+    EXPECT_EQ(vector.back(), 'b');
+    EXPECT_TRUE(vector.dynamic());
+
+    const char array[] = "hi";
+    vector = ftl::SmallVector(array);
+    EXPECT_EQ(vector, (ftl::SmallVector{'h', 'i', '\0'}));
+    EXPECT_FALSE(vector.dynamic());
+}
+
+TEST(SmallVector, Construct) {
+    {
+        // Default constructor.
+        SmallVector<std::string, 2> vector;
+
+        EXPECT_TRUE(vector.empty());
+        EXPECT_FALSE(vector.dynamic());
+    }
+    {
+        // Array constructor.
+        const float kFloats[] = {.1f, .2f, .3f};
+        SmallVector vector(kFloats);
+
+        EXPECT_EQ(vector, (SmallVector{.1f, .2f, .3f}));
+        EXPECT_FALSE(vector.dynamic());
+    }
+    {
+        // Iterator constructor.
+        const char chars[] = "abcdef";
+        std::string string(chars);
+        SmallVector<char, sizeof(chars)> vector(string.begin(), string.end());
+
+        EXPECT_STREQ(vector.begin(), chars);
+        EXPECT_FALSE(vector.dynamic());
+    }
+    {
+        // Variadic constructor with same types.
+        SmallVector vector = {1, 2, 3};
+
+        static_assert(std::is_same_v<decltype(vector), SmallVector<int, 3>>);
+        EXPECT_EQ(vector, (SmallVector{1, 2, 3}));
+        EXPECT_FALSE(vector.dynamic());
+    }
+    {
+        // Variadic constructor with different types.
+        const auto copy = "quince"s;
+        auto move = "tart"s;
+        SmallVector vector = {copy, std::move(move)};
+
+        static_assert(std::is_same_v<decltype(vector), SmallVector<std::string, 2>>);
+        EXPECT_EQ(vector, (SmallVector{"quince"s, "tart"s}));
+        EXPECT_FALSE(vector.dynamic());
+    }
+    {
+        // In-place constructor with same types.
+        SmallVector vector(std::in_place_type<std::string>, "red", "velvet", "cake");
+
+        static_assert(std::is_same_v<decltype(vector), SmallVector<std::string, 3>>);
+        EXPECT_EQ(vector, (SmallVector{"red"s, "velvet"s, "cake"s}));
+        EXPECT_FALSE(vector.dynamic());
+    }
+    {
+        // In-place constructor with different types.
+        const auto copy = "red"s;
+        auto move = "velvet"s;
+        std::initializer_list<char> list = {'c', 'a', 'k', 'e'};
+        SmallVector vector(std::in_place_type<std::string>, copy.c_str(), std::move(move), list);
+
+        static_assert(std::is_same_v<decltype(vector), SmallVector<std::string, 3>>);
+        EXPECT_EQ(vector, (SmallVector{"red"s, "velvet"s, "cake"s}));
+        EXPECT_FALSE(vector.dynamic());
+    }
+    {
+        // Conversion from StaticVector.
+        ftl::StaticVector doubles = {.1, .2, .3};
+        SmallVector vector = std::move(doubles);
+        EXPECT_TRUE(doubles.empty());
+
+        static_assert(std::is_same_v<decltype(vector), SmallVector<double, 3>>);
+        EXPECT_EQ(vector, (SmallVector{.1, .2, .3}));
+        EXPECT_FALSE(vector.dynamic());
+    }
+}
+
+TEST(SmallVector, String) {
+    SmallVector<char, 10> chars;
+    char c = 'a';
+    std::generate_n(std::back_inserter(chars), chars.max_size(), [&c] { return c++; });
+    chars.push_back('\0');
+
+    EXPECT_TRUE(chars.dynamic());
+    EXPECT_EQ(chars.size(), 11u);
+    EXPECT_STREQ(chars.begin(), "abcdefghij");
+
+    // Constructor takes iterator range.
+    const char kString[] = "123456";
+    SmallVector<char, 10> string(std::begin(kString), std::end(kString));
+
+    EXPECT_FALSE(string.dynamic());
+    EXPECT_STREQ(string.begin(), "123456");
+    EXPECT_EQ(string.size(), 7u);
+
+    // Similar to emplace, but replaces rather than inserts.
+    string.replace(string.begin() + 5, '\0');
+    EXPECT_STREQ(string.begin(), "12345");
+
+    swap(chars, string);
+
+    EXPECT_STREQ(chars.begin(), "12345");
+    EXPECT_STREQ(string.begin(), "abcdefghij");
+
+    EXPECT_FALSE(chars.dynamic());
+    EXPECT_TRUE(string.dynamic());
+}
+
+TEST(SmallVector, CopyableElement) {
+    struct Pair {
+        // Needed because std::vector emplace does not use uniform initialization.
+        Pair(int a, int b) : a(a), b(b) {}
+
+        const int a, b;
+        bool operator==(Pair p) const { return p.a == a && p.b == b; }
+    };
+
+    SmallVector<Pair, 5> pairs;
+
+    EXPECT_TRUE(pairs.empty());
+    EXPECT_EQ(pairs.max_size(), 5u);
+
+    for (size_t i = 0; i < pairs.max_size(); ++i) {
+        EXPECT_EQ(pairs.size(), i);
+
+        const int a = static_cast<int>(i) * 2;
+        EXPECT_EQ(pairs.emplace_back(a, a + 1), Pair(a, a + 1));
+    }
+
+    EXPECT_EQ(pairs.size(), 5u);
+    EXPECT_FALSE(pairs.dynamic());
+
+    // The vector is promoted when full.
+    EXPECT_EQ(pairs.emplace_back(10, 11), Pair(10, 11));
+    EXPECT_TRUE(pairs.dynamic());
+
+    EXPECT_EQ(pairs,
+              (SmallVector{Pair{0, 1}, Pair{2, 3}, Pair{4, 5}, Pair{6, 7}, Pair{8, 9},
+                           Pair{10, 11}}));
+
+    // Constructor takes at most N elements.
+    SmallVector<int, 6> sums = {0, 0, 0, 0, 0, 0};
+    EXPECT_FALSE(sums.dynamic());
+
+    // Random-access iterators comply with standard.
+    std::transform(pairs.begin(), pairs.end(), sums.begin(), [](Pair p) { return p.a + p.b; });
+    EXPECT_EQ(sums, (SmallVector{1, 5, 9, 13, 17, 21}));
+
+    sums.pop_back();
+    std::reverse(sums.begin(), sums.end());
+
+    EXPECT_EQ(sums, (SmallVector{17, 13, 9, 5, 1}));
+}
+
+TEST(SmallVector, MovableElement) {
+    // Construct std::string elements in-place from C-style strings. Without std::in_place_type, the
+    // element type would be deduced from the first element, i.e. const char*.
+    SmallVector strings(std::in_place_type<std::string>, "", "", "", "cake", "velvet", "red", "");
+    strings.pop_back();
+
+    EXPECT_EQ(strings.max_size(), 7u);
+    EXPECT_EQ(strings.size(), 6u);
+
+    // Erase "cake" and append a substring copy.
+    {
+        const auto it = std::find_if(strings.begin(), strings.end(),
+                                     [](const auto& s) { return !s.empty(); });
+        ASSERT_FALSE(it == strings.end());
+        EXPECT_EQ(*it, "cake");
+
+        strings.unstable_erase(it);
+        EXPECT_EQ(strings.emplace_back("cakewalk", 4u), "cake"s);
+    }
+
+    strings[1] = "quince"s;
+
+    // Replace last empty string with "tart".
+    {
+        const auto rit = std::find(strings.rbegin(), strings.rend(), std::string());
+        ASSERT_FALSE(rit == strings.rend());
+
+        std::initializer_list<char> list = {'t', 'a', 'r', 't'};
+        strings.replace(rit.base() - 1, list);
+    }
+
+    strings.front().assign("pie");
+
+    EXPECT_EQ(strings, (SmallVector{"pie"s, "quince"s, "tart"s, "red"s, "velvet"s, "cake"s}));
+
+    strings.push_back("nougat");
+    strings.push_back("oreo");
+    EXPECT_TRUE(strings.dynamic());
+
+    std::rotate(strings.begin(), strings.end() - 2, strings.end());
+
+    EXPECT_EQ(strings,
+              (SmallVector{"nougat"s, "oreo"s, "pie"s, "quince"s, "tart"s, "red"s, "velvet"s,
+                           "cake"s}));
+}
+
+TEST(SmallVector, Replace) {
+    // Replacing does not require a copy/move assignment operator.
+    struct Word {
+        explicit Word(std::string str) : str(std::move(str)) {}
+        const std::string str;
+
+        bool operator==(const Word& other) const { return other.str == str; }
+    };
+
+    SmallVector words(std::in_place_type<Word>, "colored", "velour");
+
+    // The replaced element can be referenced by the replacement.
+    {
+        const Word& word = words.replace(words.last(), words.back().str.substr(0, 3) + "vet");
+        EXPECT_EQ(word, Word("velvet"));
+    }
+
+    // The vector is not promoted if replacing while full.
+    EXPECT_FALSE(words.dynamic());
+
+    words.emplace_back("cake");
+    EXPECT_TRUE(words.dynamic());
+
+    {
+        const Word& word = words.replace(words.begin(), words.front().str.substr(4));
+        EXPECT_EQ(word, Word("red"));
+    }
+
+    EXPECT_EQ(words, (SmallVector{Word("red"), Word("velvet"), Word("cake")}));
+}
+
+TEST(SmallVector, ReverseAppend) {
+    SmallVector strings = {"red"s, "velvet"s, "cake"s};
+    EXPECT_FALSE(strings.dynamic());
+
+    auto rit = strings.rbegin();
+    while (rit != strings.rend()) {
+        // Iterator and reference are invalidated on insertion.
+        const auto i = std::distance(strings.begin(), rit.base());
+        std::string s = *rit;
+
+        strings.push_back(std::move(s));
+        rit = std::make_reverse_iterator(strings.begin() + i) + 1;
+    }
+
+    EXPECT_EQ(strings, (SmallVector{"red"s, "velvet"s, "cake"s, "cake"s, "velvet"s, "red"s}));
+    EXPECT_TRUE(strings.dynamic());
+}
+
+TEST(SmallVector, Sort) {
+    SmallVector strings(std::in_place_type<std::string>, "pie", "quince", "tart", "red", "velvet");
+    strings.push_back("cake"s);
+
+    auto sorted = std::move(strings);
+    EXPECT_TRUE(strings.empty());
+
+    EXPECT_TRUE(sorted.dynamic());
+    EXPECT_TRUE(strings.dynamic());
+
+    std::sort(sorted.begin(), sorted.end());
+    EXPECT_EQ(sorted, (SmallVector{"cake"s, "pie"s, "quince"s, "red"s, "tart"s, "velvet"s}));
+
+    // Constructor takes array reference.
+    {
+        const char* kStrings[] = {"cake", "lie"};
+        strings = SmallVector(kStrings);
+        EXPECT_FALSE(strings.dynamic());
+    }
+
+    EXPECT_GT(sorted, strings);
+    swap(sorted, strings);
+    EXPECT_LT(sorted, strings);
+
+    EXPECT_FALSE(sorted.dynamic());
+    EXPECT_TRUE(strings.dynamic());
+
+    // Append remaining elements, such that "pie" is the only difference.
+    for (const char* str : {"quince", "red", "tart", "velvet"}) {
+        sorted.emplace_back(str);
+    }
+    EXPECT_TRUE(sorted.dynamic());
+
+    EXPECT_NE(sorted, strings);
+
+    // Replace second element with "pie".
+    const auto it = sorted.begin() + 1;
+    EXPECT_EQ(sorted.replace(it, 'p' + it->substr(1)), "pie");
+
+    EXPECT_EQ(sorted, strings);
+}
+
+namespace {
+
+struct DestroyCounts {
+    DestroyCounts(int& live, int& dead) : counts{live, dead} {}
+    DestroyCounts(const DestroyCounts& other) : counts(other.counts) {}
+    DestroyCounts(DestroyCounts&& other) : counts(other.counts) { other.alive = false; }
+    ~DestroyCounts() { ++(alive ? counts.live : counts.dead); }
+
+    struct {
+        int& live;
+        int& dead;
+    } counts;
+
+    bool alive = true;
+};
+
+void swap(DestroyCounts& lhs, DestroyCounts& rhs) {
+    std::swap(lhs.alive, rhs.alive);
+}
+
+} // namespace
+
+TEST(SmallVector, Destroy) {
+    int live = 0;
+    int dead = 0;
+
+    { SmallVector<DestroyCounts, 3> counts; }
+    EXPECT_EQ(0, live);
+    EXPECT_EQ(0, dead);
+
+    {
+        SmallVector<DestroyCounts, 3> counts;
+        counts.emplace_back(live, dead);
+        counts.emplace_back(live, dead);
+        counts.emplace_back(live, dead);
+
+        EXPECT_FALSE(counts.dynamic());
+    }
+    EXPECT_EQ(3, live);
+    EXPECT_EQ(0, dead);
+
+    live = 0;
+    {
+        SmallVector<DestroyCounts, 3> counts;
+        counts.emplace_back(live, dead);
+        counts.emplace_back(live, dead);
+        counts.emplace_back(live, dead);
+        counts.emplace_back(live, dead);
+
+        EXPECT_TRUE(counts.dynamic());
+    }
+    EXPECT_EQ(4, live);
+    EXPECT_EQ(3, dead);
+
+    live = dead = 0;
+    {
+        SmallVector<DestroyCounts, 2> counts;
+        counts.emplace_back(live, dead);
+        counts.emplace_back(live, dead);
+        counts.emplace_back(live, dead);
+
+        auto copy = counts;
+        EXPECT_TRUE(copy.dynamic());
+    }
+    EXPECT_EQ(6, live);
+    EXPECT_EQ(2, dead);
+
+    live = dead = 0;
+    {
+        SmallVector<DestroyCounts, 2> counts;
+        counts.emplace_back(live, dead);
+        counts.emplace_back(live, dead);
+        counts.emplace_back(live, dead);
+
+        auto move = std::move(counts);
+        EXPECT_TRUE(move.dynamic());
+    }
+    EXPECT_EQ(3, live);
+    EXPECT_EQ(2, dead);
+
+    live = dead = 0;
+    {
+        SmallVector<DestroyCounts, 2> counts1;
+        counts1.emplace_back(live, dead);
+        counts1.emplace_back(live, dead);
+        counts1.emplace_back(live, dead);
+
+        EXPECT_TRUE(counts1.dynamic());
+        EXPECT_EQ(2, dead);
+        dead = 0;
+
+        SmallVector<DestroyCounts, 2> counts2;
+        counts2.emplace_back(live, dead);
+
+        EXPECT_FALSE(counts2.dynamic());
+
+        swap(counts1, counts2);
+
+        EXPECT_FALSE(counts1.dynamic());
+        EXPECT_TRUE(counts2.dynamic());
+
+        EXPECT_EQ(0, live);
+        EXPECT_EQ(1, dead);
+
+        dead = 0;
+    }
+    EXPECT_EQ(4, live);
+    EXPECT_EQ(0, dead);
+}
+
+} // namespace android::test
diff --git a/libs/ftl/StaticVector_test.cpp b/libs/ftl/StaticVector_test.cpp
index 06e4659..dd5ce35 100644
--- a/libs/ftl/StaticVector_test.cpp
+++ b/libs/ftl/StaticVector_test.cpp
@@ -106,6 +106,33 @@
         static_assert(std::is_same_v<decltype(vector), StaticVector<std::string, 3>>);
         EXPECT_EQ(vector, (StaticVector{"red"s, "velvet"s, "cake"s}));
     }
+    {
+        struct String {
+            explicit String(const char* str) : str(str) {}
+            explicit String(const char** ptr) : str(*ptr) {}
+            const char* str;
+        };
+
+        const char* kStrings[] = {"a", "b", "c", "d"};
+
+        {
+            // Two iterator-like elements.
+            StaticVector<String, 3> vector(kStrings, kStrings + 3);
+            ASSERT_EQ(vector.size(), 2u);
+
+            EXPECT_STREQ(vector[0].str, "a");
+            EXPECT_STREQ(vector[1].str, "d");
+        }
+        {
+            // Disambiguating iterator constructor.
+            StaticVector<String, 3> vector(ftl::IteratorRange, kStrings, kStrings + 3);
+            ASSERT_EQ(vector.size(), 3u);
+
+            EXPECT_STREQ(vector[0].str, "a");
+            EXPECT_STREQ(vector[1].str, "b");
+            EXPECT_STREQ(vector[2].str, "c");
+        }
+    }
 }
 
 TEST(StaticVector, String) {
@@ -124,8 +151,7 @@
     EXPECT_EQ(string.size(), 7u);
 
     // Similar to emplace, but replaces rather than inserts.
-    const auto it = string.replace(string.begin() + 5, '\0');
-    EXPECT_NE(it, string.end());
+    string.replace(string.begin() + 5, '\0');
     EXPECT_STREQ(string.begin(), "12345");
 
     swap(chars, string);
@@ -209,8 +235,7 @@
         ASSERT_FALSE(rit == strings.rend());
 
         std::initializer_list<char> list = {'t', 'a', 'r', 't'};
-        const auto it = strings.replace(rit.base() - 1, list);
-        EXPECT_NE(it, strings.end());
+        strings.replace(rit.base() - 1, list);
     }
 
     strings.front().assign("pie");
@@ -218,6 +243,21 @@
     EXPECT_EQ(strings, (StaticVector{"pie"s, "quince"s, "tart"s, "red"s, "velvet"s, "cake"s}));
 }
 
+TEST(StaticVector, Replace) {
+    // Replacing does not require a copy/move assignment operator.
+    struct Word {
+        explicit Word(std::string str) : str(std::move(str)) {}
+        const std::string str;
+    };
+
+    StaticVector words(std::in_place_type<Word>, "red", "velour", "cake");
+
+    // The replaced element can be referenced by the replacement.
+    const auto it = words.begin() + 1;
+    const Word& word = words.replace(it, it->str.substr(0, 3) + "vet");
+    EXPECT_EQ(word.str, "velvet");
+}
+
 TEST(StaticVector, ReverseTruncate) {
     StaticVector<std::string, 10> strings("pie", "quince", "tart", "red", "velvet", "cake");
     EXPECT_FALSE(strings.full());
@@ -251,12 +291,16 @@
     EXPECT_LT(sorted, strings);
 
     // Append remaining elements, such that "pie" is the only difference.
-    sorted.replace(sorted.end(), "quince");
-    for (const char* str : {"red", "tart", "velvet"}) sorted.emplace_back(str);
+    for (const char* str : {"quince", "red", "tart", "velvet"}) {
+        sorted.emplace_back(str);
+    }
 
     EXPECT_NE(sorted, strings);
 
-    sorted.replace(sorted.begin() + 1, "pie");
+    // Replace second element with "pie".
+    const auto it = sorted.begin() + 1;
+    EXPECT_EQ(sorted.replace(it, 'p' + it->substr(1)), "pie");
+
     EXPECT_EQ(sorted, strings);
 }
 
diff --git a/libs/gralloc/types/Android.bp b/libs/gralloc/types/Android.bp
index cc31cd5..243d7f1 100644
--- a/libs/gralloc/types/Android.bp
+++ b/libs/gralloc/types/Android.bp
@@ -21,6 +21,11 @@
         "-Wno-enum-compare",
     ],
     host_supported: true,
+    target: {
+        darwin: {
+            enabled: false,
+        }
+    },
 
     vendor_available: true,
     vndk: {
diff --git a/libs/gralloc/types/fuzzer/Android.bp b/libs/gralloc/types/fuzzer/Android.bp
index 8444883..8933dc3 100644
--- a/libs/gralloc/types/fuzzer/Android.bp
+++ b/libs/gralloc/types/fuzzer/Android.bp
@@ -2,6 +2,11 @@
     name: "libgralloctypes_fuzzer",
     defaults: ["libbinder_ndk_host_user"],
     host_supported: true,
+    target: {
+        darwin: {
+            enabled: false,
+        }
+    },
 
     fuzz_config: {
         cc: ["marissaw@google.com"],
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index ff64d65..678613b 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -159,7 +159,7 @@
     if (context == nullptr) {
         return;
     }
-    BLASTBufferQueue* bq = static_cast<BLASTBufferQueue*>(context);
+    sp<BLASTBufferQueue> bq = static_cast<BLASTBufferQueue*>(context);
     bq->transactionCallback(latchTime, presentFence, stats);
 }
 
diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp
index 9299721..f3559fa 100644
--- a/libs/gui/tests/BLASTBufferQueue_test.cpp
+++ b/libs/gui/tests/BLASTBufferQueue_test.cpp
@@ -123,6 +123,7 @@
                 .apply();
 
         mCaptureArgs.displayToken = mDisplayToken;
+        mCaptureArgs.dataspace = ui::Dataspace::V0_SRGB;
     }
 
     void setUpProducer(BLASTBufferQueueHelper adapter, sp<IGraphicBufferProducer>& producer) {
@@ -181,6 +182,7 @@
         for (uint32_t row = 0; row < height; row++) {
             for (uint32_t col = 0; col < width; col++) {
                 uint8_t* pixel = (uint8_t*)(bufData + (row * stride) + col);
+                ASSERT_NE(nullptr, pixel);
                 bool inRegion;
                 if (!outsideRegion) {
                     inRegion = row >= region.top + border && row < region.bottom - border &&
diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp
index 483f171..c39b0b5 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -153,6 +153,24 @@
         EXPECT_EQ(0, mev->getFlags() & VERIFIED_MOTION_EVENT_FLAGS);
     }
 
+    void expectTapWithFlag(int x, int y, int32_t flags) {
+        InputEvent *ev = consumeEvent();
+        ASSERT_NE(ev, nullptr);
+        ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, ev->getType());
+        MotionEvent *mev = static_cast<MotionEvent *>(ev);
+        EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, mev->getAction());
+        EXPECT_EQ(x, mev->getX(0));
+        EXPECT_EQ(y, mev->getY(0));
+        EXPECT_EQ(flags, mev->getFlags() & flags);
+
+        ev = consumeEvent();
+        ASSERT_NE(ev, nullptr);
+        ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, ev->getType());
+        mev = static_cast<MotionEvent *>(ev);
+        EXPECT_EQ(AMOTION_EVENT_ACTION_UP, mev->getAction());
+        EXPECT_EQ(flags, mev->getFlags() & flags);
+    }
+
     ~InputSurface() { mInputFlinger->removeInputChannel(mClientChannel->getConnectionToken()); }
 
     void doTransaction(std::function<void(SurfaceComposerClient::Transaction&,
@@ -602,4 +620,68 @@
     surface->expectTap(5, 10);
 }
 
+TEST_F(InputSurfacesTest, touch_flag_obscured) {
+    std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
+    surface->showAt(100, 100);
+
+    // Add non touchable window to fully cover touchable window. Window behind gets touch, but
+    // with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED
+    std::unique_ptr<InputSurface> nonTouchableSurface = makeSurface(100, 100);
+    nonTouchableSurface->mInputInfo.flags = InputWindowInfo::Flag::NOT_TOUCHABLE;
+    nonTouchableSurface->mInputInfo.ownerUid = 22222;
+    nonTouchableSurface->showAt(100, 100);
+
+    injectTap(190, 199);
+    surface->expectTapWithFlag(90, 99, AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED);
+}
+
+TEST_F(InputSurfacesTest, touch_flag_partially_obscured_with_crop) {
+    std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
+    surface->showAt(100, 100);
+
+    // Add non touchable window to cover touchable window, but parent is cropped to not cover area
+    // that will be tapped. Window behind gets touch, but with flag
+    // AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED
+    std::unique_ptr<InputSurface> parentSurface = makeSurface(100, 100);
+    std::unique_ptr<InputSurface> nonTouchableSurface = makeSurface(100, 100);
+    nonTouchableSurface->mInputInfo.flags = InputWindowInfo::Flag::NOT_TOUCHABLE;
+    parentSurface->mInputInfo.flags = InputWindowInfo::Flag::NOT_TOUCHABLE;
+    nonTouchableSurface->mInputInfo.ownerUid = 22222;
+    parentSurface->mInputInfo.ownerUid = 22222;
+    nonTouchableSurface->showAt(0, 0);
+    parentSurface->showAt(100, 100);
+
+    nonTouchableSurface->doTransaction([&](auto &t, auto &sc) {
+        t.setCrop_legacy(parentSurface->mSurfaceControl, Rect(0, 0, 50, 50));
+        t.reparent(sc, parentSurface->mSurfaceControl);
+    });
+
+    injectTap(190, 199);
+    surface->expectTapWithFlag(90, 99, AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
+}
+
+TEST_F(InputSurfacesTest, touch_not_obscured_with_crop) {
+    std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
+    surface->showAt(100, 100);
+
+    // Add non touchable window to cover touchable window, but parent is cropped to avoid covering
+    // the touchable window. Window behind gets touch with no obscured flags.
+    std::unique_ptr<InputSurface> parentSurface = makeSurface(100, 100);
+    std::unique_ptr<InputSurface> nonTouchableSurface = makeSurface(100, 100);
+    nonTouchableSurface->mInputInfo.flags = InputWindowInfo::Flag::NOT_TOUCHABLE;
+    parentSurface->mInputInfo.flags = InputWindowInfo::Flag::NOT_TOUCHABLE;
+    nonTouchableSurface->mInputInfo.ownerUid = 22222;
+    parentSurface->mInputInfo.ownerUid = 22222;
+    nonTouchableSurface->showAt(0, 0);
+    parentSurface->showAt(50, 50);
+
+    nonTouchableSurface->doTransaction([&](auto &t, auto &sc) {
+        t.setCrop_legacy(parentSurface->mSurfaceControl, Rect(0, 0, 50, 50));
+        t.reparent(sc, parentSurface->mSurfaceControl);
+    });
+
+    injectTap(101, 110);
+    surface->expectTap(1, 10);
+}
+
 } // namespace android::test
diff --git a/libs/gui/view/Surface.cpp b/libs/gui/view/Surface.cpp
index 3e49de6..1bfe462 100644
--- a/libs/gui/view/Surface.cpp
+++ b/libs/gui/view/Surface.cpp
@@ -75,13 +75,9 @@
 }
 
 String16 Surface::readMaybeEmptyString16(const Parcel* parcel) {
-    size_t len;
-    const char16_t* str = parcel->readString16Inplace(&len);
-    if (str != nullptr) {
-        return String16(str, len);
-    } else {
-        return String16();
-    }
+    std::optional<String16> str;
+    parcel->readString16(&str);
+    return str.value_or(String16());
 }
 
 } // namespace view
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index ad7db75..0ea3889 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -28,7 +28,9 @@
 #include <input/InputDevice.h>
 #include <input/InputEventLabels.h>
 
+#ifdef __linux__
 #include <binder/Parcel.h>
+#endif
 #ifdef __ANDROID__
 #include <sys/random.h>
 #endif
@@ -254,6 +256,7 @@
     setAxisValue(AMOTION_EVENT_AXIS_Y, getY() + yOffset);
 }
 
+#ifdef __linux__
 status_t PointerCoords::readFromParcel(Parcel* parcel) {
     bits = parcel->readInt64();
 
@@ -277,6 +280,7 @@
     }
     return OK;
 }
+#endif
 
 void PointerCoords::tooManyAxes(int axis) {
     ALOGW("Could not set value for axis %d because the PointerCoords structure is full and "
@@ -538,6 +542,7 @@
     }
 }
 
+#ifdef __linux__
 static status_t readFromParcel(ui::Transform& transform, const Parcel& parcel) {
     float dsdx, dtdx, tx, dtdy, dsdy, ty;
     status_t status = parcel.readFloat(&dsdx);
@@ -674,6 +679,7 @@
     }
     return OK;
 }
+#endif
 
 bool MotionEvent::isTouchEvent(uint32_t source, int32_t action) {
     if (source & AINPUT_SOURCE_CLASS_POINTER) {
diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp
index 2623ecd..f5432ad 100644
--- a/libs/input/KeyCharacterMap.cpp
+++ b/libs/input/KeyCharacterMap.cpp
@@ -19,7 +19,9 @@
 #include <stdlib.h>
 #include <string.h>
 
+#ifdef __linux__
 #include <binder/Parcel.h>
+#endif
 #include <android/keycodes.h>
 #include <attestation/HmacKeyManager.h>
 #include <input/InputEventLabels.h>
@@ -83,9 +85,7 @@
 
 // --- KeyCharacterMap ---
 
-KeyCharacterMap::KeyCharacterMap() :
-    mType(KEYBOARD_TYPE_UNKNOWN) {
-}
+KeyCharacterMap::KeyCharacterMap() : mType(KeyboardType::UNKNOWN) {}
 
 KeyCharacterMap::KeyCharacterMap(const KeyCharacterMap& other)
       : mType(other.mType),
@@ -184,7 +184,7 @@
     mLoadFileName = overlay.mLoadFileName;
 }
 
-int32_t KeyCharacterMap::getKeyboardType() const {
+KeyCharacterMap::KeyboardType KeyCharacterMap::getKeyboardType() const {
     return mType;
 }
 
@@ -587,13 +587,14 @@
     }
 }
 
+#ifdef __linux__
 std::shared_ptr<KeyCharacterMap> KeyCharacterMap::readFromParcel(Parcel* parcel) {
     if (parcel == nullptr) {
         ALOGE("%s: Null parcel", __func__);
         return nullptr;
     }
     std::shared_ptr<KeyCharacterMap> map = std::shared_ptr<KeyCharacterMap>(new KeyCharacterMap());
-    map->mType = parcel->readInt32();
+    map->mType = static_cast<KeyCharacterMap::KeyboardType>(parcel->readInt32());
     size_t numKeys = parcel->readInt32();
     if (parcel->errorCheck()) {
         return nullptr;
@@ -651,7 +652,7 @@
         ALOGE("%s: Null parcel", __func__);
         return;
     }
-    parcel->writeInt32(mType);
+    parcel->writeInt32(static_cast<int32_t>(mType));
 
     size_t numKeys = mKeys.size();
     parcel->writeInt32(numKeys);
@@ -672,7 +673,7 @@
         parcel->writeInt32(0);
     }
 }
-
+#endif // __linux__
 
 // --- KeyCharacterMap::Key ---
 
@@ -776,20 +777,20 @@
         return BAD_VALUE;
     }
 
-    if (mMap->mType == KEYBOARD_TYPE_UNKNOWN) {
+    if (mMap->mType == KeyboardType::UNKNOWN) {
         ALOGE("%s: Keyboard layout missing required keyboard 'type' declaration.",
                 mTokenizer->getLocation().string());
         return BAD_VALUE;
     }
 
-    if (mFormat == FORMAT_BASE) {
-        if (mMap->mType == KEYBOARD_TYPE_OVERLAY) {
+    if (mFormat == Format::BASE) {
+        if (mMap->mType == KeyboardType::OVERLAY) {
             ALOGE("%s: Base keyboard layout must specify a keyboard 'type' other than 'OVERLAY'.",
                     mTokenizer->getLocation().string());
             return BAD_VALUE;
         }
-    } else if (mFormat == FORMAT_OVERLAY) {
-        if (mMap->mType != KEYBOARD_TYPE_OVERLAY) {
+    } else if (mFormat == Format::OVERLAY) {
+        if (mMap->mType != KeyboardType::OVERLAY) {
             ALOGE("%s: Overlay keyboard layout missing required keyboard "
                     "'type OVERLAY' declaration.",
                     mTokenizer->getLocation().string());
@@ -801,7 +802,7 @@
 }
 
 status_t KeyCharacterMap::Parser::parseType() {
-    if (mMap->mType != KEYBOARD_TYPE_UNKNOWN) {
+    if (mMap->mType != KeyboardType::UNKNOWN) {
         ALOGE("%s: Duplicate keyboard 'type' declaration.",
                 mTokenizer->getLocation().string());
         return BAD_VALUE;
@@ -810,20 +811,20 @@
     KeyboardType type;
     String8 typeToken = mTokenizer->nextToken(WHITESPACE);
     if (typeToken == "NUMERIC") {
-        type = KEYBOARD_TYPE_NUMERIC;
+        type = KeyboardType::NUMERIC;
     } else if (typeToken == "PREDICTIVE") {
-        type = KEYBOARD_TYPE_PREDICTIVE;
+        type = KeyboardType::PREDICTIVE;
     } else if (typeToken == "ALPHA") {
-        type = KEYBOARD_TYPE_ALPHA;
+        type = KeyboardType::ALPHA;
     } else if (typeToken == "FULL") {
-        type = KEYBOARD_TYPE_FULL;
+        type = KeyboardType::FULL;
     } else if (typeToken == "SPECIAL_FUNCTION") {
         ALOGW("The SPECIAL_FUNCTION type is now declared in the device's IDC file, please set "
                 "the property 'keyboard.specialFunction' to '1' there instead.");
         // TODO: return BAD_VALUE here in Q
-        type = KEYBOARD_TYPE_SPECIAL_FUNCTION;
+        type = KeyboardType::SPECIAL_FUNCTION;
     } else if (typeToken == "OVERLAY") {
-        type = KEYBOARD_TYPE_OVERLAY;
+        type = KeyboardType::OVERLAY;
     } else {
         ALOGE("%s: Expected keyboard type label, got '%s'.", mTokenizer->getLocation().string(),
                 typeToken.string());
diff --git a/libs/input/Keyboard.cpp b/libs/input/Keyboard.cpp
index 38a68b3..14dc9e5 100644
--- a/libs/input/Keyboard.cpp
+++ b/libs/input/Keyboard.cpp
@@ -128,7 +128,7 @@
     }
 
     base::Result<std::shared_ptr<KeyCharacterMap>> ret =
-            KeyCharacterMap::load(path, KeyCharacterMap::FORMAT_BASE);
+            KeyCharacterMap::load(path, KeyCharacterMap::Format::BASE);
     if (!ret) {
         return ret.error().code();
     }
@@ -159,9 +159,9 @@
 bool isEligibleBuiltInKeyboard(const InputDeviceIdentifier& deviceIdentifier,
         const PropertyMap* deviceConfiguration, const KeyMap* keyMap) {
     // TODO: remove the third OR statement (SPECIAL_FUNCTION) in Q
-    if (!keyMap->haveKeyCharacterMap() || isKeyboardSpecialFunction(deviceConfiguration)
-            || keyMap->keyCharacterMap->getKeyboardType()
-                    == KeyCharacterMap::KEYBOARD_TYPE_SPECIAL_FUNCTION) {
+    if (!keyMap->haveKeyCharacterMap() || isKeyboardSpecialFunction(deviceConfiguration) ||
+        keyMap->keyCharacterMap->getKeyboardType() ==
+                KeyCharacterMap::KeyboardType::SPECIAL_FUNCTION) {
         return false;
     }
 
diff --git a/libs/renderengine/Description.cpp b/libs/renderengine/Description.cpp
index b9cea10..245c9e1 100644
--- a/libs/renderengine/Description.cpp
+++ b/libs/renderengine/Description.cpp
@@ -52,5 +52,10 @@
     return colorMatrix != identity;
 }
 
+bool Description::hasDisplayColorMatrix() const {
+    const mat4 identity;
+    return displayColorMatrix != identity;
+}
+
 } // namespace renderengine
 } // namespace android
diff --git a/libs/renderengine/gl/GLESRenderEngine.cpp b/libs/renderengine/gl/GLESRenderEngine.cpp
index 13577f7..be83ebc 100644
--- a/libs/renderengine/gl/GLESRenderEngine.cpp
+++ b/libs/renderengine/gl/GLESRenderEngine.cpp
@@ -1121,6 +1121,7 @@
 
     setOutputDataSpace(display.outputDataspace);
     setDisplayMaxLuminance(display.maxLuminance);
+    setDisplayColorTransform(display.colorTransform);
 
     const mat4 projectionMatrix =
             ui::Transform(display.orientation).asMatrix4() * mState.projectionMatrix;
@@ -1189,7 +1190,7 @@
         position[3] = vec2(bounds.right, bounds.top);
 
         setupLayerCropping(*layer, mesh);
-        setColorTransform(display.colorTransform * layer->colorTransform);
+        setColorTransform(layer->colorTransform);
 
         bool usePremultipliedAlpha = true;
         bool disableTexture = true;
@@ -1351,6 +1352,10 @@
     mState.colorMatrix = colorTransform;
 }
 
+void GLESRenderEngine::setDisplayColorTransform(const mat4& colorTransform) {
+    mState.displayColorMatrix = colorTransform;
+}
+
 void GLESRenderEngine::disableTexturing() {
     mState.textureEnabled = false;
 }
diff --git a/libs/renderengine/gl/GLESRenderEngine.h b/libs/renderengine/gl/GLESRenderEngine.h
index 1779994..c0449a1 100644
--- a/libs/renderengine/gl/GLESRenderEngine.h
+++ b/libs/renderengine/gl/GLESRenderEngine.h
@@ -168,6 +168,7 @@
     void setupLayerTexturing(const Texture& texture);
     void setupFillWithColor(float r, float g, float b, float a);
     void setColorTransform(const mat4& colorTransform);
+    void setDisplayColorTransform(const mat4& colorTransform);
     void disableTexturing();
     void disableBlending();
     void setupCornerRadiusCropSize(float width, float height);
diff --git a/libs/renderengine/gl/Program.cpp b/libs/renderengine/gl/Program.cpp
index f4fbf35..a172c56 100644
--- a/libs/renderengine/gl/Program.cpp
+++ b/libs/renderengine/gl/Program.cpp
@@ -66,6 +66,7 @@
         mTextureMatrixLoc = glGetUniformLocation(programId, "texture");
         mSamplerLoc = glGetUniformLocation(programId, "sampler");
         mColorLoc = glGetUniformLocation(programId, "color");
+        mDisplayColorMatrixLoc = glGetUniformLocation(programId, "displayColorMatrix");
         mDisplayMaxLuminanceLoc = glGetUniformLocation(programId, "displayMaxLuminance");
         mMaxMasteringLuminanceLoc = glGetUniformLocation(programId, "maxMasteringLuminance");
         mMaxContentLuminanceLoc = glGetUniformLocation(programId, "maxContentLuminance");
@@ -129,6 +130,9 @@
         const float color[4] = {desc.color.r, desc.color.g, desc.color.b, desc.color.a};
         glUniform4fv(mColorLoc, 1, color);
     }
+    if (mDisplayColorMatrixLoc >= 0) {
+        glUniformMatrix4fv(mDisplayColorMatrixLoc, 1, GL_FALSE, desc.displayColorMatrix.asArray());
+    }
     if (mInputTransformMatrixLoc >= 0) {
         mat4 inputTransformMatrix = desc.inputTransformMatrix;
         glUniformMatrix4fv(mInputTransformMatrixLoc, 1, GL_FALSE, inputTransformMatrix.asArray());
diff --git a/libs/renderengine/gl/Program.h b/libs/renderengine/gl/Program.h
index fc3755e..4292645 100644
--- a/libs/renderengine/gl/Program.h
+++ b/libs/renderengine/gl/Program.h
@@ -104,6 +104,7 @@
     /* location of transform matrix */
     GLint mInputTransformMatrixLoc;
     GLint mOutputTransformMatrixLoc;
+    GLint mDisplayColorMatrixLoc;
 
     /* location of corner radius uniform */
     GLint mCornerRadiusLoc;
diff --git a/libs/renderengine/gl/ProgramCache.cpp b/libs/renderengine/gl/ProgramCache.cpp
index 3ae35ec..7fc0499 100644
--- a/libs/renderengine/gl/ProgramCache.cpp
+++ b/libs/renderengine/gl/ProgramCache.cpp
@@ -180,6 +180,9 @@
                  description.hasOutputTransformMatrix() || description.hasColorMatrix()
                          ? Key::OUTPUT_TRANSFORM_MATRIX_ON
                          : Key::OUTPUT_TRANSFORM_MATRIX_OFF)
+            .set(Key::Key::DISPLAY_COLOR_TRANSFORM_MATRIX_MASK,
+                 description.hasDisplayColorMatrix() ? Key::DISPLAY_COLOR_TRANSFORM_MATRIX_ON
+                                                     : Key::DISPLAY_COLOR_TRANSFORM_MATRIX_OFF)
             .set(Key::ROUNDED_CORNERS_MASK,
                  description.cornerRadius > 0 ? Key::ROUNDED_CORNERS_ON : Key::ROUNDED_CORNERS_OFF)
             .set(Key::SHADOW_MASK, description.drawShadows ? Key::SHADOW_ON : Key::SHADOW_OFF);
@@ -661,7 +664,8 @@
             )__SHADER__";
     }
 
-    if (needs.hasTransformMatrix() || (needs.getInputTF() != needs.getOutputTF())) {
+    if (needs.hasTransformMatrix() || (needs.getInputTF() != needs.getOutputTF()) ||
+        needs.hasDisplayColorMatrix()) {
         if (needs.needsToneMapping()) {
             fs << "uniform float displayMaxLuminance;";
             fs << "uniform float maxMasteringLuminance;";
@@ -700,6 +704,21 @@
             )__SHADER__";
         }
 
+        if (needs.hasDisplayColorMatrix()) {
+            fs << "uniform mat4 displayColorMatrix;";
+            fs << R"__SHADER__(
+                highp vec3 DisplayColorMatrix(const highp vec3 color) {
+                    return clamp(vec3(displayColorMatrix * vec4(color, 1.0)), 0.0, 1.0);
+                }
+            )__SHADER__";
+        } else {
+            fs << R"__SHADER__(
+                highp vec3 DisplayColorMatrix(const highp vec3 color) {
+                    return color;
+                }
+            )__SHADER__";
+        }
+
         generateEOTF(fs, needs);
         generateOOTF(fs, needs);
         generateOETF(fs, needs);
@@ -732,14 +751,17 @@
         }
     }
 
-    if (needs.hasTransformMatrix() || (needs.getInputTF() != needs.getOutputTF())) {
+    if (needs.hasTransformMatrix() || (needs.getInputTF() != needs.getOutputTF()) ||
+        needs.hasDisplayColorMatrix()) {
         if (!needs.isOpaque() && needs.isPremultiplied()) {
             // un-premultiply if needed before linearization
             // avoid divide by 0 by adding 0.5/256 to the alpha channel
             fs << "gl_FragColor.rgb = gl_FragColor.rgb / (gl_FragColor.a + 0.0019);";
         }
         fs << "gl_FragColor.rgb = "
-              "OETF(OutputTransform(OOTF(InputTransform(EOTF(gl_FragColor.rgb)))));";
+              "DisplayColorMatrix(OETF(OutputTransform(OOTF(InputTransform(EOTF(gl_FragColor.rgb)))"
+              ")));";
+
         if (!needs.isOpaque() && needs.isPremultiplied()) {
             // and re-premultiply if needed after gamma correction
             fs << "gl_FragColor.rgb = gl_FragColor.rgb * (gl_FragColor.a + 0.0019);";
diff --git a/libs/renderengine/gl/ProgramCache.h b/libs/renderengine/gl/ProgramCache.h
index 901e631..37bb651 100644
--- a/libs/renderengine/gl/ProgramCache.h
+++ b/libs/renderengine/gl/ProgramCache.h
@@ -117,6 +117,11 @@
             SHADOW_MASK = 1 << SHADOW_SHIFT,
             SHADOW_OFF = 0 << SHADOW_SHIFT,
             SHADOW_ON = 1 << SHADOW_SHIFT,
+
+            DISPLAY_COLOR_TRANSFORM_MATRIX_SHIFT = 14,
+            DISPLAY_COLOR_TRANSFORM_MATRIX_MASK = 1 << DISPLAY_COLOR_TRANSFORM_MATRIX_SHIFT,
+            DISPLAY_COLOR_TRANSFORM_MATRIX_OFF = 0 << DISPLAY_COLOR_TRANSFORM_MATRIX_SHIFT,
+            DISPLAY_COLOR_TRANSFORM_MATRIX_ON = 1 << DISPLAY_COLOR_TRANSFORM_MATRIX_SHIFT,
         };
 
         inline Key() : mKey(0) {}
@@ -143,6 +148,10 @@
         inline bool hasOutputTransformMatrix() const {
             return (mKey & OUTPUT_TRANSFORM_MATRIX_MASK) == OUTPUT_TRANSFORM_MATRIX_ON;
         }
+        inline bool hasDisplayColorMatrix() const {
+            return (mKey & DISPLAY_COLOR_TRANSFORM_MATRIX_MASK) ==
+                    DISPLAY_COLOR_TRANSFORM_MATRIX_ON;
+        }
         inline bool hasTransformMatrix() const {
             return hasInputTransformMatrix() || hasOutputTransformMatrix();
         }
diff --git a/libs/renderengine/include/renderengine/private/Description.h b/libs/renderengine/include/renderengine/private/Description.h
index a62161a..fa6ec10 100644
--- a/libs/renderengine/include/renderengine/private/Description.h
+++ b/libs/renderengine/include/renderengine/private/Description.h
@@ -44,6 +44,7 @@
     bool hasInputTransformMatrix() const;
     bool hasOutputTransformMatrix() const;
     bool hasColorMatrix() const;
+    bool hasDisplayColorMatrix() const;
 
     // whether textures are premultiplied
     bool isPremultipliedAlpha = false;
@@ -79,6 +80,8 @@
 
     // The color matrix will be applied in linear space right before OETF.
     mat4 colorMatrix;
+    // The display color matrix will be applied in gamma space after OETF
+    mat4 displayColorMatrix;
     mat4 inputTransformMatrix;
     mat4 outputTransformMatrix;
 
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index 03c8e80..e5f7539 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -560,6 +560,10 @@
             }
 
             auto texMatrix = getSkM44(item.textureTransform).asM33();
+
+            // b/171404534, scale to fix the layer
+            matrix.postScale(bounds.getWidth() / bufferWidth, bounds.getHeight() / bufferHeight);
+
             // textureTansform was intended to be passed directly into a shader, so when
             // building the total matrix with the textureTransform we need to first
             // normalize it, then apply the textureTransform, then scale back up.
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index d795616..d20fcc4 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -39,7 +39,7 @@
 
 struct RenderEngineTest : public ::testing::Test {
     static void SetUpTestSuite() {
-        sRE = renderengine::gl::GLESRenderEngine::create(
+        renderengine::RenderEngineCreationArgs reCreationArgs =
                 renderengine::RenderEngineCreationArgs::Builder()
                         .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
                         .setImageCacheSize(1)
@@ -49,13 +49,18 @@
                         .setSupportsBackgroundBlur(true)
                         .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
                         .setRenderEngineType(renderengine::RenderEngine::RenderEngineType::GLES)
-                        .build());
+                        .build();
+        sRE = renderengine::gl::GLESRenderEngine::create(reCreationArgs);
+
+        reCreationArgs.useColorManagement = true;
+        sRECM = renderengine::gl::GLESRenderEngine::create(reCreationArgs);
     }
 
     static void TearDownTestSuite() {
         // The ordering here is important - sCurrentBuffer must live longer
         // than RenderEngine to avoid a null reference on tear-down.
         sRE = nullptr;
+        sRECM = nullptr;
         sCurrentBuffer = nullptr;
     }
 
@@ -85,6 +90,9 @@
             sRE->deleteTextures(1, &texName);
             EXPECT_FALSE(sRE->isTextureNameKnownForTesting(texName));
         }
+        for (uint32_t texName : mTexNamesCM) {
+            sRECM->deleteTextures(1, &texName);
+        }
     }
 
     void writeBufferToFile(const char* basename) {
@@ -253,10 +261,11 @@
 
     void invokeDraw(renderengine::DisplaySettings settings,
                     std::vector<const renderengine::LayerSettings*> layers,
-                    sp<GraphicBuffer> buffer) {
+                    sp<GraphicBuffer> buffer, bool useColorManagement = false) {
         base::unique_fd fence;
-        status_t status =
-                sRE->drawLayers(settings, layers, buffer, true, base::unique_fd(), &fence);
+        status_t status = useColorManagement
+                ? sRECM->drawLayers(settings, layers, buffer, true, base::unique_fd(), &fence)
+                : sRE->drawLayers(settings, layers, buffer, true, base::unique_fd(), &fence);
         sCurrentBuffer = buffer;
 
         int fd = fence.release();
@@ -267,7 +276,11 @@
 
         ASSERT_EQ(NO_ERROR, status);
         if (layers.size() > 0) {
-            ASSERT_TRUE(sRE->isFramebufferImageCachedForTesting(buffer->getId()));
+            if (useColorManagement) {
+                ASSERT_TRUE(sRECM->isFramebufferImageCachedForTesting(buffer->getId()));
+            } else {
+                ASSERT_TRUE(sRE->isFramebufferImageCachedForTesting(buffer->getId()));
+            }
         }
     }
 
@@ -322,12 +335,15 @@
     void fillBufferLayerTransform();
 
     template <typename SourceVariant>
-    void fillBufferWithColorTransform();
+    void fillBufferWithColorTransform(bool useColorManagement = false);
 
     template <typename SourceVariant>
     void fillBufferColorTransform();
 
     template <typename SourceVariant>
+    void fillBufferColorTransformCM();
+
+    template <typename SourceVariant>
     void fillRedBufferWithRoundedCorners();
 
     template <typename SourceVariant>
@@ -366,6 +382,8 @@
     // For now, exercise the GL backend directly so that some caching specifics
     // can be tested without changing the interface.
     static std::unique_ptr<renderengine::gl::GLESRenderEngine> sRE;
+    // renderengine object with Color Management enabled
+    static std::unique_ptr<renderengine::gl::GLESRenderEngine> sRECM;
     // Dumb hack to avoid NPE in the EGL driver: the GraphicBuffer needs to
     // be freed *after* RenderEngine is destroyed, so that the EGL image is
     // destroyed first.
@@ -374,14 +392,17 @@
     sp<GraphicBuffer> mBuffer;
 
     std::vector<uint32_t> mTexNames;
+    std::vector<uint32_t> mTexNamesCM;
 };
 
 std::unique_ptr<renderengine::gl::GLESRenderEngine> RenderEngineTest::sRE = nullptr;
+std::unique_ptr<renderengine::gl::GLESRenderEngine> RenderEngineTest::sRECM = nullptr;
+
 sp<GraphicBuffer> RenderEngineTest::sCurrentBuffer = nullptr;
 
 struct ColorSourceVariant {
     static void fillColor(renderengine::LayerSettings& layer, half r, half g, half b,
-                          RenderEngineTest* /*fixture*/) {
+                          RenderEngineTest* /*fixture*/, bool /*useColorManagement*/ = false) {
         layer.source.solidColor = half3(r, g, b);
     }
 };
@@ -409,11 +430,16 @@
 template <typename OpaquenessVariant>
 struct BufferSourceVariant {
     static void fillColor(renderengine::LayerSettings& layer, half r, half g, half b,
-                          RenderEngineTest* fixture) {
+                          RenderEngineTest* fixture, bool useColorManagement = false) {
         sp<GraphicBuffer> buf = RenderEngineTest::allocateSourceBuffer(1, 1);
         uint32_t texName;
-        fixture->sRE->genTextures(1, &texName);
-        fixture->mTexNames.push_back(texName);
+        if (useColorManagement) {
+            fixture->sRECM->genTextures(1, &texName);
+            fixture->mTexNamesCM.push_back(texName);
+        } else {
+            fixture->sRE->genTextures(1, &texName);
+            fixture->mTexNames.push_back(texName);
+        }
 
         uint8_t* pixels;
         buf->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
@@ -643,7 +669,7 @@
 }
 
 template <typename SourceVariant>
-void RenderEngineTest::fillBufferWithColorTransform() {
+void RenderEngineTest::fillBufferWithColorTransform(bool useColorManagement) {
     renderengine::DisplaySettings settings;
     settings.physicalDisplay = fullscreenRect();
     settings.clip = Rect(1, 1);
@@ -652,12 +678,12 @@
 
     renderengine::LayerSettings layer;
     layer.geometry.boundaries = Rect(1, 1).toFloatRect();
-    SourceVariant::fillColor(layer, 0.5f, 0.25f, 0.125f, this);
+    SourceVariant::fillColor(layer, 0.5f, 0.25f, 0.125f, this, useColorManagement);
     layer.alpha = 1.0f;
 
     // construct a fake color matrix
     // annihilate green and blue channels
-    settings.colorTransform = mat4::scale(vec4(1, 0, 0, 1));
+    settings.colorTransform = mat4::scale(vec4(0.9f, 0, 0, 1));
     // set red channel to red + green
     layer.colorTransform = mat4(1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
 
@@ -666,13 +692,19 @@
 
     layers.push_back(&layer);
 
-    invokeDraw(settings, layers, mBuffer);
+    invokeDraw(settings, layers, mBuffer, useColorManagement);
 }
 
 template <typename SourceVariant>
 void RenderEngineTest::fillBufferColorTransform() {
     fillBufferWithColorTransform<SourceVariant>();
-    expectBufferColor(fullscreenRect(), 191, 0, 0, 255);
+    expectBufferColor(fullscreenRect(), 172, 0, 0, 255, 1);
+}
+
+template <typename SourceVariant>
+void RenderEngineTest::fillBufferColorTransformCM() {
+    fillBufferWithColorTransform<SourceVariant>(true);
+    expectBufferColor(fullscreenRect(), 126, 0, 0, 255, 1);
 }
 
 template <typename SourceVariant>
@@ -1073,7 +1105,11 @@
 }
 
 TEST_F(RenderEngineTest, drawLayers_fillBufferColorTransform_colorSource) {
-    fillBufferLayerTransform<ColorSourceVariant>();
+    fillBufferColorTransform<ColorSourceVariant>();
+}
+
+TEST_F(RenderEngineTest, drawLayers_fillBufferColorTransformCM_colorSource) {
+    fillBufferColorTransformCM<ColorSourceVariant>();
 }
 
 TEST_F(RenderEngineTest, drawLayers_fillBufferRoundedCorners_colorSource) {
@@ -1129,7 +1165,11 @@
 }
 
 TEST_F(RenderEngineTest, drawLayers_fillBufferColorTransform_opaqueBufferSource) {
-    fillBufferLayerTransform<BufferSourceVariant<ForceOpaqueBufferVariant>>();
+    fillBufferColorTransform<BufferSourceVariant<ForceOpaqueBufferVariant>>();
+}
+
+TEST_F(RenderEngineTest, drawLayers_fillBufferColorTransformCM_opaqueBufferSource) {
+    fillBufferColorTransformCM<BufferSourceVariant<ForceOpaqueBufferVariant>>();
 }
 
 TEST_F(RenderEngineTest, drawLayers_fillBufferRoundedCorners_opaqueBufferSource) {
@@ -1185,7 +1225,11 @@
 }
 
 TEST_F(RenderEngineTest, drawLayers_fillBufferColorTransform_bufferSource) {
-    fillBufferLayerTransform<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
+    fillBufferColorTransform<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
+}
+
+TEST_F(RenderEngineTest, drawLayers_fillBufferColorTransformCM_bufferSource) {
+    fillBufferColorTransformCM<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
 }
 
 TEST_F(RenderEngineTest, drawLayers_fillBufferRoundedCorners_bufferSource) {
diff --git a/services/inputflinger/InputReaderBase.cpp b/services/inputflinger/InputReaderBase.cpp
index b2dadf8..9cc777d 100644
--- a/services/inputflinger/InputReaderBase.cpp
+++ b/services/inputflinger/InputReaderBase.cpp
@@ -19,6 +19,9 @@
 //#define LOG_NDEBUG 0
 
 #include "InputReaderBase.h"
+#include "input/DisplayViewport.h"
+#include "input/Input.h"
+#include "input/NamedEnum.h"
 
 #include <android/log.h>
 #include <android-base/stringprintf.h>
@@ -99,17 +102,19 @@
     size_t count = 0;
     std::optional<DisplayViewport> result = std::nullopt;
     for (const DisplayViewport& currentViewport : mDisplays) {
-        // Return the first match
+        // Return the first match, or the default display if we're looking for the internal viewport
         if (currentViewport.type == type) {
-            if (!result) {
+            if (!result ||
+                (type == ViewportType::INTERNAL &&
+                 currentViewport.displayId == ADISPLAY_ID_DEFAULT)) {
                 result = std::make_optional(currentViewport);
             }
             count++;
         }
     }
     if (count > 1) {
-        ALOGE("Found %zu viewports with type %s, but expected 1 at most",
-                count, viewportTypeToString(type));
+        ALOGW("Found %zu viewports with type %s, but expected 1 at most", count,
+              NamedEnum::string(type).c_str());
     }
     return result;
 }
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index 2b18180..0661709 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -250,7 +250,7 @@
     int32_t userActivityEventType;
     uint32_t seq;
     bool handled;
-    std::shared_ptr<InputChannel> inputChannel;
+    sp<IBinder> connectionToken;
     sp<IBinder> oldToken;
     sp<IBinder> newToken;
     std::string obscuringPackage;
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 3135c19..d6fa74d 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -204,12 +204,12 @@
     return true;
 }
 
-static void dumpRegion(std::string& dump, const Region& region) {
+static std::string dumpRegion(const Region& region) {
     if (region.isEmpty()) {
-        dump += "<empty>";
-        return;
+        return "<empty>";
     }
 
+    std::string dump;
     bool first = true;
     Region::const_iterator cur = region.begin();
     Region::const_iterator const tail = region.end();
@@ -222,6 +222,7 @@
         dump += StringPrintf("[%d,%d][%d,%d]", cur->left, cur->top, cur->right, cur->bottom);
         cur++;
     }
+    return dump;
 }
 
 static std::string dumpQueue(const std::deque<DispatchEntry*>& queue, nsecs_t currentTime) {
@@ -1229,9 +1230,7 @@
                     &InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
             sp<IBinder> focusedWindowToken =
                     getValueByKey(mFocusedWindowTokenByDisplay, getTargetDisplayId(*entry));
-            if (focusedWindowToken != nullptr) {
-                commandEntry->inputChannel = getInputChannelLocked(focusedWindowToken);
-            }
+            commandEntry->connectionToken = focusedWindowToken;
             commandEntry->keyEntry = entry;
             postCommandLocked(std::move(commandEntry));
             return false; // wait for the command to run
@@ -2273,13 +2272,16 @@
                                                          bool isTouchedWindow) const {
     return StringPrintf(INDENT2 "* %stype=%s, package=%s/%" PRId32 ", mode=%s, alpha=%.2f, "
                                 "frame=[%" PRId32 ",%" PRId32 "][%" PRId32 ",%" PRId32
-                                "], window=%s, applicationInfo=%s, flags=%s\n",
+                                "], touchableRegion=%s, window={%s}, applicationInfo=%s, "
+                                "flags={%s}, inputFeatures={%s}, hasToken=%s\n",
                         (isTouchedWindow) ? "[TOUCHED] " : "",
-                        NamedEnum::string(info->type).c_str(), info->packageName.c_str(),
-                        info->ownerUid, toString(info->touchOcclusionMode).c_str(), info->alpha,
-                        info->frameLeft, info->frameTop, info->frameRight, info->frameBottom,
-                        info->name.c_str(), info->applicationInfo.name.c_str(),
-                        info->flags.string().c_str());
+                        NamedEnum::string(info->type, "%" PRId32).c_str(),
+                        info->packageName.c_str(), info->ownerUid,
+                        toString(info->touchOcclusionMode).c_str(), info->alpha, info->frameLeft,
+                        info->frameTop, info->frameRight, info->frameBottom,
+                        dumpRegion(info->touchableRegion).c_str(), info->name.c_str(),
+                        info->applicationInfo.name.c_str(), info->flags.string().c_str(),
+                        info->inputFeatures.string().c_str(), toString(info->token != nullptr));
 }
 
 bool InputDispatcher::isTouchTrustedLocked(const TouchOcclusionInfo& occlusionInfo) const {
@@ -4400,6 +4402,24 @@
     return dump;
 }
 
+std::string InputDispatcher::dumpPendingFocusRequestsLocked() {
+    if (mPendingFocusRequests.empty()) {
+        return INDENT "mPendingFocusRequests: <none>\n";
+    }
+
+    std::string dump;
+    dump += INDENT "mPendingFocusRequests:\n";
+    for (const auto& [displayId, focusRequest] : mPendingFocusRequests) {
+        // Rather than printing raw values for focusRequest.token and focusRequest.focusedToken,
+        // try to resolve them to actual windows.
+        std::string windowName = getConnectionNameLocked(focusRequest.token);
+        std::string focusedWindowName = getConnectionNameLocked(focusRequest.focusedToken);
+        dump += StringPrintf(INDENT2 "displayId=%" PRId32 ", token->%s, focusedToken->%s\n",
+                             displayId, windowName.c_str(), focusedWindowName.c_str());
+    }
+    return dump;
+}
+
 void InputDispatcher::dumpDispatchStateLocked(std::string& dump) {
     dump += StringPrintf(INDENT "DispatchEnabled: %s\n", toString(mDispatchEnabled));
     dump += StringPrintf(INDENT "DispatchFrozen: %s\n", toString(mDispatchFrozen));
@@ -4422,6 +4442,7 @@
     }
 
     dump += dumpFocusedWindowsLocked();
+    dump += dumpPendingFocusRequestsLocked();
 
     if (!mTouchStatesByDisplay.empty()) {
         dump += StringPrintf(INDENT "TouchStatesByDisplay:\n");
@@ -4484,13 +4505,15 @@
                                          windowInfo->frameRight, windowInfo->frameBottom,
                                          windowInfo->globalScaleFactor,
                                          windowInfo->applicationInfo.name.c_str());
-                    dumpRegion(dump, windowInfo->touchableRegion);
+                    dump += dumpRegion(windowInfo->touchableRegion);
                     dump += StringPrintf(", inputFeatures=%s",
                                          windowInfo->inputFeatures.string().c_str());
                     dump += StringPrintf(", ownerPid=%d, ownerUid=%d, dispatchingTimeout=%" PRId64
-                                         "ms\n",
+                                         "ms, trustedOverlay=%s, hasToken=%s\n",
                                          windowInfo->ownerPid, windowInfo->ownerUid,
-                                         millis(windowInfo->dispatchingTimeout));
+                                         millis(windowInfo->dispatchingTimeout),
+                                         toString(windowInfo->trustedOverlay),
+                                         toString(windowInfo->token != nullptr));
                     windowInfo->transform.dump(dump, "transform", INDENT4);
                 }
             } else {
@@ -4827,6 +4850,14 @@
     return nullptr;
 }
 
+std::string InputDispatcher::getConnectionNameLocked(const sp<IBinder>& connectionToken) const {
+    sp<Connection> connection = getConnectionLocked(connectionToken);
+    if (connection == nullptr) {
+        return "<nullptr>";
+    }
+    return connection->getInputChannelName();
+}
+
 void InputDispatcher::removeConnectionLocked(const sp<Connection>& connection) {
     mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken());
     removeByValue(mConnectionsByFd, connection);
@@ -4887,14 +4918,13 @@
                                         connection.inputChannel->getName().c_str(),
                                         ns2ms(currentWait),
                                         oldestEntry->eventEntry->getDescription().c_str());
-
-    updateLastAnrStateLocked(getWindowHandleLocked(connection.inputChannel->getConnectionToken()),
-                             reason);
+    sp<IBinder> connectionToken = connection.inputChannel->getConnectionToken();
+    updateLastAnrStateLocked(getWindowHandleLocked(connectionToken), reason);
 
     std::unique_ptr<CommandEntry> commandEntry =
             std::make_unique<CommandEntry>(&InputDispatcher::doNotifyAnrLockedInterruptible);
     commandEntry->inputApplicationHandle = nullptr;
-    commandEntry->inputChannel = connection.inputChannel;
+    commandEntry->connectionToken = connectionToken;
     commandEntry->reason = std::move(reason);
     postCommandLocked(std::move(commandEntry));
 }
@@ -4908,7 +4938,6 @@
     std::unique_ptr<CommandEntry> commandEntry =
             std::make_unique<CommandEntry>(&InputDispatcher::doNotifyAnrLockedInterruptible);
     commandEntry->inputApplicationHandle = application;
-    commandEntry->inputChannel = nullptr;
     commandEntry->reason = std::move(reason);
     postCommandLocked(std::move(commandEntry));
 }
@@ -4977,10 +5006,8 @@
 }
 
 void InputDispatcher::doNotifyAnrLockedInterruptible(CommandEntry* commandEntry) {
-    sp<IBinder> token =
-            commandEntry->inputChannel ? commandEntry->inputChannel->getConnectionToken() : nullptr;
     mLock.unlock();
-
+    const sp<IBinder>& token = commandEntry->connectionToken;
     const std::chrono::nanoseconds timeoutExtension =
             mPolicy->notifyAnr(commandEntry->inputApplicationHandle, token, commandEntry->reason);
 
@@ -5043,9 +5070,7 @@
     mLock.unlock();
 
     android::base::Timer t;
-    sp<IBinder> token = commandEntry->inputChannel != nullptr
-            ? commandEntry->inputChannel->getConnectionToken()
-            : nullptr;
+    const sp<IBinder>& token = commandEntry->connectionToken;
     nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(token, &event, entry.policyFlags);
     if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
         ALOGW("Excessive delay in interceptKeyBeforeDispatching; took %s ms",
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 4565151..5387c40 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -210,6 +210,8 @@
     sp<Connection> getConnectionLocked(const sp<IBinder>& inputConnectionToken) const
             REQUIRES(mLock);
 
+    std::string getConnectionNameLocked(const sp<IBinder>& connectionToken) const REQUIRES(mLock);
+
     void removeConnectionLocked(const sp<Connection>& connection) REQUIRES(mLock);
 
     struct IBinderHash {
@@ -532,6 +534,7 @@
     void dumpMonitors(std::string& dump, const std::vector<Monitor>& monitors);
     void logDispatchStateLocked() REQUIRES(mLock);
     std::string dumpFocusedWindowsLocked() REQUIRES(mLock);
+    std::string dumpPendingFocusRequestsLocked() REQUIRES(mLock);
 
     // Registration.
     void removeMonitorChannelLocked(const sp<IBinder>& connectionToken) REQUIRES(mLock);
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index dc32003..40471b2 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -198,7 +198,7 @@
     std::condition_variable mNotifyAnr;
     std::chrono::nanoseconds mAnrTimeout = 0ms;
 
-    virtual void notifyConfigurationChanged(nsecs_t when) override {
+    void notifyConfigurationChanged(nsecs_t when) override {
         std::scoped_lock lock(mLock);
         mConfigurationChangedTime = when;
     }
@@ -213,17 +213,17 @@
         return mAnrTimeout;
     }
 
-    virtual void notifyInputChannelBroken(const sp<IBinder>&) override {}
+    void notifyInputChannelBroken(const sp<IBinder>&) override {}
 
-    virtual void notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) override {}
+    void notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) override {}
 
-    virtual void notifyUntrustedTouch(const std::string& obscuringPackage) override {}
+    void notifyUntrustedTouch(const std::string& obscuringPackage) override {}
 
-    virtual void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) override {
+    void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) override {
         *outConfig = mConfig;
     }
 
-    virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) override {
+    bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) override {
         std::scoped_lock lock(mLock);
         switch (inputEvent->getType()) {
             case AINPUT_EVENT_TYPE_KEY: {
@@ -241,22 +241,20 @@
         return true;
     }
 
-    virtual void interceptKeyBeforeQueueing(const KeyEvent*, uint32_t&) override {}
+    void interceptKeyBeforeQueueing(const KeyEvent*, uint32_t&) override {}
 
-    virtual void interceptMotionBeforeQueueing(int32_t, nsecs_t, uint32_t&) override {}
+    void interceptMotionBeforeQueueing(int32_t, nsecs_t, uint32_t&) override {}
 
-    virtual nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent*,
-                                                  uint32_t) override {
+    nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent*, uint32_t) override {
         return 0;
     }
 
-    virtual bool dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent*, uint32_t,
-                                      KeyEvent*) override {
+    bool dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent*, uint32_t, KeyEvent*) override {
         return false;
     }
 
-    virtual void notifySwitch(nsecs_t when, uint32_t switchValues, uint32_t switchMask,
-                              uint32_t policyFlags) override {
+    void notifySwitch(nsecs_t when, uint32_t switchValues, uint32_t switchMask,
+                      uint32_t policyFlags) override {
         std::scoped_lock lock(mLock);
         /** We simply reconstruct NotifySwitchArgs in policy because InputDispatcher is
          * essentially a passthrough for notifySwitch.
@@ -264,13 +262,11 @@
         mLastNotifySwitch = NotifySwitchArgs(1 /*id*/, when, policyFlags, switchValues, switchMask);
     }
 
-    virtual void pokeUserActivity(nsecs_t, int32_t) override {}
+    void pokeUserActivity(nsecs_t, int32_t) override {}
 
-    virtual bool checkInjectEventsPermissionNonReentrant(int32_t, int32_t) override {
-        return false;
-    }
+    bool checkInjectEventsPermissionNonReentrant(int32_t, int32_t) override { return false; }
 
-    virtual void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override {
+    void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override {
         std::scoped_lock lock(mLock);
         mOnPointerDownToken = newToken;
     }
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 211b49e..a72d5c3 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -33,6 +33,8 @@
 #include <math.h>
 
 #include <memory>
+#include "input/DisplayViewport.h"
+#include "input/Input.h"
 
 namespace android {
 
@@ -1233,6 +1235,47 @@
 }
 
 /**
+ * When we have multiple internal displays make sure we always return the default display when
+ * querying by type.
+ */
+TEST_F(InputReaderPolicyTest, Viewports_ByTypeReturnsDefaultForInternal) {
+    const std::string uniqueId1 = "uniqueId1";
+    const std::string uniqueId2 = "uniqueId2";
+    constexpr int32_t nonDefaultDisplayId = 2;
+    static_assert(nonDefaultDisplayId != ADISPLAY_ID_DEFAULT,
+                  "Test display ID should not be ADISPLAY_ID_DEFAULT");
+
+    // Add the default display first and ensure it gets returned.
+    mFakePolicy->clearViewports();
+    mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT,
+                                    DISPLAY_ORIENTATION_0, uniqueId1, NO_PORT,
+                                    ViewportType::INTERNAL);
+    mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT,
+                                    DISPLAY_ORIENTATION_0, uniqueId2, NO_PORT,
+                                    ViewportType::INTERNAL);
+
+    std::optional<DisplayViewport> viewport =
+            mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
+    ASSERT_TRUE(viewport);
+    ASSERT_EQ(ADISPLAY_ID_DEFAULT, viewport->displayId);
+    ASSERT_EQ(ViewportType::INTERNAL, viewport->type);
+
+    // Add the default display second to make sure order doesn't matter.
+    mFakePolicy->clearViewports();
+    mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT,
+                                    DISPLAY_ORIENTATION_0, uniqueId2, NO_PORT,
+                                    ViewportType::INTERNAL);
+    mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT,
+                                    DISPLAY_ORIENTATION_0, uniqueId1, NO_PORT,
+                                    ViewportType::INTERNAL);
+
+    viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
+    ASSERT_TRUE(viewport);
+    ASSERT_EQ(ADISPLAY_ID_DEFAULT, viewport->displayId);
+    ASSERT_EQ(ViewportType::INTERNAL, viewport->type);
+}
+
+/**
  * Check getDisplayViewportByPort
  */
 TEST_F(InputReaderPolicyTest, Viewports_GetByPort) {
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
index d302f98..fa75ffa 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -393,6 +393,15 @@
                               nsecs_t expectedPresentTime) {
     ATRACE_CALL();
 
+    // If this is not a valid vsync for the layer's uid, return and try again later
+    const bool isVsyncValidForUid =
+            mFlinger->mScheduler->isVsyncValid(expectedPresentTime, mOwnerUid);
+    if (!isVsyncValidForUid) {
+        ATRACE_NAME("!isVsyncValidForUid");
+        mFlinger->setTransactionFlags(eTraversalNeeded);
+        return false;
+    }
+
     bool refreshRequired = latchSidebandStream(recomputeVisibleRegions);
 
     if (refreshRequired) {
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index b60acde..9481966 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -2495,12 +2495,6 @@
         transformedLayerBounds.bottom = tmp;
     }
 
-    // Input coordinates should be in display coordinate space.
-    info.frameLeft = transformedLayerBounds.left;
-    info.frameTop = transformedLayerBounds.top;
-    info.frameRight = transformedLayerBounds.right;
-    info.frameBottom = transformedLayerBounds.bottom;
-
     // Compute the correct transform to send to input. This will allow it to transform the
     // input coordinates from display space into window space. Therefore, it needs to use the
     // final layer frame to create the inverse transform. Since surface insets are added later,
@@ -2522,6 +2516,16 @@
     inputTransform.set(translation.x, translation.y);
     info.transform = inputTransform.inverse();
 
+    // We need to send the layer bounds cropped to the screenbounds since the layer can be cropped.
+    // The frame should be the area the user sees on screen since it's used for occlusion
+    // detection.
+    Rect screenBounds = Rect{mScreenBounds};
+    transformedLayerBounds.intersect(screenBounds, &transformedLayerBounds);
+    info.frameLeft = transformedLayerBounds.left;
+    info.frameTop = transformedLayerBounds.top;
+    info.frameRight = transformedLayerBounds.right;
+    info.frameBottom = transformedLayerBounds.bottom;
+
     // Position the touchable region relative to frame screen location and restrict it to frame
     // bounds.
     info.touchableRegion = inputTransform.transform(info.touchableRegion);
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index bf2a509..bf5be47 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -31,6 +31,8 @@
 
 #include <android-base/stringprintf.h>
 
+#include <binder/IPCThreadState.h>
+
 #include <cutils/compiler.h>
 #include <cutils/sched_policy.h>
 
@@ -123,11 +125,12 @@
 
 } // namespace
 
-EventThreadConnection::EventThreadConnection(EventThread* eventThread,
+EventThreadConnection::EventThreadConnection(EventThread* eventThread, uid_t callingUid,
                                              ResyncCallback resyncCallback,
                                              ISurfaceComposer::ConfigChanged configChanged)
       : resyncCallback(std::move(resyncCallback)),
         mConfigChanged(configChanged),
+        mOwnerUid(callingUid),
         mEventThread(eventThread),
         mChannel(gui::BitTube::DefaultSize) {}
 
@@ -170,10 +173,12 @@
 
 EventThread::EventThread(std::unique_ptr<VSyncSource> vsyncSource,
                          android::frametimeline::TokenManager* tokenManager,
-                         InterceptVSyncsCallback interceptVSyncsCallback)
+                         InterceptVSyncsCallback interceptVSyncsCallback,
+                         ThrottleVsyncCallback throttleVsyncCallback)
       : mVSyncSource(std::move(vsyncSource)),
         mTokenManager(tokenManager),
         mInterceptVSyncsCallback(std::move(interceptVSyncsCallback)),
+        mThrottleVsyncCallback(std::move(throttleVsyncCallback)),
         mThreadName(mVSyncSource->getName()) {
     mVSyncSource->setCallback(this);
 
@@ -216,8 +221,9 @@
 
 sp<EventThreadConnection> EventThread::createEventConnection(
         ResyncCallback resyncCallback, ISurfaceComposer::ConfigChanged configChanged) const {
-    return new EventThreadConnection(const_cast<EventThread*>(this), std::move(resyncCallback),
-                                     configChanged);
+    return new EventThreadConnection(const_cast<EventThread*>(this),
+                                     IPCThreadState::self()->getCallingUid(),
+                                     std::move(resyncCallback), configChanged);
 }
 
 status_t EventThread::registerDisplayEventConnection(const sp<EventThreadConnection>& connection) {
@@ -443,6 +449,11 @@
 
 bool EventThread::shouldConsumeEvent(const DisplayEventReceiver::Event& event,
                                      const sp<EventThreadConnection>& connection) const {
+    const auto throttleVsync = [&] {
+        return mThrottleVsyncCallback &&
+                mThrottleVsyncCallback(event.vsync.expectedVSyncTimestamp, connection->mOwnerUid);
+    };
+
     switch (event.header.type) {
         case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
             return true;
@@ -458,12 +469,22 @@
                 case VSyncRequest::SingleSuppressCallback:
                     connection->vsyncRequest = VSyncRequest::None;
                     return false;
-                case VSyncRequest::Single:
+                case VSyncRequest::Single: {
+                    if (throttleVsync()) {
+                        return false;
+                    }
                     connection->vsyncRequest = VSyncRequest::SingleSuppressCallback;
                     return true;
+                }
                 case VSyncRequest::Periodic:
+                    if (throttleVsync()) {
+                        return false;
+                    }
                     return true;
                 default:
+                    // We don't throttle vsync if the app set a vsync request rate
+                    // since there is no easy way to do that and this is a very
+                    // rare case
                     return event.vsync.count % vsyncPeriod(connection->vsyncRequest) == 0;
             }
 
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index e42ca05..2e2d989 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -81,7 +81,7 @@
 
 class EventThreadConnection : public BnDisplayEventConnection {
 public:
-    EventThreadConnection(EventThread*, ResyncCallback,
+    EventThreadConnection(EventThread*, uid_t callingUid, ResyncCallback,
                           ISurfaceComposer::ConfigChanged configChanged);
     virtual ~EventThreadConnection();
 
@@ -98,6 +98,8 @@
     const ISurfaceComposer::ConfigChanged mConfigChanged =
             ISurfaceComposer::ConfigChanged::eConfigChangedSuppress;
 
+    const uid_t mOwnerUid;
+
 private:
     virtual void onFirstRef();
     EventThread* const mEventThread;
@@ -143,9 +145,10 @@
 class EventThread : public android::EventThread, private VSyncSource::Callback {
 public:
     using InterceptVSyncsCallback = std::function<void(nsecs_t)>;
+    using ThrottleVsyncCallback = std::function<bool(nsecs_t, uid_t)>;
 
-    EventThread(std::unique_ptr<VSyncSource>, frametimeline::TokenManager*,
-                InterceptVSyncsCallback);
+    EventThread(std::unique_ptr<VSyncSource>, frametimeline::TokenManager*, InterceptVSyncsCallback,
+                ThrottleVsyncCallback);
     ~EventThread();
 
     sp<EventThreadConnection> createEventConnection(
@@ -196,6 +199,7 @@
     frametimeline::TokenManager* const mTokenManager;
 
     const InterceptVSyncsCallback mInterceptVSyncsCallback;
+    const ThrottleVsyncCallback mThrottleVsyncCallback;
     const char* const mThreadName;
 
     std::thread mThread;
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 7ab49a9..150f925 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -48,7 +48,7 @@
     }
 }
 
-std::string RefreshRateConfigs::Policy::toString() {
+std::string RefreshRateConfigs::Policy::toString() const {
     return base::StringPrintf("default config ID: %d, allowGroupSwitching = %d"
                               ", primary range: [%.2f %.2f], app request range: [%.2f %.2f]",
                               defaultConfig.value(), allowGroupSwitching, primaryRange.min,
@@ -433,10 +433,12 @@
     // defaultConfig must be a valid config, and within the given refresh rate range.
     auto iter = mRefreshRates.find(policy.defaultConfig);
     if (iter == mRefreshRates.end()) {
+        ALOGE("Default config is not found.");
         return false;
     }
     const RefreshRate& refreshRate = *iter->second;
     if (!refreshRate.inPolicy(policy.primaryRange.min, policy.primaryRange.max)) {
+        ALOGE("Default config is not in the primary range.");
         return false;
     }
     return policy.appRequestRange.min <= policy.primaryRange.min &&
@@ -446,6 +448,7 @@
 status_t RefreshRateConfigs::setDisplayManagerPolicy(const Policy& policy) {
     std::lock_guard lock(mLock);
     if (!isPolicyValid(policy)) {
+        ALOGE("Invalid refresh rate policy: %s", policy.toString().c_str());
         return BAD_VALUE;
     }
     Policy previousPolicy = *getCurrentPolicyLocked();
@@ -625,4 +628,36 @@
     return RefreshRateConfigs::KernelIdleTimerAction::TurnOn;
 }
 
+void RefreshRateConfigs::setPreferredRefreshRateForUid(uid_t uid, float refreshRateHz) {
+    if (refreshRateHz > 0 && refreshRateHz < 1) {
+        return;
+    }
+
+    std::lock_guard lock(mLock);
+    if (refreshRateHz != 0) {
+        mPreferredRefreshRateForUid[uid] = refreshRateHz;
+    } else {
+        mPreferredRefreshRateForUid.erase(uid);
+    }
+}
+
+int RefreshRateConfigs::getRefreshRateDividerForUid(uid_t uid) const {
+    constexpr float kThreshold = 0.1f;
+    std::lock_guard lock(mLock);
+
+    const auto iter = mPreferredRefreshRateForUid.find(uid);
+    if (iter == mPreferredRefreshRateForUid.end()) {
+        return 1;
+    }
+
+    const auto refreshRateHz = iter->second;
+    const auto numPeriods = mCurrentRefreshRate->getFps() / refreshRateHz;
+    const auto numPeriodsRounded = std::round(numPeriods);
+    if (std::abs(numPeriods - numPeriodsRounded) > kThreshold) {
+        return 1;
+    }
+
+    return static_cast<int>(numPeriods);
+}
+
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 280ed62..8ff92a0 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -166,7 +166,7 @@
         }
 
         bool operator!=(const Policy& other) const { return !(*this == other); }
-        std::string toString();
+        std::string toString() const;
     };
 
     // Return code set*Policy() to indicate the current policy is unchanged.
@@ -311,6 +311,13 @@
     // refresh rates.
     KernelIdleTimerAction getIdleTimerAction() const;
 
+    // Stores the preferred refresh rate that an app should run at.
+    // refreshRate == 0 means no preference.
+    void setPreferredRefreshRateForUid(uid_t, float refreshRateHz) EXCLUDES(mLock);
+
+    // Returns a divider for the current refresh rate
+    int getRefreshRateDividerForUid(uid_t) const EXCLUDES(mLock);
+
 private:
     friend class RefreshRateConfigsTest;
 
@@ -368,6 +375,8 @@
     Policy mDisplayManagerPolicy GUARDED_BY(mLock);
     std::optional<Policy> mOverridePolicy GUARDED_BY(mLock);
 
+    std::unordered_map<uid_t, float> mPreferredRefreshRateForUid GUARDED_BY(mLock);
+
     // The min and max refresh rates supported by the device.
     // This will not change at runtime.
     const RefreshRate* mMinSupportedRefreshRate;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 7b8448f..a14019e 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -212,13 +212,26 @@
                                                        readyDuration, traceVsync, name);
 }
 
+bool Scheduler::isVsyncValid(nsecs_t expectedVsyncTimestamp, uid_t uid) const {
+    const auto divider = mRefreshRateConfigs.getRefreshRateDividerForUid(uid);
+    if (divider <= 1) {
+        return true;
+    }
+
+    return mVsyncSchedule.tracker->isVSyncInPhase(expectedVsyncTimestamp, divider);
+}
+
 Scheduler::ConnectionHandle Scheduler::createConnection(
         const char* connectionName, frametimeline::TokenManager* tokenManager,
         std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration,
         impl::EventThread::InterceptVSyncsCallback interceptCallback) {
     auto vsyncSource = makePrimaryDispSyncSource(connectionName, workDuration, readyDuration);
+    auto throttleVsync = [this](nsecs_t expectedVsyncTimestamp, uid_t uid) {
+        return !isVsyncValid(expectedVsyncTimestamp, uid);
+    };
     auto eventThread = std::make_unique<impl::EventThread>(std::move(vsyncSource), tokenManager,
-                                                           std::move(interceptCallback));
+                                                           std::move(interceptCallback),
+                                                           std::move(throttleVsync));
     return createConnection(std::move(eventThread));
 }
 
@@ -379,7 +392,8 @@
         auto eventThread =
                 std::make_unique<impl::EventThread>(std::move(vsyncSource),
                                                     /*tokenManager=*/nullptr,
-                                                    impl::EventThread::InterceptVSyncsCallback());
+                                                    impl::EventThread::InterceptVSyncsCallback(),
+                                                    impl::EventThread::ThrottleVsyncCallback());
 
         mInjectorConnectionHandle = createConnection(std::move(eventThread));
     }
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 47ce4a4..4c86d26 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -139,6 +139,10 @@
 
     scheduler::VSyncDispatch& getVsyncDispatch() { return *mVsyncSchedule.dispatch; }
 
+    // Returns true if a given vsync timestamp is considered valid vsync
+    // for a given uid
+    bool isVsyncValid(nsecs_t expectedVsyncTimestamp, uid_t uid) const;
+
     void dump(std::string&) const;
     void dump(ConnectionHandle, std::string&) const;
     void dumpVsync(std::string&) const;
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index e90edf7..75d1e6f 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -27,6 +27,9 @@
 #include <chrono>
 #include <sstream>
 
+#undef LOG_TAG
+#define LOG_TAG "VSyncPredictor"
+
 namespace android::scheduler {
 using base::StringAppendF;
 
@@ -66,7 +69,7 @@
 
 nsecs_t VSyncPredictor::currentPeriod() const {
     std::lock_guard lock(mMutex);
-    return std::get<0>(mRateMap.find(mIdealPeriod)->second);
+    return mRateMap.find(mIdealPeriod)->second.slope;
 }
 
 bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
@@ -118,7 +121,7 @@
     // normalizing to the oldest timestamp cuts down on error in calculating the intercept.
     auto const oldest_ts = *std::min_element(mTimestamps.begin(), mTimestamps.end());
     auto it = mRateMap.find(mIdealPeriod);
-    auto const currentPeriod = std::get<0>(it->second);
+    auto const currentPeriod = it->second.slope;
     // TODO (b/144707443): its important that there's some precision in the mean of the ordinals
     //                     for the intercept calculation, so scale the ordinals by 1000 to continue
     //                     fixed point calculation. Explore expanding
@@ -172,10 +175,8 @@
     return true;
 }
 
-nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const {
-    std::lock_guard lock(mMutex);
-
-    auto const [slope, intercept] = getVSyncPredictionModel(lock);
+nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const {
+    auto const [slope, intercept] = getVSyncPredictionModelLocked();
 
     if (mTimestamps.empty()) {
         traceInt64If("VSP-mode", 1);
@@ -210,13 +211,71 @@
     return prediction;
 }
 
-std::tuple<nsecs_t, nsecs_t> VSyncPredictor::getVSyncPredictionModel() const {
+nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const {
     std::lock_guard lock(mMutex);
-    return VSyncPredictor::getVSyncPredictionModel(lock);
+    return nextAnticipatedVSyncTimeFromLocked(timePoint);
 }
 
-std::tuple<nsecs_t, nsecs_t> VSyncPredictor::getVSyncPredictionModel(
-        std::lock_guard<std::mutex> const&) const {
+/*
+ * Returns whether a given vsync timestamp is in phase with a vsync divider.
+ * For example, if the vsync timestamps are (0,16,32,48):
+ * isVSyncInPhase(0, 2) = true
+ * isVSyncInPhase(16, 2) = false
+ * isVSyncInPhase(32, 2) = true
+ */
+bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, int divider) const {
+    struct VsyncError {
+        nsecs_t vsyncTimestamp;
+        float error;
+
+        bool operator<(const VsyncError& other) const { return error < other.error; }
+    };
+
+    std::lock_guard lock(mMutex);
+    if (divider <= 1) {
+        return true;
+    }
+
+    const nsecs_t period = mRateMap[mIdealPeriod].slope;
+    const nsecs_t justBeforeTimePoint = timePoint - period / 2;
+    const nsecs_t dividedPeriod = mIdealPeriod / divider;
+
+    // If this is the first time we have asked about this divider with the
+    // current vsync period, it is considered in phase and we store the closest
+    // vsync timestamp
+    const auto knownTimestampIter = mRateDividerKnownTimestampMap.find(dividedPeriod);
+    if (knownTimestampIter == mRateDividerKnownTimestampMap.end()) {
+        const auto vsync = nextAnticipatedVSyncTimeFromLocked(justBeforeTimePoint);
+        mRateDividerKnownTimestampMap[dividedPeriod] = vsync;
+        return true;
+    }
+
+    // Find the next N vsync timestamp where N is the divider.
+    // One of these vsyncs will be in phase. We return the one which is
+    // the most aligned with the last known in phase vsync
+    std::vector<VsyncError> vsyncs(static_cast<size_t>(divider));
+    const nsecs_t knownVsync = knownTimestampIter->second;
+    nsecs_t point = justBeforeTimePoint;
+    for (size_t i = 0; i < divider; i++) {
+        const nsecs_t vsync = nextAnticipatedVSyncTimeFromLocked(point);
+        const auto numPeriods = static_cast<float>(vsync - knownVsync) / (period * divider);
+        const auto error = std::abs(std::round(numPeriods) - numPeriods);
+        vsyncs[i] = {vsync, error};
+        point = vsync + 1;
+    }
+
+    const auto minVsyncError = std::min_element(vsyncs.begin(), vsyncs.end());
+    mRateDividerKnownTimestampMap[dividedPeriod] = minVsyncError->vsyncTimestamp;
+    return std::abs(minVsyncError->vsyncTimestamp - timePoint) < period / 2;
+}
+
+VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const {
+    std::lock_guard lock(mMutex);
+    const auto model = VSyncPredictor::getVSyncPredictionModelLocked();
+    return {model.slope, model.intercept};
+}
+
+VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModelLocked() const {
     return mRateMap.find(mIdealPeriod)->second;
 }
 
@@ -269,8 +328,8 @@
     for (const auto& [idealPeriod, periodInterceptTuple] : mRateMap) {
         StringAppendF(&result,
                       "\t\tFor ideal period %.2fms: period = %.2fms, intercept = %" PRId64 "\n",
-                      idealPeriod / 1e6f, std::get<0>(periodInterceptTuple) / 1e6f,
-                      std::get<1>(periodInterceptTuple));
+                      idealPeriod / 1e6f, periodInterceptTuple.slope / 1e6f,
+                      periodInterceptTuple.intercept);
     }
 }
 
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index 5f2ec49..381cf81 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -38,10 +38,10 @@
                    uint32_t outlierTolerancePercent);
     ~VSyncPredictor();
 
-    bool addVsyncTimestamp(nsecs_t timestamp) final;
-    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const final;
-    nsecs_t currentPeriod() const final;
-    void resetModel() final;
+    bool addVsyncTimestamp(nsecs_t timestamp) final EXCLUDES(mMutex);
+    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const final EXCLUDES(mMutex);
+    nsecs_t currentPeriod() const final EXCLUDES(mMutex);
+    void resetModel() final EXCLUDES(mMutex);
 
     /*
      * Inform the model that the period is anticipated to change to a new value.
@@ -50,16 +50,23 @@
      *
      * \param [in] period   The new period that should be used.
      */
-    void setPeriod(nsecs_t period) final;
+    void setPeriod(nsecs_t period) final EXCLUDES(mMutex);
 
     /* Query if the model is in need of more samples to make a prediction.
      * \return  True, if model would benefit from more samples, False if not.
      */
-    bool needsMoreSamples() const final;
+    bool needsMoreSamples() const final EXCLUDES(mMutex);
 
-    std::tuple<nsecs_t /* slope */, nsecs_t /* intercept */> getVSyncPredictionModel() const;
+    struct Model {
+        nsecs_t slope;
+        nsecs_t intercept;
+    };
 
-    void dump(std::string& result) const final;
+    VSyncPredictor::Model getVSyncPredictionModel() const EXCLUDES(mMutex);
+
+    bool isVSyncInPhase(nsecs_t timePoint, int divider) const final EXCLUDES(mMutex);
+
+    void dump(std::string& result) const final EXCLUDES(mMutex);
 
 private:
     VSyncPredictor(VSyncPredictor const&) = delete;
@@ -76,13 +83,19 @@
     std::mutex mutable mMutex;
     size_t next(size_t i) const REQUIRES(mMutex);
     bool validate(nsecs_t timestamp) const REQUIRES(mMutex);
-    std::tuple<nsecs_t, nsecs_t> getVSyncPredictionModel(std::lock_guard<std::mutex> const&) const
-            REQUIRES(mMutex);
+
+    Model getVSyncPredictionModelLocked() const REQUIRES(mMutex);
+
+    nsecs_t nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const REQUIRES(mMutex);
 
     nsecs_t mIdealPeriod GUARDED_BY(mMutex);
     std::optional<nsecs_t> mKnownTimestamp GUARDED_BY(mMutex);
 
-    std::unordered_map<nsecs_t, std::tuple<nsecs_t, nsecs_t>> mutable mRateMap GUARDED_BY(mMutex);
+    // Map between ideal vsync period and the calculated model
+    std::unordered_map<nsecs_t, Model> mutable mRateMap GUARDED_BY(mMutex);
+
+    // Map between the divided vsync period and the last known vsync timestamp
+    std::unordered_map<nsecs_t, nsecs_t> mutable mRateDividerKnownTimestampMap GUARDED_BY(mMutex);
 
     size_t mLastTimestampIndex GUARDED_BY(mMutex) = 0;
     std::vector<nsecs_t> mTimestamps GUARDED_BY(mMutex);
diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h
index 107c540..2cd9b3d 100644
--- a/services/surfaceflinger/Scheduler/VSyncTracker.h
+++ b/services/surfaceflinger/Scheduler/VSyncTracker.h
@@ -68,6 +68,14 @@
 
     virtual bool needsMoreSamples() const = 0;
 
+    /*
+     * Checks if a vsync timestamp is in phase for a given divider.
+     *
+     * \param [in] timePoint  A vsync timestamp
+     * \param [in] divider  The divider to check for
+     */
+    virtual bool isVSyncInPhase(nsecs_t timePoint, int divider) const = 0;
+
     virtual void dump(std::string& result) const = 0;
 
 protected:
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index fde38c9..57c4d52 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -3787,9 +3787,7 @@
     }
     if (what & layer_state_t::eFrameTimelineVsyncChanged) {
         layer->setFrameTimelineVsyncForTransaction(s.frameTimelineVsyncId, postTime);
-    } else {
-        // TODO (b/171252403) We are calling this too much, potentially triggering
-        // unnecessary work
+    } else if (frameTimelineVsyncId != ISurfaceComposer::INVALID_VSYNC_ID) {
         layer->setFrameTimelineVsyncForTransaction(frameTimelineVsyncId, postTime);
     }
     if (what & layer_state_t::eFixedTransformHintChanged) {
@@ -4915,7 +4913,7 @@
     }
     // Numbers from 1000 to 1038 are currently used for backdoors. The code
     // in onTransact verifies that the user is root, and has access to use SF.
-    if (code >= 1000 && code <= 1038) {
+    if (code >= 1000 && code <= 1039) {
         ALOGV("Accessing SurfaceFlinger through backdoor code: %u", code);
         return OK;
     }
@@ -5280,6 +5278,13 @@
                 mFrameTimeline->setMaxDisplayFrames(n);
                 return NO_ERROR;
             }
+            case 1039: {
+                // The first parameter is the uid
+                n = data.readInt32();
+                const float refreshRateHz = data.readFloat();
+                mRefreshRateConfigs->setPreferredRefreshRateForUid(n, refreshRateHz);
+            }
+                return NO_ERROR;
         }
     }
     return err;
diff --git a/services/surfaceflinger/tests/fakehwc/Android.bp b/services/surfaceflinger/tests/fakehwc/Android.bp
index 8d094e4..3535fbb 100644
--- a/services/surfaceflinger/tests/fakehwc/Android.bp
+++ b/services/surfaceflinger/tests/fakehwc/Android.bp
@@ -14,7 +14,6 @@
         "android.hardware.graphics.composer@2.2",
         "android.hardware.graphics.composer@2.3",
         "android.hardware.graphics.composer@2.4",
-        "android.hardware.graphics.composer@2.1-resources",
         "android.hardware.graphics.mapper@2.0",
         "android.hardware.graphics.mapper@3.0",
         "android.hardware.graphics.mapper@4.0",
@@ -34,6 +33,7 @@
         "libutils",
     ],
     static_libs: [
+        "android.hardware.graphics.composer@2.1-resources",
         "libcompositionengine",
         "libgmock",
         "libperfetto_client_experimental",
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index a4f7449..0911712 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -134,13 +134,15 @@
         EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
         EXPECT_CALL(*eventThread, createEventConnection(_, _))
                 .WillOnce(Return(
-                        new EventThreadConnection(eventThread.get(), ResyncCallback(),
+                        new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
+                                                  ResyncCallback(),
                                                   ISurfaceComposer::eConfigChangedSuppress)));
 
         EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
         EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
                 .WillOnce(Return(
-                        new EventThreadConnection(sfEventThread.get(), ResyncCallback(),
+                        new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
+                                                  ResyncCallback(),
                                                   ISurfaceComposer::eConfigChangedSuppress)));
 
         auto vsyncController = std::make_unique<mock::VsyncController>();
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index db05d5a..f0311bd 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -70,12 +70,14 @@
 void DisplayTransactionTest::injectMockScheduler() {
     EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_));
     EXPECT_CALL(*mEventThread, createEventConnection(_, _))
-            .WillOnce(Return(new EventThreadConnection(mEventThread, ResyncCallback(),
+            .WillOnce(Return(new EventThreadConnection(mEventThread, /*callingUid=*/0,
+                                                       ResyncCallback(),
                                                        ISurfaceComposer::eConfigChangedSuppress)));
 
     EXPECT_CALL(*mSFEventThread, registerDisplayEventConnection(_));
     EXPECT_CALL(*mSFEventThread, createEventConnection(_, _))
-            .WillOnce(Return(new EventThreadConnection(mSFEventThread, ResyncCallback(),
+            .WillOnce(Return(new EventThreadConnection(mSFEventThread, /*callingUid=*/0,
+                                                       ResyncCallback(),
                                                        ISurfaceComposer::eConfigChangedSuppress)));
 
     mFlinger.setupScheduler(std::unique_ptr<scheduler::VsyncController>(mVsyncController),
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index f680bdb..3aafd45 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -59,9 +59,11 @@
 protected:
     class MockEventThreadConnection : public EventThreadConnection {
     public:
-        MockEventThreadConnection(impl::EventThread* eventThread, ResyncCallback&& resyncCallback,
+        MockEventThreadConnection(impl::EventThread* eventThread, uid_t callingUid,
+                                  ResyncCallback&& resyncCallback,
                                   ISurfaceComposer::ConfigChanged configChanged)
-              : EventThreadConnection(eventThread, std::move(resyncCallback), configChanged) {}
+              : EventThreadConnection(eventThread, callingUid, std::move(resyncCallback),
+                                      configChanged) {}
         MOCK_METHOD1(postEvent, status_t(const DisplayEventReceiver::Event& event));
     };
 
@@ -73,7 +75,8 @@
 
     void createThread(std::unique_ptr<VSyncSource>);
     sp<MockEventThreadConnection> createConnection(ConnectionEventRecorder& recorder,
-                                                   ISurfaceComposer::ConfigChanged configChanged);
+                                                   ISurfaceComposer::ConfigChanged configChanged,
+                                                   uid_t ownerUid = mConnectionUid);
 
     void expectVSyncSetEnabledCallReceived(bool expectedState);
     void expectVSyncSetDurationCallReceived(std::chrono::nanoseconds expectedDuration,
@@ -89,6 +92,7 @@
     void expectConfigChangedEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
                                                       int32_t expectedConfigId,
                                                       nsecs_t expectedVsyncPeriod);
+    void expectThrottleVsyncReceived(nsecs_t expectedTimestamp, uid_t);
 
     AsyncCallRecorder<void (*)(bool)> mVSyncSetEnabledCallRecorder;
     AsyncCallRecorder<void (*)(VSyncSource::Callback*)> mVSyncSetCallbackCallRecorder;
@@ -96,12 +100,18 @@
             mVSyncSetDurationCallRecorder;
     AsyncCallRecorder<void (*)()> mResyncCallRecorder;
     AsyncCallRecorder<void (*)(nsecs_t)> mInterceptVSyncCallRecorder;
+    AsyncCallRecorder<void (*)(nsecs_t, uid_t)> mThrottleVsyncCallRecorder;
     ConnectionEventRecorder mConnectionEventCallRecorder{0};
+    ConnectionEventRecorder mThrottledConnectionEventCallRecorder{0};
 
     MockVSyncSource* mVSyncSource;
     VSyncSource::Callback* mCallback = nullptr;
     std::unique_ptr<impl::EventThread> mThread;
     sp<MockEventThreadConnection> mConnection;
+    sp<MockEventThreadConnection> mThrottledConnection;
+
+    static constexpr uid_t mConnectionUid = 443;
+    static constexpr uid_t mThrottledConnectionUid = 177;
 };
 
 EventThreadTest::EventThreadTest() {
@@ -124,6 +134,9 @@
     createThread(std::move(vsyncSource));
     mConnection = createConnection(mConnectionEventCallRecorder,
                                    ISurfaceComposer::eConfigChangedDispatch);
+    mThrottledConnection =
+            createConnection(mThrottledConnectionEventCallRecorder,
+                             ISurfaceComposer::eConfigChangedDispatch, mThrottledConnectionUid);
 
     // A display must be connected for VSYNC events to be delivered.
     mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, true);
@@ -140,9 +153,15 @@
 }
 
 void EventThreadTest::createThread(std::unique_ptr<VSyncSource> source) {
+    const auto throttleVsync = [&](nsecs_t expectedVsyncTimestamp, uid_t uid) {
+        mThrottleVsyncCallRecorder.getInvocable()(expectedVsyncTimestamp, uid);
+        return (uid == mThrottledConnectionUid);
+    };
+
     mThread = std::make_unique<impl::EventThread>(std::move(source),
                                                   /*tokenManager=*/nullptr,
-                                                  mInterceptVSyncCallRecorder.getInvocable());
+                                                  mInterceptVSyncCallRecorder.getInvocable(),
+                                                  throttleVsync);
 
     // EventThread should register itself as VSyncSource callback.
     mCallback = expectVSyncSetCallbackCallReceived();
@@ -150,10 +169,11 @@
 }
 
 sp<EventThreadTest::MockEventThreadConnection> EventThreadTest::createConnection(
-        ConnectionEventRecorder& recorder, ISurfaceComposer::ConfigChanged configChanged) {
+        ConnectionEventRecorder& recorder, ISurfaceComposer::ConfigChanged configChanged,
+        uid_t ownerUid) {
     sp<MockEventThreadConnection> connection =
-            new MockEventThreadConnection(mThread.get(), mResyncCallRecorder.getInvocable(),
-                                          configChanged);
+            new MockEventThreadConnection(mThread.get(), ownerUid,
+                                          mResyncCallRecorder.getInvocable(), configChanged);
     EXPECT_CALL(*connection, postEvent(_)).WillRepeatedly(Invoke(recorder.getInvocable()));
     return connection;
 }
@@ -183,6 +203,13 @@
     EXPECT_EQ(expectedTimestamp, std::get<0>(args.value()));
 }
 
+void EventThreadTest::expectThrottleVsyncReceived(nsecs_t expectedTimestamp, uid_t uid) {
+    auto args = mThrottleVsyncCallRecorder.waitForCall();
+    ASSERT_TRUE(args.has_value());
+    EXPECT_EQ(expectedTimestamp, std::get<0>(args.value()));
+    EXPECT_EQ(uid, std::get<1>(args.value()));
+}
+
 void EventThreadTest::expectVsyncEventReceivedByConnection(
         const char* name, ConnectionEventRecorder& connectionEventRecorder,
         nsecs_t expectedTimestamp, unsigned expectedCount) {
@@ -267,13 +294,15 @@
     // The interceptor should receive the event, as well as the connection.
     mCallback->onVSyncEvent(123, 456, 789);
     expectInterceptCallReceived(123);
+    expectThrottleVsyncReceived(456, mConnectionUid);
     expectVsyncEventReceivedByConnection(123, 1u);
 
     // Use the received callback to signal a second vsync event.
-    // The interceptor should receive the event, but the the connection should
+    // The interceptor should receive the event, but the connection should
     // not as it was only interested in the first.
     mCallback->onVSyncEvent(456, 123, 0);
     expectInterceptCallReceived(456);
+    EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value());
     EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
 
     // EventThread should also detect that at this point that it does not need
@@ -323,16 +352,19 @@
     // interceptor, and the connection.
     mCallback->onVSyncEvent(123, 456, 789);
     expectInterceptCallReceived(123);
+    expectThrottleVsyncReceived(456, mConnectionUid);
     expectVsyncEventReceivedByConnection(123, 1u);
 
     // A second event should go to the same places.
     mCallback->onVSyncEvent(456, 123, 0);
     expectInterceptCallReceived(456);
+    expectThrottleVsyncReceived(123, mConnectionUid);
     expectVsyncEventReceivedByConnection(456, 2u);
 
     // A third event should go to the same places.
     mCallback->onVSyncEvent(789, 777, 111);
     expectInterceptCallReceived(789);
+    expectThrottleVsyncReceived(777, mConnectionUid);
     expectVsyncEventReceivedByConnection(789, 3u);
 }
 
@@ -346,16 +378,19 @@
     mCallback->onVSyncEvent(123, 456, 789);
     expectInterceptCallReceived(123);
     EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
+    EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value());
 
     // The second event will be seen by the interceptor and the connection.
     mCallback->onVSyncEvent(456, 123, 0);
     expectInterceptCallReceived(456);
     expectVsyncEventReceivedByConnection(456, 2u);
+    EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value());
 
     // The third event will be seen by the interceptor, and not the connection.
     mCallback->onVSyncEvent(789, 777, 744);
     expectInterceptCallReceived(789);
     EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
+    EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value());
 
     // The fourth event will be seen by the interceptor and the connection.
     mCallback->onVSyncEvent(101112, 7847, 86);
@@ -408,19 +443,19 @@
 }
 
 TEST_F(EventThreadTest, tracksEventConnections) {
-    EXPECT_EQ(1, mThread->getEventThreadConnectionCount());
+    EXPECT_EQ(2, mThread->getEventThreadConnectionCount());
     ConnectionEventRecorder errorConnectionEventRecorder{NO_MEMORY};
     sp<MockEventThreadConnection> errorConnection =
             createConnection(errorConnectionEventRecorder,
                              ISurfaceComposer::eConfigChangedSuppress);
     mThread->setVsyncRate(1, errorConnection);
-    EXPECT_EQ(2, mThread->getEventThreadConnectionCount());
+    EXPECT_EQ(3, mThread->getEventThreadConnectionCount());
     ConnectionEventRecorder secondConnectionEventRecorder{0};
     sp<MockEventThreadConnection> secondConnection =
             createConnection(secondConnectionEventRecorder,
                              ISurfaceComposer::eConfigChangedSuppress);
     mThread->setVsyncRate(1, secondConnection);
-    EXPECT_EQ(3, mThread->getEventThreadConnectionCount());
+    EXPECT_EQ(4, mThread->getEventThreadConnectionCount());
 
     // EventThread should enable vsync callbacks.
     expectVSyncSetEnabledCallReceived(true);
@@ -432,7 +467,7 @@
     expectVsyncEventReceivedByConnection("errorConnection", errorConnectionEventRecorder, 123, 1u);
     expectVsyncEventReceivedByConnection("successConnection", secondConnectionEventRecorder, 123,
                                          1u);
-    EXPECT_EQ(2, mThread->getEventThreadConnectionCount());
+    EXPECT_EQ(3, mThread->getEventThreadConnectionCount());
 }
 
 TEST_F(EventThreadTest, eventsDroppedIfNonfatalEventDeliveryError) {
@@ -514,5 +549,35 @@
     ASSERT_FALSE(args.has_value());
 }
 
+TEST_F(EventThreadTest, requestNextVsyncWithThrottleVsyncDoesntPostVSync) {
+    // Signal that we want the next vsync event to be posted to the throttled connection
+    mThread->requestNextVsync(mThrottledConnection);
+
+    // EventThread should immediately request a resync.
+    EXPECT_TRUE(mResyncCallRecorder.waitForCall().has_value());
+
+    // EventThread should enable vsync callbacks.
+    expectVSyncSetEnabledCallReceived(true);
+
+    // Use the received callback to signal a first vsync event.
+    // The interceptor should receive the event, but not the connection.
+    mCallback->onVSyncEvent(123, 456, 789);
+    expectInterceptCallReceived(123);
+    expectThrottleVsyncReceived(456, mThrottledConnectionUid);
+    mThrottledConnectionEventCallRecorder.waitForUnexpectedCall();
+
+    // Use the received callback to signal a second vsync event.
+    // The interceptor should receive the event, but the connection should
+    // not as it was only interested in the first.
+    mCallback->onVSyncEvent(456, 123, 0);
+    expectInterceptCallReceived(456);
+    expectThrottleVsyncReceived(123, mThrottledConnectionUid);
+    EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
+
+    // EventThread should not change the vsync state as it didn't send the event
+    // yet
+    EXPECT_FALSE(mVSyncSetEnabledCallRecorder.waitForUnexpectedCall().has_value());
+}
+
 } // namespace
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index 1f6f166..4762fd4 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -1471,6 +1471,34 @@
     EXPECT_EQ(KernelIdleTimerAction::TurnOff, refreshRateConfigs->getIdleTimerAction());
 }
 
+TEST_F(RefreshRateConfigsTest, RefreshRateDividerForUnknownUid) {
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_30);
+    EXPECT_EQ(1, refreshRateConfigs->getRefreshRateDividerForUid(1234));
+}
+
+TEST_F(RefreshRateConfigsTest, RefreshRateDividerForUid) {
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_30);
+    const uid_t uid = 1234;
+    refreshRateConfigs->setPreferredRefreshRateForUid(uid, 30);
+    EXPECT_EQ(1, refreshRateConfigs->getRefreshRateDividerForUid(uid));
+
+    refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_60);
+    EXPECT_EQ(2, refreshRateConfigs->getRefreshRateDividerForUid(uid));
+
+    refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_72);
+    EXPECT_EQ(1, refreshRateConfigs->getRefreshRateDividerForUid(uid));
+
+    refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
+    EXPECT_EQ(3, refreshRateConfigs->getRefreshRateDividerForUid(uid));
+
+    refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_120);
+    EXPECT_EQ(4, refreshRateConfigs->getRefreshRateDividerForUid(uid));
+}
+
 } // namespace
 } // namespace scheduler
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp
index 2c8178e..8cd8372 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp
@@ -131,12 +131,14 @@
 
     EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
     EXPECT_CALL(*eventThread, createEventConnection(_, _))
-            .WillOnce(Return(new EventThreadConnection(eventThread.get(), ResyncCallback(),
+            .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
+                                                       ResyncCallback(),
                                                        ISurfaceComposer::eConfigChangedSuppress)));
 
     EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
     EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
-            .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), ResyncCallback(),
+            .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
+                                                       ResyncCallback(),
                                                        ISurfaceComposer::eConfigChangedSuppress)));
 
     auto vsyncController = std::make_unique<mock::VsyncController>();
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index eee9400..509858a 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -42,7 +42,7 @@
     class MockEventThreadConnection : public android::EventThreadConnection {
     public:
         explicit MockEventThreadConnection(EventThread* eventThread)
-              : EventThreadConnection(eventThread, ResyncCallback(),
+              : EventThreadConnection(eventThread, /*callingUid=*/0, ResyncCallback(),
                                       ISurfaceComposer::eConfigChangedSuppress) {}
         ~MockEventThreadConnection() = default;
 
diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
index efee826..e25d501 100644
--- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
@@ -171,12 +171,14 @@
 
     EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
     EXPECT_CALL(*eventThread, createEventConnection(_, _))
-            .WillOnce(Return(new EventThreadConnection(eventThread.get(), ResyncCallback(),
+            .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
+                                                       ResyncCallback(),
                                                        ISurfaceComposer::eConfigChangedSuppress)));
 
     EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
     EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
-            .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), ResyncCallback(),
+            .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
+                                                       ResyncCallback(),
                                                        ISurfaceComposer::eConfigChangedSuppress)));
 
     auto vsyncController = std::make_unique<mock::VsyncController>();
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index 760bf65..68cf330 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -66,13 +66,15 @@
         EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
         EXPECT_CALL(*eventThread, createEventConnection(_, _))
                 .WillOnce(Return(
-                        new EventThreadConnection(eventThread.get(), ResyncCallback(),
+                        new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
+                                                  ResyncCallback(),
                                                   ISurfaceComposer::eConfigChangedSuppress)));
 
         EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
         EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
                 .WillOnce(Return(
-                        new EventThreadConnection(sfEventThread.get(), ResyncCallback(),
+                        new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
+                                                  ResyncCallback(),
                                                   ISurfaceComposer::eConfigChangedSuppress)));
 
         EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
index 1e5139c..0af5f30 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
@@ -52,6 +52,7 @@
     void setPeriod(nsecs_t) final {}
     void resetModel() final {}
     bool needsMoreSamples() const final { return false; }
+    bool isVSyncInPhase(nsecs_t, int) const final { return false; }
     void dump(std::string&) const final {}
 
 private:
@@ -88,6 +89,7 @@
     void setPeriod(nsecs_t) final {}
     void resetModel() final {}
     bool needsMoreSamples() const final { return false; }
+    bool isVSyncInPhase(nsecs_t, int) const final { return false; }
     void dump(std::string&) const final {}
 
 private:
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
index 69731fd..72b5396 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
@@ -48,6 +48,7 @@
     MOCK_METHOD1(setPeriod, void(nsecs_t));
     MOCK_METHOD0(resetModel, void());
     MOCK_CONST_METHOD0(needsMoreSamples, bool());
+    MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, int));
     MOCK_CONST_METHOD1(dump, void(std::string&));
 
     nsecs_t nextVSyncTime(nsecs_t timePoint) const {
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index d4cd11d..3d60479 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -59,16 +59,16 @@
 };
 
 TEST_F(VSyncPredictorTest, reportsAnticipatedPeriod) {
-    auto [slope, intercept] = tracker.getVSyncPredictionModel();
+    auto model = tracker.getVSyncPredictionModel();
 
-    EXPECT_THAT(slope, Eq(mPeriod));
-    EXPECT_THAT(intercept, Eq(0));
+    EXPECT_THAT(model.slope, Eq(mPeriod));
+    EXPECT_THAT(model.intercept, Eq(0));
 
     auto const changedPeriod = 2000;
     tracker.setPeriod(changedPeriod);
-    std::tie(slope, intercept) = tracker.getVSyncPredictionModel();
-    EXPECT_THAT(slope, Eq(changedPeriod));
-    EXPECT_THAT(intercept, Eq(0));
+    model = tracker.getVSyncPredictionModel();
+    EXPECT_THAT(model.slope, Eq(changedPeriod));
+    EXPECT_THAT(model.intercept, Eq(0));
 }
 
 TEST_F(VSyncPredictorTest, reportsSamplesNeededWhenHasNoDataPoints) {
@@ -264,17 +264,17 @@
     }
 
     auto const mMaxRoundingError = 100;
-    auto [slope, intercept] = tracker.getVSyncPredictionModel();
-    EXPECT_THAT(slope, IsCloseTo(fastPeriod, mMaxRoundingError));
-    EXPECT_THAT(intercept, IsCloseTo(0, mMaxRoundingError));
+    auto model = tracker.getVSyncPredictionModel();
+    EXPECT_THAT(model.slope, IsCloseTo(fastPeriod, mMaxRoundingError));
+    EXPECT_THAT(model.intercept, IsCloseTo(0, mMaxRoundingError));
 
     tracker.setPeriod(slowPeriod);
     for (auto const& timestamp : simulatedVsyncsSlow) {
         tracker.addVsyncTimestamp(timestamp);
     }
-    std::tie(slope, intercept) = tracker.getVSyncPredictionModel();
-    EXPECT_THAT(slope, IsCloseTo(slowPeriod, mMaxRoundingError));
-    EXPECT_THAT(intercept, IsCloseTo(0, mMaxRoundingError));
+    model = tracker.getVSyncPredictionModel();
+    EXPECT_THAT(model.slope, IsCloseTo(slowPeriod, mMaxRoundingError));
+    EXPECT_THAT(model.intercept, IsCloseTo(0, mMaxRoundingError));
 }
 
 TEST_F(VSyncPredictorTest, willBeAccurateUsingPriorResultsForRate) {
@@ -296,9 +296,9 @@
     for (auto const& timestamp : simulatedVsyncsFast) {
         tracker.addVsyncTimestamp(timestamp);
     }
-    auto [slope, intercept] = tracker.getVSyncPredictionModel();
-    EXPECT_THAT(slope, Eq(fastPeriod));
-    EXPECT_THAT(intercept, Eq(0));
+    auto model = tracker.getVSyncPredictionModel();
+    EXPECT_THAT(model.slope, Eq(fastPeriod));
+    EXPECT_THAT(model.intercept, Eq(0));
 
     tracker.setPeriod(slowPeriod);
     for (auto const& timestamp : simulatedVsyncsSlow) {
@@ -308,16 +308,16 @@
     // we had a model for 100ns mPeriod before, use that until the new samples are
     // sufficiently built up
     tracker.setPeriod(idealPeriod);
-    std::tie(slope, intercept) = tracker.getVSyncPredictionModel();
-    EXPECT_THAT(slope, Eq(fastPeriod));
-    EXPECT_THAT(intercept, Eq(0));
+    model = tracker.getVSyncPredictionModel();
+    EXPECT_THAT(model.slope, Eq(fastPeriod));
+    EXPECT_THAT(model.intercept, Eq(0));
 
     for (auto const& timestamp : simulatedVsyncsFast2) {
         tracker.addVsyncTimestamp(timestamp);
     }
-    std::tie(slope, intercept) = tracker.getVSyncPredictionModel();
-    EXPECT_THAT(slope, Eq(fastPeriod2));
-    EXPECT_THAT(intercept, Eq(0));
+    model = tracker.getVSyncPredictionModel();
+    EXPECT_THAT(model.slope, Eq(fastPeriod2));
+    EXPECT_THAT(model.intercept, Eq(0));
 }
 
 TEST_F(VSyncPredictorTest, idealModelPredictionsBeforeRegressionModelIsBuilt) {
@@ -407,11 +407,9 @@
         tracker.addVsyncTimestamp(i * realPeriod);
     }
 
-    EXPECT_THAT(std::get<0>(tracker.getVSyncPredictionModel()),
-                IsCloseTo(realPeriod, mMaxRoundingError));
+    EXPECT_THAT(tracker.getVSyncPredictionModel().slope, IsCloseTo(realPeriod, mMaxRoundingError));
     tracker.resetModel();
-    EXPECT_THAT(std::get<0>(tracker.getVSyncPredictionModel()),
-                IsCloseTo(idealPeriod, mMaxRoundingError));
+    EXPECT_THAT(tracker.getVSyncPredictionModel().slope, IsCloseTo(idealPeriod, mMaxRoundingError));
 }
 
 TEST_F(VSyncPredictorTest, slopeAlwaysValid) {
@@ -450,6 +448,33 @@
     EXPECT_THAT(intercept, Eq(0));
 }
 
+TEST_F(VSyncPredictorTest, isVSyncInPhase) {
+    auto last = mNow;
+    auto const bias = 10;
+    for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
+        EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
+        mNow += mPeriod - bias;
+        last = mNow;
+        tracker.addVsyncTimestamp(mNow);
+        mNow += bias;
+    }
+
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod - bias));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod - bias));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 990), Eq(mNow + 2 * mPeriod - bias));
+
+    const auto maxDivider = 5;
+    const auto maxPeriods = 15;
+    for (int divider = 1; divider < maxDivider; divider++) {
+        for (int i = 0; i < maxPeriods; i++) {
+            const bool expectedInPhase = (i % divider) == 0;
+            EXPECT_THAT(expectedInPhase, tracker.isVSyncInPhase(mNow + i * mPeriod - bias, divider))
+                    << "vsync at " << mNow + (i + 1) * mPeriod - bias << " is "
+                    << (expectedInPhase ? "not " : "") << "in phase for divider " << divider;
+        }
+    }
+}
+
 } // namespace android::scheduler
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
index 0dcaf26..a7568e4 100644
--- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
@@ -42,6 +42,7 @@
     MOCK_METHOD1(setPeriod, void(nsecs_t));
     MOCK_METHOD0(resetModel, void());
     MOCK_CONST_METHOD0(needsMoreSamples, bool());
+    MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, int));
     MOCK_CONST_METHOD1(dump, void(std::string&));
 };
 
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
index 03ddc85..de98025 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
@@ -33,6 +33,7 @@
     MOCK_METHOD1(setPeriod, void(nsecs_t));
     MOCK_METHOD0(resetModel, void());
     MOCK_CONST_METHOD0(needsMoreSamples, bool());
+    MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, int));
     MOCK_CONST_METHOD1(dump, void(std::string&));
 };