Merge "SF: Carve out LayerHandle"
diff --git a/cmds/dumpsys/tests/dumpsys_test.cpp b/cmds/dumpsys/tests/dumpsys_test.cpp
index f0c19b9..b8e5ce1 100644
--- a/cmds/dumpsys/tests/dumpsys_test.cpp
+++ b/cmds/dumpsys/tests/dumpsys_test.cpp
@@ -60,6 +60,7 @@
     MOCK_METHOD1(isDeclared, bool(const String16&));
     MOCK_METHOD1(getDeclaredInstances, Vector<String16>(const String16&));
     MOCK_METHOD1(updatableViaApex, std::optional<String16>(const String16&));
+    MOCK_METHOD1(getUpdatableNames, Vector<String16>(const String16&));
     MOCK_METHOD1(getConnectionInfo, std::optional<ConnectionInfo>(const String16&));
     MOCK_METHOD2(registerForNotifications, status_t(const String16&,
                                              const sp<LocalRegistrationCallback>&));
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index 3681d5b..2684f04 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -142,6 +142,26 @@
     return updatableViaApex;
 }
 
+static std::vector<std::string> getVintfUpdatableInstances(const std::string& apexName) {
+    std::vector<std::string> instances;
+
+    forEachManifest([&](const ManifestWithDescription& mwd) {
+        mwd.manifest->forEachInstance([&](const auto& manifestInstance) {
+            if (manifestInstance.format() == vintf::HalFormat::AIDL &&
+                manifestInstance.updatableViaApex().has_value() &&
+                manifestInstance.updatableViaApex().value() == apexName) {
+                std::string aname = manifestInstance.package() + "." +
+                        manifestInstance.interface() + "/" + manifestInstance.instance();
+                instances.push_back(aname);
+            }
+            return false; // continue
+        });
+        return false; // continue
+    });
+
+    return instances;
+}
+
 static std::optional<ConnectionInfo> getVintfConnectionInfo(const std::string& name) {
     AidlName aname;
     if (!AidlName::fill(name, &aname)) return std::nullopt;
@@ -512,6 +532,30 @@
     return Status::ok();
 }
 
+Status ServiceManager::getUpdatableNames([[maybe_unused]] const std::string& apexName,
+                                         std::vector<std::string>* outReturn) {
+    auto ctx = mAccess->getCallingContext();
+
+    std::vector<std::string> apexUpdatableInstances;
+#ifndef VENDORSERVICEMANAGER
+    apexUpdatableInstances = getVintfUpdatableInstances(apexName);
+#endif
+
+    outReturn->clear();
+
+    for (const std::string& instance : apexUpdatableInstances) {
+        if (mAccess->canFind(ctx, instance)) {
+            outReturn->push_back(instance);
+        }
+    }
+
+    if (outReturn->size() == 0 && apexUpdatableInstances.size() != 0) {
+        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denial");
+    }
+
+    return Status::ok();
+}
+
 Status ServiceManager::getConnectionInfo(const std::string& name,
                                          std::optional<ConnectionInfo>* outReturn) {
     auto ctx = mAccess->getCallingContext();
diff --git a/cmds/servicemanager/ServiceManager.h b/cmds/servicemanager/ServiceManager.h
index 07b79f8..b24c11c 100644
--- a/cmds/servicemanager/ServiceManager.h
+++ b/cmds/servicemanager/ServiceManager.h
@@ -49,6 +49,8 @@
     binder::Status getDeclaredInstances(const std::string& interface, std::vector<std::string>* outReturn) override;
     binder::Status updatableViaApex(const std::string& name,
                                     std::optional<std::string>* outReturn) override;
+    binder::Status getUpdatableNames(const std::string& apexName,
+                                     std::vector<std::string>* outReturn) override;
     binder::Status getConnectionInfo(const std::string& name,
                                      std::optional<ConnectionInfo>* outReturn) override;
     binder::Status registerClientCallback(const std::string& name, const sp<IBinder>& service,
diff --git a/cmds/servicemanager/servicemanager.microdroid.rc b/cmds/servicemanager/servicemanager.microdroid.rc
index c516043..8819e1e 100644
--- a/cmds/servicemanager/servicemanager.microdroid.rc
+++ b/cmds/servicemanager/servicemanager.microdroid.rc
@@ -5,5 +5,4 @@
     critical
     onrestart setprop servicemanager.ready false
     onrestart restart apexd
-    task_profiles ServiceCapacityLow
     shutdown critical
diff --git a/include/ftl/concat.h b/include/ftl/concat.h
index ded48f7..e0774d3 100644
--- a/include/ftl/concat.h
+++ b/include/ftl/concat.h
@@ -20,7 +20,9 @@
 
 namespace android::ftl {
 
-// Lightweight (not allocating nor sprintf-based) concatenation.
+// Lightweight (not allocating nor sprintf-based) concatenation. The variadic arguments can be
+// values of integral type (including bool and char), string literals, or strings whose length
+// is constrained:
 //
 //   std::string_view name = "Volume";
 //   ftl::Concat string(ftl::truncated<3>(name), ": ", -3, " dB");
diff --git a/include/ftl/details/concat.h b/include/ftl/details/concat.h
index 8ce949e..726ba02 100644
--- a/include/ftl/details/concat.h
+++ b/include/ftl/details/concat.h
@@ -19,6 +19,7 @@
 #include <functional>
 #include <string_view>
 
+#include <ftl/details/type_traits.h>
 #include <ftl/string.h>
 
 namespace android::ftl::details {
@@ -26,16 +27,42 @@
 template <typename T, typename = void>
 struct StaticString;
 
+// Booleans.
 template <typename T>
-struct StaticString<T, std::enable_if_t<std::is_integral_v<T>>> {
-  static constexpr std::size_t N = to_chars_length_v<T>;
+struct StaticString<T, std::enable_if_t<is_bool_v<T>>> {
+  static constexpr std::size_t N = 5;  // Length of "false".
 
-  explicit StaticString(T v) : view(to_chars(buffer, v)) {}
+  explicit constexpr StaticString(bool b) : view(b ? "true" : "false") {}
 
-  to_chars_buffer_t<T> buffer;
   const std::string_view view;
 };
 
+// Characters.
+template <typename T>
+struct StaticString<T, std::enable_if_t<is_char_v<T>>> {
+  static constexpr std::size_t N = 1;
+
+  explicit constexpr StaticString(char c) : character(c) {}
+
+  const char character;
+  const std::string_view view{&character, 1u};
+};
+
+// Integers, including the integer value of other character types like char32_t.
+template <typename T>
+struct StaticString<
+    T, std::enable_if_t<std::is_integral_v<remove_cvref_t<T>> && !is_bool_v<T> && !is_char_v<T>>> {
+  using U = remove_cvref_t<T>;
+  static constexpr std::size_t N = to_chars_length_v<U>;
+
+  // TODO: Mark this and to_chars as `constexpr` in C++23.
+  explicit StaticString(U v) : view(to_chars(buffer, v)) {}
+
+  to_chars_buffer_t<U> buffer;
+  const std::string_view view;
+};
+
+// Character arrays.
 template <std::size_t M>
 struct StaticString<const char (&)[M], void> {
   static constexpr std::size_t N = M - 1;
@@ -50,6 +77,7 @@
   std::string_view view;
 };
 
+// Strings with constrained length.
 template <std::size_t M>
 struct StaticString<Truncated<M>, void> {
   static constexpr std::size_t N = M;
diff --git a/include/ftl/details/type_traits.h b/include/ftl/details/type_traits.h
index 7092ec5..47bebc5 100644
--- a/include/ftl/details/type_traits.h
+++ b/include/ftl/details/type_traits.h
@@ -24,4 +24,10 @@
 template <typename U>
 using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<U>>;
 
+template <typename T>
+constexpr bool is_bool_v = std::is_same_v<remove_cvref_t<T>, bool>;
+
+template <typename T>
+constexpr bool is_char_v = std::is_same_v<remove_cvref_t<T>, char>;
+
 }  // namespace android::ftl::details
diff --git a/include/input/VelocityTracker.h b/include/input/VelocityTracker.h
index 294879e..62c3ae1 100644
--- a/include/input/VelocityTracker.h
+++ b/include/input/VelocityTracker.h
@@ -106,6 +106,9 @@
 
     ~VelocityTracker();
 
+    /** Return true if the axis is supported for velocity tracking, false otherwise. */
+    static bool isAxisSupported(int32_t axis);
+
     // Resets the velocity tracker state.
     void clear();
 
diff --git a/libs/arect/Android.bp b/libs/arect/Android.bp
index 76e3e66..5e539f2 100644
--- a/libs/arect/Android.bp
+++ b/libs/arect/Android.bp
@@ -49,6 +49,9 @@
         "com.android.media",
         "com.android.media.swcodec",
     ],
+    llndk: {
+        llndk_headers: true,
+    },
 }
 
 cc_library_static {
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index c4bb6d0..fdf4167 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -161,7 +161,7 @@
     ],
 
     header_libs: [
-        "libandroid_runtime_vm_headers",
+        "jni_headers",
     ],
 
     export_header_lib_headers: [
@@ -315,7 +315,7 @@
         },
         recovery: {
             exclude_header_libs: [
-                "libandroid_runtime_vm_headers",
+                "jni_headers",
             ],
         },
     },
@@ -484,9 +484,6 @@
         java: {
             enabled: false,
         },
-        cpp: {
-            gen_trace: false,
-        },
     },
 }
 
diff --git a/libs/binder/IServiceManager.cpp b/libs/binder/IServiceManager.cpp
index 05db774..a0c4334 100644
--- a/libs/binder/IServiceManager.cpp
+++ b/libs/binder/IServiceManager.cpp
@@ -81,6 +81,7 @@
     bool isDeclared(const String16& name) override;
     Vector<String16> getDeclaredInstances(const String16& interface) override;
     std::optional<String16> updatableViaApex(const String16& name) override;
+    Vector<String16> getUpdatableNames(const String16& apexName) override;
     std::optional<IServiceManager::ConnectionInfo> getConnectionInfo(const String16& name) override;
     class RegistrationWaiter : public android::os::BnServiceCallback {
     public:
@@ -479,6 +480,23 @@
     return declared ? std::optional<String16>(String16(declared.value().c_str())) : std::nullopt;
 }
 
+Vector<String16> ServiceManagerShim::getUpdatableNames(const String16& apexName) {
+    std::vector<std::string> out;
+    if (Status status = mTheRealServiceManager->getUpdatableNames(String8(apexName).c_str(), &out);
+        !status.isOk()) {
+        ALOGW("Failed to getUpdatableNames for %s: %s", String8(apexName).c_str(),
+              status.toString8().c_str());
+        return {};
+    }
+
+    Vector<String16> res;
+    res.setCapacity(out.size());
+    for (const std::string& instance : out) {
+        res.push(String16(instance.c_str()));
+    }
+    return res;
+}
+
 std::optional<IServiceManager::ConnectionInfo> ServiceManagerShim::getConnectionInfo(
         const String16& name) {
     std::optional<os::ConnectionInfo> connectionInfo;
diff --git a/libs/binder/OS.cpp b/libs/binder/OS.cpp
index 77e401f..ce60e33 100644
--- a/libs/binder/OS.cpp
+++ b/libs/binder/OS.cpp
@@ -67,7 +67,7 @@
     return RpcTransportCtxFactoryRaw::make();
 }
 
-int sendMessageOnSocket(
+ssize_t sendMessageOnSocket(
         const RpcTransportFd& socket, iovec* iovs, int niovs,
         const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) {
     if (ancillaryFds != nullptr && !ancillaryFds->empty()) {
@@ -112,7 +112,7 @@
     return TEMP_FAILURE_RETRY(sendmsg(socket.fd.get(), &msg, MSG_NOSIGNAL));
 }
 
-int receiveMessageFromSocket(
+ssize_t receiveMessageFromSocket(
         const RpcTransportFd& socket, iovec* iovs, int niovs,
         std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) {
     if (ancillaryFds != nullptr) {
diff --git a/libs/binder/OS.h b/libs/binder/OS.h
index 0d38968..fecae31 100644
--- a/libs/binder/OS.h
+++ b/libs/binder/OS.h
@@ -33,11 +33,11 @@
 
 std::unique_ptr<RpcTransportCtxFactory> makeDefaultRpcTransportCtxFactory();
 
-int sendMessageOnSocket(
+ssize_t sendMessageOnSocket(
         const RpcTransportFd& socket, iovec* iovs, int niovs,
         const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds);
 
-int receiveMessageFromSocket(
+ssize_t receiveMessageFromSocket(
         const RpcTransportFd& socket, iovec* iovs, int niovs,
         std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds);
 
diff --git a/libs/binder/RpcSession.cpp b/libs/binder/RpcSession.cpp
index 7d6bcfc..ce6ef2b 100644
--- a/libs/binder/RpcSession.cpp
+++ b/libs/binder/RpcSession.cpp
@@ -46,8 +46,8 @@
 #include "Utils.h"
 
 #if defined(__ANDROID__) && !defined(__ANDROID_RECOVERY__)
-#include <android_runtime/vm.h>
 #include <jni.h>
+extern "C" JavaVM* AndroidRuntimeGetJavaVM();
 #endif
 
 namespace android {
@@ -408,10 +408,11 @@
                             "Unable to detach thread. No JavaVM, but it was present before!");
 
         LOG_RPC_DETAIL("Detaching current thread from JVM");
-        if (vm->DetachCurrentThread() != JNI_OK) {
+        int ret = vm->DetachCurrentThread();
+        if (ret == JNI_OK) {
             mAttached = false;
         } else {
-            ALOGW("Unable to detach current thread from JVM");
+            ALOGW("Unable to detach current thread from JVM (%d)", ret);
         }
     }
 
diff --git a/libs/binder/RpcTransportRaw.cpp b/libs/binder/RpcTransportRaw.cpp
index 1912d14..cd067bf 100644
--- a/libs/binder/RpcTransportRaw.cpp
+++ b/libs/binder/RpcTransportRaw.cpp
@@ -61,7 +61,8 @@
             override {
         bool sentFds = false;
         auto send = [&](iovec* iovs, int niovs) -> ssize_t {
-            int ret = sendMessageOnSocket(mSocket, iovs, niovs, sentFds ? nullptr : ancillaryFds);
+            ssize_t ret =
+                    sendMessageOnSocket(mSocket, iovs, niovs, sentFds ? nullptr : ancillaryFds);
             sentFds |= ret > 0;
             return ret;
         };
diff --git a/libs/binder/aidl/android/os/IServiceManager.aidl b/libs/binder/aidl/android/os/IServiceManager.aidl
index 5880c0a..0fb1615 100644
--- a/libs/binder/aidl/android/os/IServiceManager.aidl
+++ b/libs/binder/aidl/android/os/IServiceManager.aidl
@@ -114,6 +114,12 @@
     @nullable @utf8InCpp String updatableViaApex(@utf8InCpp String name);
 
     /**
+     * Returns all instances which are updatable via the APEX. Instance names are fully qualified
+     * like `pack.age.IFoo/default`.
+     */
+    @utf8InCpp String[] getUpdatableNames(@utf8InCpp String apexName);
+
+    /**
      * If connection info is available for the given instance, returns the ConnectionInfo
      */
     @nullable ConnectionInfo getConnectionInfo(@utf8InCpp String name);
diff --git a/libs/binder/include/binder/IServiceManager.h b/libs/binder/include/binder/IServiceManager.h
index 413c97f..79e771f 100644
--- a/libs/binder/include/binder/IServiceManager.h
+++ b/libs/binder/include/binder/IServiceManager.h
@@ -115,6 +115,12 @@
     virtual std::optional<String16> updatableViaApex(const String16& name) = 0;
 
     /**
+     * Returns all instances which are updatable via the APEX. Instance names are fully qualified
+     * like `pack.age.IFoo/default`.
+     */
+    virtual Vector<String16> getUpdatableNames(const String16& apexName) = 0;
+
+    /**
      * If this instance has declared remote connection information, returns
      * the ConnectionInfo.
      */
diff --git a/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp b/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
index 883ae31..e4a9f99 100644
--- a/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
+++ b/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
@@ -40,7 +40,7 @@
 bool RunVsockRpcServerWithFactory(AIBinder* (*factory)(unsigned int cid, void* context),
                                   void* factoryContext, unsigned int port);
 
-AIBinder* RpcClient(unsigned int cid, unsigned int port);
+AIBinder* VsockRpcClient(unsigned int cid, unsigned int port);
 
 // Connect to an RPC server with preconnected file descriptors.
 //
diff --git a/libs/binder/libbinder_rpc_unstable.cpp b/libs/binder/libbinder_rpc_unstable.cpp
index 49686e1..1f38bb9 100644
--- a/libs/binder/libbinder_rpc_unstable.cpp
+++ b/libs/binder/libbinder_rpc_unstable.cpp
@@ -74,7 +74,7 @@
     return RunVsockRpcServerCallback(service, port, nullptr, nullptr);
 }
 
-AIBinder* RpcClient(unsigned int cid, unsigned int port) {
+AIBinder* VsockRpcClient(unsigned int cid, unsigned int port) {
     auto session = RpcSession::make();
     if (status_t status = session->setupVsockClient(cid, port); status != OK) {
         LOG(ERROR) << "Failed to set up vsock client with CID " << cid << " and port " << port
diff --git a/libs/binder/libbinder_rpc_unstable.map.txt b/libs/binder/libbinder_rpc_unstable.map.txt
index 15b6ee9..347831a 100644
--- a/libs/binder/libbinder_rpc_unstable.map.txt
+++ b/libs/binder/libbinder_rpc_unstable.map.txt
@@ -2,7 +2,7 @@
   global:
     RunVsockRpcServer;
     RunVsockRpcServerCallback;
-    RpcClient;
+    VsockRpcClient;
     RpcPreconnectedClient;
   local:
     *;
diff --git a/libs/binder/ndk/include_platform/android/binder_manager.h b/libs/binder/ndk/include_platform/android/binder_manager.h
index dfa8ea2..c234270 100644
--- a/libs/binder/ndk/include_platform/android/binder_manager.h
+++ b/libs/binder/ndk/include_platform/android/binder_manager.h
@@ -143,6 +143,17 @@
 bool AServiceManager_isUpdatableViaApex(const char* instance) __INTRODUCED_IN(31);
 
 /**
+ * Returns the APEX name if a service is declared as updatable via an APEX module.
+ *
+ * \param instance identifier of the service
+ * \param context to pass to callback
+ * \param callback taking the APEX name (e.g. 'com.android.foo') and context
+ */
+void AServiceManager_getUpdatableApexName(const char* instance, void* context,
+                                          void (*callback)(const char*, void*))
+        __INTRODUCED_IN(__ANDROID_API_U__);
+
+/**
  * Prevent lazy services without client from shutting down their process
  *
  * This should only be used if it is every eventually set to false. If a
diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index 259a736..32ca564 100644
--- a/libs/binder/ndk/libbinder_ndk.map.txt
+++ b/libs/binder/ndk/libbinder_ndk.map.txt
@@ -152,6 +152,11 @@
     AParcel_unmarshal;
 };
 
+LIBBINDER_NDK34 { # introduced=UpsideDownCake
+  global:
+    AServiceManager_getUpdatableApexName; # systemapi
+};
+
 LIBBINDER_NDK_PLATFORM {
   global:
     AParcel_getAllowFds;
diff --git a/libs/binder/ndk/service_manager.cpp b/libs/binder/ndk/service_manager.cpp
index 7649a26..a12d0e9 100644
--- a/libs/binder/ndk/service_manager.cpp
+++ b/libs/binder/ndk/service_manager.cpp
@@ -113,6 +113,18 @@
     sp<IServiceManager> sm = defaultServiceManager();
     return sm->updatableViaApex(String16(instance)) != std::nullopt;
 }
+void AServiceManager_getUpdatableApexName(const char* instance, void* context,
+                                          void (*callback)(const char*, void*)) {
+    CHECK_NE(instance, nullptr);
+    // context may be nullptr
+    CHECK_NE(callback, nullptr);
+
+    sp<IServiceManager> sm = defaultServiceManager();
+    std::optional<String16> updatableViaApex = sm->updatableViaApex(String16(instance));
+    if (updatableViaApex.has_value()) {
+        callback(String8(updatableViaApex.value()).c_str(), context);
+    }
+}
 void AServiceManager_forceLazyServicesPersist(bool persist) {
     auto serviceRegistrar = android::binder::LazyServiceRegistrar::getInstance();
     serviceRegistrar.forcePersist(persist);
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index 01b9472..e221e4c 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -33,13 +33,15 @@
 #include <binder/IResultReceiver.h>
 #include <binder/IServiceManager.h>
 #include <binder/IShellCallback.h>
-
 #include <sys/prctl.h>
+
 #include <chrono>
 #include <condition_variable>
 #include <iostream>
 #include <mutex>
+#include <optional>
 #include <thread>
+
 #include "android/binder_ibinder.h"
 
 using namespace android;
@@ -337,6 +339,16 @@
     EXPECT_EQ(isUpdatable, false);
 }
 
+TEST(NdkBinder, GetUpdatableViaApex) {
+    std::optional<std::string> updatableViaApex;
+    AServiceManager_getUpdatableApexName(
+            "android.hardware.light.ILights/default", &updatableViaApex,
+            [](const char* apexName, void* context) {
+                *static_cast<std::optional<std::string>*>(context) = apexName;
+            });
+    EXPECT_EQ(updatableViaApex, std::nullopt) << *updatableViaApex;
+}
+
 // This is too slow
 TEST(NdkBinder, CheckLazyServiceShutDown) {
     ndk::SpAIBinder binder(AServiceManager_waitForService(kLazyBinderNdkUnitTestService));
diff --git a/libs/binder/rust/rpcbinder/src/client.rs b/libs/binder/rust/rpcbinder/src/client.rs
index 743800b..4343ff4 100644
--- a/libs/binder/rust/rpcbinder/src/client.rs
+++ b/libs/binder/rust/rpcbinder/src/client.rs
@@ -22,9 +22,9 @@
 
 /// Connects to an RPC Binder server over vsock.
 pub fn get_vsock_rpc_service(cid: u32, port: u32) -> Option<SpIBinder> {
-    // SAFETY: AIBinder returned by RpcClient has correct reference count, and the ownership can
-    // safely be taken by new_spibinder.
-    unsafe { new_spibinder(binder_rpc_unstable_bindgen::RpcClient(cid, port)) }
+    // SAFETY: AIBinder returned by VsockRpcClient has correct reference count,
+    // and the ownership can safely be taken by new_spibinder.
+    unsafe { new_spibinder(binder_rpc_unstable_bindgen::VsockRpcClient(cid, port)) }
 }
 
 /// Connects to an RPC Binder server for a particular interface over vsock.
diff --git a/libs/binder/servicedispatcher.cpp b/libs/binder/servicedispatcher.cpp
index 777f3c9..692cc95 100644
--- a/libs/binder/servicedispatcher.cpp
+++ b/libs/binder/servicedispatcher.cpp
@@ -156,6 +156,10 @@
                                              std::optional<std::string>* _aidl_return) override {
         return mImpl->updatableViaApex(name, _aidl_return);
     }
+    android::binder::Status getUpdatableNames(const std::string& apexName,
+                                              std::vector<std::string>* _aidl_return) override {
+        return mImpl->getUpdatableNames(apexName, _aidl_return);
+    }
     android::binder::Status getConnectionInfo(
             const std::string& name,
             std::optional<android::os::ConnectionInfo>* _aidl_return) override {
diff --git a/libs/binder/trusty/OS.cpp b/libs/binder/trusty/OS.cpp
index 397ff41..8ec9823 100644
--- a/libs/binder/trusty/OS.cpp
+++ b/libs/binder/trusty/OS.cpp
@@ -59,14 +59,14 @@
     return RpcTransportCtxFactoryTipcTrusty::make();
 }
 
-int sendMessageOnSocket(
+ssize_t sendMessageOnSocket(
         const RpcTransportFd& /* socket */, iovec* /* iovs */, int /* niovs */,
         const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* /* ancillaryFds */) {
     errno = ENOTSUP;
     return -1;
 }
 
-int receiveMessageFromSocket(
+ssize_t receiveMessageFromSocket(
         const RpcTransportFd& /* socket */, iovec* /* iovs */, int /* niovs */,
         std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* /* ancillaryFds */) {
     errno = ENOTSUP;
diff --git a/libs/fakeservicemanager/ServiceManager.cpp b/libs/fakeservicemanager/ServiceManager.cpp
index 6c6d7f3..480ec79 100644
--- a/libs/fakeservicemanager/ServiceManager.cpp
+++ b/libs/fakeservicemanager/ServiceManager.cpp
@@ -78,6 +78,11 @@
     return std::nullopt;
 }
 
+Vector<String16> ServiceManager::getUpdatableNames(const String16& apexName) {
+    (void)apexName;
+    return {};
+}
+
 std::optional<IServiceManager::ConnectionInfo> ServiceManager::getConnectionInfo(
         const String16& name) {
     (void)name;
diff --git a/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h b/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h
index e0af5d4..ee0637e 100644
--- a/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h
+++ b/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h
@@ -52,6 +52,8 @@
 
     std::optional<String16> updatableViaApex(const String16& name) override;
 
+    Vector<String16> getUpdatableNames(const String16& apexName) override;
+
     std::optional<IServiceManager::ConnectionInfo> getConnectionInfo(const String16& name) override;
 
     status_t registerForNotifications(const String16& name,
diff --git a/libs/ftl/concat_test.cpp b/libs/ftl/concat_test.cpp
index 8ecb1b2..771f054 100644
--- a/libs/ftl/concat_test.cpp
+++ b/libs/ftl/concat_test.cpp
@@ -28,8 +28,25 @@
   EXPECT_EQ(string.c_str()[string.size()], '\0');
 }
 
+TEST(Concat, Characters) {
+  EXPECT_EQ(ftl::Concat(u'a', ' ', U'b').str(), "97 98");
+}
+
+TEST(Concat, References) {
+  int i[] = {-1, 2};
+  unsigned u = 3;
+  EXPECT_EQ(ftl::Concat(i[0], std::as_const(i[1]), u).str(), "-123");
+
+  const bool b = false;
+  const char c = 'o';
+  EXPECT_EQ(ftl::Concat(b, "tt", c).str(), "falsetto");
+}
+
 namespace {
 
+static_assert(ftl::Concat{true, false, true}.str() == "truefalsetrue");
+static_assert(ftl::Concat{':', '-', ')'}.str() == ":-)");
+
 static_assert(ftl::Concat{"foo"}.str() == "foo");
 static_assert(ftl::Concat{ftl::truncated<3>("foobar")}.str() == "foo");
 
diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp
index 4a4f734..19b4684 100644
--- a/libs/input/VelocityTracker.cpp
+++ b/libs/input/VelocityTracker.cpp
@@ -150,6 +150,10 @@
 VelocityTracker::~VelocityTracker() {
 }
 
+bool VelocityTracker::isAxisSupported(int32_t axis) {
+    return DEFAULT_STRATEGY_BY_AXIS.find(axis) != DEFAULT_STRATEGY_BY_AXIS.end();
+}
+
 void VelocityTracker::configureStrategy(int32_t axis) {
     const bool isDifferentialAxis = DIFFERENTIAL_AXES.find(axis) != DIFFERENTIAL_AXES.end();
 
diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp
index bd12663..54feea2 100644
--- a/libs/input/tests/VelocityTracker_test.cpp
+++ b/libs/input/tests/VelocityTracker_test.cpp
@@ -299,6 +299,26 @@
 }
 
 /*
+ *================== VelocityTracker tests that do not require test motion data ====================
+ */
+TEST(SimpleVelocityTrackerTest, TestSupportedAxis) {
+    // Note that we are testing up to the max possible axis value, plus 3 more values. We are going
+    // beyond the max value to add a bit more protection. "3" is chosen arbitrarily to cover a few
+    // more values beyond the max.
+    for (int32_t axis = 0; axis <= AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE + 3; axis++) {
+        switch (axis) {
+            case AMOTION_EVENT_AXIS_X:
+            case AMOTION_EVENT_AXIS_Y:
+            case AMOTION_EVENT_AXIS_SCROLL:
+                EXPECT_TRUE(VelocityTracker::isAxisSupported(axis)) << axis << " is supported";
+                break;
+            default:
+                EXPECT_FALSE(VelocityTracker::isAxisSupported(axis)) << axis << " is NOT supported";
+        }
+    }
+}
+
+/*
  * ================== VelocityTracker tests generated manually =====================================
  */
 TEST_F(VelocityTrackerTest, TestDefaultStrategiesForPlanarAxes) {
diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp
index 285f8d5..3ab2ba8 100644
--- a/libs/jpegrecoverymap/Android.bp
+++ b/libs/jpegrecoverymap/Android.bp
@@ -31,4 +31,36 @@
     srcs: [
         "recoverymap.cpp",
     ],
+
+    shared_libs: [
+        "libutils",
+    ],
+}
+
+cc_library_static {
+    name: "libjpegencoder",
+
+    shared_libs: [
+        "libjpeg",
+    ],
+
+    export_include_dirs: ["include"],
+
+    srcs: [
+        "jpegencoder.cpp",
+    ],
+}
+
+cc_library_static {
+    name: "libjpegdecoder",
+
+    shared_libs: [
+        "libjpeg",
+    ],
+
+    export_include_dirs: ["include"],
+
+    srcs: [
+        "jpegdecoder.cpp",
+    ],
 }
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
new file mode 100644
index 0000000..2ab7550
--- /dev/null
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
@@ -0,0 +1,62 @@
+
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
+#include <cstdio>
+extern "C" {
+#include <jerror.h>
+#include <jpeglib.h>
+}
+#include <utils/Errors.h>
+#include <vector>
+namespace android::recoverymap {
+/*
+ * Encapsulates a converter from JPEG to raw image (YUV420planer or grey-scale) format.
+ * This class is not thread-safe.
+ */
+class JpegDecoder {
+public:
+    JpegDecoder();
+    ~JpegDecoder();
+    /*
+     * Decompresses JPEG image to raw image (YUV420planer or grey-scale) format. After calling
+     * this method, call getDecompressedImage() to get the image.
+     * Returns false if decompressing the image fails.
+     */
+    bool decompressImage(const void* image, int length);
+    /*
+     * Returns the decompressed raw image buffer pointer. This method must be called only after
+     * calling decompressImage().
+     */
+    const void* getDecompressedImagePtr();
+    /*
+     * Returns the decompressed raw image buffer size. This method must be called only after
+     * calling decompressImage().
+     */
+    size_t getDecompressedImageSize();
+private:
+    bool decode(const void* image, int length);
+    // Returns false if errors occur.
+    bool decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, bool isSingleChannel);
+    bool decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest);
+    bool decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest);
+    // Process 16 lines of Y and 16 lines of U/V each time.
+    // We must pass at least 16 scanlines according to libjpeg documentation.
+    static const int kCompressBatchSize = 16;
+    // The buffer that holds the compressed result.
+    std::vector<JOCTET> mResultBuffer;
+};
+} /* namespace android  */
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h
new file mode 100644
index 0000000..9641fda
--- /dev/null
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
+#include <cstdio>
+
+extern "C" {
+#include <jerror.h>
+#include <jpeglib.h>
+}
+
+#include <utils/Errors.h>
+#include <vector>
+
+namespace android::recoverymap {
+
+/*
+ * Encapsulates a converter from raw image (YUV420planer or grey-scale) to JPEG format.
+ * This class is not thread-safe.
+ */
+class JpegEncoder {
+public:
+    JpegEncoder();
+    ~JpegEncoder();
+
+    /*
+     * Compresses YUV420Planer image to JPEG format. After calling this method, call
+     * getCompressedImage() to get the image. |quality| is the jpeg image quality parameter to use.
+     * It ranges from 1 (poorest quality) to 100 (highest quality). |iccBuffer| is the buffer of
+     * ICC segment which will be added to the compressed image.
+     * Returns false if errors occur during compression.
+     */
+    bool compressImage(const void* image, int width, int height, int quality,
+                       const void* iccBuffer, unsigned int iccSize, bool isSingleChannel = false);
+
+    /*
+     * Returns the compressed JPEG buffer pointer. This method must be called only after calling
+     * compressImage().
+     */
+    const void* getCompressedImagePtr();
+
+    /*
+     * Returns the compressed JPEG buffer size. This method must be called only after calling
+     * compressImage().
+     */
+    size_t getCompressedImageSize();
+
+private:
+    // initDestination(), emptyOutputBuffer() and emptyOutputBuffer() are callback functions to be
+    // passed into jpeg library.
+    static void initDestination(j_compress_ptr cinfo);
+    static boolean emptyOutputBuffer(j_compress_ptr cinfo);
+    static void terminateDestination(j_compress_ptr cinfo);
+    static void outputErrorMessage(j_common_ptr cinfo);
+
+    // Returns false if errors occur.
+    bool encode(const void* inYuv, int width, int height, int jpegQuality,
+                const void* iccBuffer, unsigned int iccSize, bool isSingleChannel);
+    void setJpegDestination(jpeg_compress_struct* cinfo);
+    void setJpegCompressStruct(int width, int height, int quality, jpeg_compress_struct* cinfo,
+                               bool isSingleChannel);
+    // Returns false if errors occur.
+    bool compress(jpeg_compress_struct* cinfo, const uint8_t* image, bool isSingleChannel);
+    bool compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv);
+    bool compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image);
+
+    // The block size for encoded jpeg image buffer.
+    static const int kBlockSize = 16384;
+    // Process 16 lines of Y and 16 lines of U/V each time.
+    // We must pass at least 16 scanlines according to libjpeg documentation.
+    static const int kCompressBatchSize = 16;
+
+    // The buffer that holds the compressed result.
+    std::vector<JOCTET> mResultBuffer;
+};
+
+} /* namespace android  */
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
index 6949f85..15eca1e 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
+ #include <utils/Errors.h>
+
 namespace android::recoverymap {
 
 /*
  * Holds information for uncompressed image or recovery map.
  */
-struct jpeg_r_uncompressed_struct {
+struct jpegr_uncompressed_struct {
     // Pointer to the data location.
     void* data;
     // Width of the recovery map or image in pixels.
@@ -31,37 +33,90 @@
 /*
  * Holds information for compressed image or recovery map.
  */
-struct jpeg_r_compressed_struct {
+struct jpegr_compressed_struct {
     // Pointer to the data location.
     void* data;
     // Data length;
     int length;
 };
 
-typedef struct jpeg_r_uncompressed_struct* j_r_uncompressed_ptr;
-typedef struct jpeg_r_compressed_struct* j_r_compressed_ptr;
+typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr;
+typedef struct jpegr_compressed_struct* jr_compressed_ptr;
 
 class RecoveryMap {
 public:
     /*
+     * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV.
+     *
+     * Generate recovery map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append
+     * the recovery map to the end of the compressed JPEG.
+     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
+     * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
+     * @param dest destination of the compressed JPEGR image
+     * @return NO_ERROR if encoding succeeds, error code if error occurs.
+     */
+    status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+                         jr_uncompressed_ptr uncompressed_yuv_420_image,
+                         void* dest);
+
+    /*
+     * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV.
+     *
+     * Generate recovery map from the HDR and SDR inputs, append the recovery map to the end of the
+     * compressed JPEG.
+     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
+     * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
+     * @param compressed_jpeg_image compressed 8-bit JPEG image
+     * @param dest destination of the compressed JPEGR image
+     * @return NO_ERROR if encoding succeeds, error code if error occurs.
+     */
+    status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+                         jr_uncompressed_ptr uncompressed_yuv_420_image,
+                         void* compressed_jpeg_image,
+                         void* dest);
+
+    /*
+     * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV.
+     *
+     * Decode the compressed 8-bit JPEG image to YUV SDR, generate recovery map from the HDR input
+     * and the decoded SDR result, append the recovery map to the end of the compressed JPEG.
+     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
+     * @param compressed_jpeg_image compressed 8-bit JPEG image
+     * @param dest destination of the compressed JPEGR image
+     * @return NO_ERROR if encoding succeeds, error code if error occurs.
+     */
+    status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+                         void* compressed_jpeg_image,
+                         void* dest);
+
+    /*
+     * Decompress JPEGR image.
+     *
+     * @param compressed_jpegr_image compressed JPEGR image
+     * @param dest destination of the uncompressed JPEGR image
+     * @return NO_ERROR if decoding succeeds, error code if error occurs.
+     */
+    status_t decodeJPEGR(void* compressed_jpegr_image, jr_uncompressed_ptr dest);
+private:
+    /*
      * This method is called in the decoding pipeline. It will decode the recovery map.
      *
      * @param compressed_recovery_map compressed recovery map
      * @param dest decoded recover map
-     * @return true if decoding succeeds
+     * @return NO_ERROR if decoding succeeds, error code if error occurs.
      */
-    bool decodeRecoveryMap(j_r_compressed_ptr compressed_recovery_map,
-                           j_r_uncompressed_ptr dest);
+    status_t decodeRecoveryMap(jr_compressed_ptr compressed_recovery_map,
+                               jr_uncompressed_ptr dest);
 
     /*
      * This method is called in the encoding pipeline. It will encode the recovery map.
      *
      * @param uncompressed_recovery_map uncompressed recovery map
      * @param dest encoded recover map
-     * @return true if encoding succeeds
+     * @return NO_ERROR if encoding succeeds, error code if error occurs.
      */
-    bool encodeRecoveryMap(j_r_uncompressed_ptr uncompressed_recovery_map,
-                           j_r_compressed_ptr dest);
+    status_t encodeRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
+                               jr_compressed_ptr dest);
 
     /*
      * This method is called in the encoding pipeline. It will take the uncompressed 8-bit and
@@ -70,11 +125,11 @@
      * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
      * @param uncompressed_p010_image uncompressed HDR image in P010 color format
      * @param dest recover map
-     * @return true if calculation succeeds
+     * @return NO_ERROR if calculation succeeds, error code if error occurs.
      */
-    bool generateRecoveryMap(j_r_uncompressed_ptr uncompressed_yuv_420_image,
-                             j_r_uncompressed_ptr uncompressed_p010_image,
-                             j_r_uncompressed_ptr dest);
+    status_t generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
+                                 jr_uncompressed_ptr uncompressed_p010_image,
+                                 jr_uncompressed_ptr dest);
 
     /*
      * This method is called in the decoding pipeline. It will take the uncompressed (decoded)
@@ -84,20 +139,21 @@
      * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
      * @param uncompressed_recovery_map uncompressed recovery map
      * @param dest reconstructed HDR image
-     * @return true if calculation succeeds
+     * @return NO_ERROR if calculation succeeds, error code if error occurs.
      */
-    bool applyRecoveryMap(j_r_uncompressed_ptr uncompressed_yuv_420_image,
-                          j_r_uncompressed_ptr uncompressed_recovery_map,
-                          j_r_uncompressed_ptr dest);
+    status_t applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
+                              jr_uncompressed_ptr uncompressed_recovery_map,
+                              jr_uncompressed_ptr dest);
 
     /*
      * This method is called in the decoding pipeline. It will read XMP metadata to find the start
      * position of the compressed recovery map, and will extract the compressed recovery map.
      *
-     * @param compressed_jpeg_r_image compressed JPEG_R image
-     * @return compressed recovery map
+     * @param compressed_jpegr_image compressed JPEGR image
+     * @param dest destination of compressed recovery map
+     * @return NO_ERROR if calculation succeeds, error code if error occurs.
      */
-    j_r_compressed_ptr extractRecoveryMap(void* compressed_jpeg_r_image);
+    status_t extractRecoveryMap(void* compressed_jpegr_image, jr_compressed_ptr dest);
 
     /*
      * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image
@@ -106,10 +162,12 @@
      *
      * @param compressed_jpeg_image compressed 8-bit JPEG image
      * @param compress_recovery_map compressed recover map
-     * @return compressed JPEG_R image
+     * @param dest compressed JPEGR image
+     * @return NO_ERROR if calculation succeeds, error code if error occurs.
      */
-    void* appendRecoveryMap(void* compressed_jpeg_image,
-                            j_r_compressed_ptr compressed_recovery_map);
+    status_t appendRecoveryMap(void* compressed_jpeg_image,
+                               jr_compressed_ptr compressed_recovery_map,
+                               void* dest);
 };
 
 } // namespace android::recoverymap
diff --git a/libs/jpegrecoverymap/jpegdecoder.cpp b/libs/jpegrecoverymap/jpegdecoder.cpp
new file mode 100644
index 0000000..22a5389
--- /dev/null
+++ b/libs/jpegrecoverymap/jpegdecoder.cpp
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jpegrecoverymap/jpegdecoder.h>
+
+#include <cutils/log.h>
+
+#include <errno.h>
+#include <setjmp.h>
+
+namespace android::recoverymap {
+struct jpegr_source_mgr : jpeg_source_mgr {
+    jpegr_source_mgr(const uint8_t* ptr, int len);
+    ~jpegr_source_mgr();
+
+    const uint8_t* mBufferPtr;
+    size_t mBufferLength;
+};
+
+struct jpegrerror_mgr {
+    struct jpeg_error_mgr pub;
+    jmp_buf setjmp_buffer;
+};
+
+static void jpegr_init_source(j_decompress_ptr cinfo) {
+    jpegr_source_mgr* src = static_cast<jpegr_source_mgr*>(cinfo->src);
+    src->next_input_byte = static_cast<const JOCTET*>(src->mBufferPtr);
+    src->bytes_in_buffer = src->mBufferLength;
+}
+
+static boolean jpegr_fill_input_buffer(j_decompress_ptr /* cinfo */) {
+    ALOGE("%s : should not get here", __func__);
+    return FALSE;
+}
+
+static void jpegr_skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
+    jpegr_source_mgr* src = static_cast<jpegr_source_mgr*>(cinfo->src);
+
+    if (num_bytes > static_cast<long>(src->bytes_in_buffer)) {
+        ALOGE("jpegr_skip_input_data - num_bytes > (long)src->bytes_in_buffer");
+    } else {
+        src->next_input_byte += num_bytes;
+        src->bytes_in_buffer -= num_bytes;
+    }
+}
+
+static void jpegr_term_source(j_decompress_ptr /*cinfo*/) {}
+
+jpegr_source_mgr::jpegr_source_mgr(const uint8_t* ptr, int len) :
+        mBufferPtr(ptr), mBufferLength(len) {
+    init_source = jpegr_init_source;
+    fill_input_buffer = jpegr_fill_input_buffer;
+    skip_input_data = jpegr_skip_input_data;
+    resync_to_restart = jpeg_resync_to_restart;
+    term_source = jpegr_term_source;
+}
+
+jpegr_source_mgr::~jpegr_source_mgr() {}
+
+static void jpegrerror_exit(j_common_ptr cinfo) {
+    jpegrerror_mgr* err = reinterpret_cast<jpegrerror_mgr*>(cinfo->err);
+    longjmp(err->setjmp_buffer, 1);
+}
+
+JpegDecoder::JpegDecoder() {
+}
+
+JpegDecoder::~JpegDecoder() {
+}
+
+bool JpegDecoder::decompressImage(const void* image, int length) {
+    if (image == nullptr || length <= 0) {
+        ALOGE("Image size can not be handled: %d", length);
+        return false;
+    }
+
+    mResultBuffer.clear();
+    if (!decode(image, length)) {
+        return false;
+    }
+
+    return true;
+}
+
+const void* JpegDecoder::getDecompressedImagePtr() {
+    return mResultBuffer.data();
+}
+
+size_t JpegDecoder::getDecompressedImageSize() {
+    return mResultBuffer.size();
+}
+
+bool JpegDecoder::decode(const void* image, int length) {
+    jpeg_decompress_struct cinfo;
+    jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
+    jpegrerror_mgr myerr;
+    cinfo.err = jpeg_std_error(&myerr.pub);
+    myerr.pub.error_exit = jpegrerror_exit;
+
+    if (setjmp(myerr.setjmp_buffer)) {
+        jpeg_destroy_decompress(&cinfo);
+        return false;
+    }
+    jpeg_create_decompress(&cinfo);
+
+    cinfo.src = &mgr;
+    jpeg_read_header(&cinfo, TRUE);
+
+    if (cinfo.jpeg_color_space == JCS_YCbCr) {
+        mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0);
+    } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
+        mResultBuffer.resize(cinfo.image_width * cinfo.image_height, 0);
+    }
+
+    cinfo.raw_data_out = TRUE;
+    cinfo.dct_method = JDCT_IFAST;
+    cinfo.out_color_space = cinfo.jpeg_color_space;
+
+    jpeg_start_decompress(&cinfo);
+
+    if (!decompress(&cinfo, static_cast<const uint8_t*>(mResultBuffer.data()),
+            cinfo.jpeg_color_space == JCS_GRAYSCALE)) {
+        return false;
+    }
+
+    jpeg_finish_decompress(&cinfo);
+    jpeg_destroy_decompress(&cinfo);
+
+    return true;
+}
+
+bool JpegDecoder::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest,
+        bool isSingleChannel) {
+    if (isSingleChannel) {
+        return decompressSingleChannel(cinfo, dest);
+    }
+    return decompressYUV(cinfo, dest);
+}
+
+bool JpegDecoder::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
+
+    JSAMPROW y[kCompressBatchSize];
+    JSAMPROW cb[kCompressBatchSize / 2];
+    JSAMPROW cr[kCompressBatchSize / 2];
+    JSAMPARRAY planes[3] {y, cb, cr};
+
+    size_t y_plane_size = cinfo->image_width * cinfo->image_height;
+    size_t uv_plane_size = y_plane_size / 4;
+    uint8_t* y_plane = const_cast<uint8_t*>(dest);
+    uint8_t* u_plane = const_cast<uint8_t*>(dest + y_plane_size);
+    uint8_t* v_plane = const_cast<uint8_t*>(dest + y_plane_size + uv_plane_size);
+    std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]);
+    memset(empty.get(), 0, cinfo->image_width);
+
+    while (cinfo->output_scanline < cinfo->image_height) {
+        for (int i = 0; i < kCompressBatchSize; ++i) {
+            size_t scanline = cinfo->output_scanline + i;
+            if (scanline < cinfo->image_height) {
+                y[i] = y_plane + scanline * cinfo->image_width;
+            } else {
+                y[i] = empty.get();
+            }
+        }
+        // cb, cr only have half scanlines
+        for (int i = 0; i < kCompressBatchSize / 2; ++i) {
+            size_t scanline = cinfo->output_scanline / 2 + i;
+            if (scanline < cinfo->image_height / 2) {
+                int offset = scanline * (cinfo->image_width / 2);
+                cb[i] = u_plane + offset;
+                cr[i] = v_plane + offset;
+            } else {
+                cb[i] = cr[i] = empty.get();
+            }
+        }
+
+        int processed = jpeg_read_raw_data(cinfo, planes, kCompressBatchSize);
+        if (processed != kCompressBatchSize) {
+            ALOGE("Number of processed lines does not equal input lines.");
+            return false;
+        }
+    }
+    return true;
+}
+
+bool JpegDecoder::decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
+    JSAMPROW y[kCompressBatchSize];
+    JSAMPARRAY planes[1] {y};
+
+    uint8_t* y_plane = const_cast<uint8_t*>(dest);
+    std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]);
+    memset(empty.get(), 0, cinfo->image_width);
+
+    while (cinfo->output_scanline < cinfo->image_height) {
+        for (int i = 0; i < kCompressBatchSize; ++i) {
+            size_t scanline = cinfo->output_scanline + i;
+            if (scanline < cinfo->image_height) {
+                y[i] = y_plane + scanline * cinfo->image_width;
+            } else {
+                y[i] = empty.get();
+            }
+        }
+
+        int processed = jpeg_read_raw_data(cinfo, planes, kCompressBatchSize);
+        if (processed != kCompressBatchSize / 2) {
+            ALOGE("Number of processed lines does not equal input lines.");
+            return false;
+        }
+    }
+    return true;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/jpegencoder.cpp b/libs/jpegrecoverymap/jpegencoder.cpp
new file mode 100644
index 0000000..d45d9b3
--- /dev/null
+++ b/libs/jpegrecoverymap/jpegencoder.cpp
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jpegrecoverymap/jpegencoder.h>
+
+#include <cutils/log.h>
+
+#include <errno.h>
+
+namespace android::recoverymap {
+
+// The destination manager that can access |mResultBuffer| in JpegEncoder.
+struct destination_mgr {
+public:
+    struct jpeg_destination_mgr mgr;
+    JpegEncoder* encoder;
+};
+
+JpegEncoder::JpegEncoder() {
+}
+
+JpegEncoder::~JpegEncoder() {
+}
+
+bool JpegEncoder::compressImage(const void* image, int width, int height, int quality,
+                                   const void* iccBuffer, unsigned int iccSize,
+                                   bool isSingleChannel) {
+    if (width % 8 != 0 || height % 2 != 0) {
+        ALOGE("Image size can not be handled: %dx%d", width, height);
+        return false;
+    }
+
+    mResultBuffer.clear();
+    if (!encode(image, width, height, quality, iccBuffer, iccSize, isSingleChannel)) {
+        return false;
+    }
+    ALOGI("Compressed JPEG: %d[%dx%d] -> %zu bytes",
+        (width * height * 12) / 8, width, height, mResultBuffer.size());
+    return true;
+}
+
+const void* JpegEncoder::getCompressedImagePtr() {
+    return mResultBuffer.data();
+}
+
+size_t JpegEncoder::getCompressedImageSize() {
+    return mResultBuffer.size();
+}
+
+void JpegEncoder::initDestination(j_compress_ptr cinfo) {
+    destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
+    std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
+    buffer.resize(kBlockSize);
+    dest->mgr.next_output_byte = &buffer[0];
+    dest->mgr.free_in_buffer = buffer.size();
+}
+
+boolean JpegEncoder::emptyOutputBuffer(j_compress_ptr cinfo) {
+    destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
+    std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
+    size_t oldsize = buffer.size();
+    buffer.resize(oldsize + kBlockSize);
+    dest->mgr.next_output_byte = &buffer[oldsize];
+    dest->mgr.free_in_buffer = kBlockSize;
+    return true;
+}
+
+void JpegEncoder::terminateDestination(j_compress_ptr cinfo) {
+    destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
+    std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
+    buffer.resize(buffer.size() - dest->mgr.free_in_buffer);
+}
+
+void JpegEncoder::outputErrorMessage(j_common_ptr cinfo) {
+    char buffer[JMSG_LENGTH_MAX];
+
+    /* Create the message */
+    (*cinfo->err->format_message) (cinfo, buffer);
+    ALOGE("%s\n", buffer);
+}
+
+bool JpegEncoder::encode(const void* image, int width, int height, int jpegQuality,
+                         const void* iccBuffer, unsigned int iccSize, bool isSingleChannel) {
+    jpeg_compress_struct cinfo;
+    jpeg_error_mgr jerr;
+
+    cinfo.err = jpeg_std_error(&jerr);
+    // Override output_message() to print error log with ALOGE().
+    cinfo.err->output_message = &outputErrorMessage;
+    jpeg_create_compress(&cinfo);
+    setJpegDestination(&cinfo);
+
+    setJpegCompressStruct(width, height, jpegQuality, &cinfo, isSingleChannel);
+    jpeg_start_compress(&cinfo, TRUE);
+
+    if (iccBuffer != nullptr && iccSize > 0) {
+        jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast<const JOCTET*>(iccBuffer), iccSize);
+    }
+
+    if (!compress(&cinfo, static_cast<const uint8_t*>(image), isSingleChannel)) {
+        return false;
+    }
+    jpeg_finish_compress(&cinfo);
+    jpeg_destroy_compress(&cinfo);
+    return true;
+}
+
+void JpegEncoder::setJpegDestination(jpeg_compress_struct* cinfo) {
+    destination_mgr* dest = static_cast<struct destination_mgr *>((*cinfo->mem->alloc_small) (
+            (j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(destination_mgr)));
+    dest->encoder = this;
+    dest->mgr.init_destination = &initDestination;
+    dest->mgr.empty_output_buffer = &emptyOutputBuffer;
+    dest->mgr.term_destination = &terminateDestination;
+    cinfo->dest = reinterpret_cast<struct jpeg_destination_mgr*>(dest);
+}
+
+void JpegEncoder::setJpegCompressStruct(int width, int height, int quality,
+                                        jpeg_compress_struct* cinfo, bool isSingleChannel) {
+    cinfo->image_width = width;
+    cinfo->image_height = height;
+    if (isSingleChannel) {
+        cinfo->input_components = 1;
+        cinfo->in_color_space = JCS_GRAYSCALE;
+    } else {
+        cinfo->input_components = 3;
+        cinfo->in_color_space = JCS_YCbCr;
+    }
+    jpeg_set_defaults(cinfo);
+
+    jpeg_set_quality(cinfo, quality, TRUE);
+    jpeg_set_colorspace(cinfo, isSingleChannel ? JCS_GRAYSCALE : JCS_YCbCr);
+    cinfo->raw_data_in = TRUE;
+    cinfo->dct_method = JDCT_IFAST;
+
+    if (!isSingleChannel) {
+        // Configure sampling factors. The sampling factor is JPEG subsampling 420 because the
+        // source format is YUV420.
+        cinfo->comp_info[0].h_samp_factor = 2;
+        cinfo->comp_info[0].v_samp_factor = 2;
+        cinfo->comp_info[1].h_samp_factor = 1;
+        cinfo->comp_info[1].v_samp_factor = 1;
+        cinfo->comp_info[2].h_samp_factor = 1;
+        cinfo->comp_info[2].v_samp_factor = 1;
+    }
+}
+
+bool JpegEncoder::compress(
+        jpeg_compress_struct* cinfo, const uint8_t* image, bool isSingleChannel) {
+    if (isSingleChannel) {
+        return compressSingleChannel(cinfo, image);
+    }
+    return compressYuv(cinfo, image);
+}
+
+bool JpegEncoder::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv) {
+    JSAMPROW y[kCompressBatchSize];
+    JSAMPROW cb[kCompressBatchSize / 2];
+    JSAMPROW cr[kCompressBatchSize / 2];
+    JSAMPARRAY planes[3] {y, cb, cr};
+
+    size_t y_plane_size = cinfo->image_width * cinfo->image_height;
+    size_t uv_plane_size = y_plane_size / 4;
+    uint8_t* y_plane = const_cast<uint8_t*>(yuv);
+    uint8_t* u_plane = const_cast<uint8_t*>(yuv + y_plane_size);
+    uint8_t* v_plane = const_cast<uint8_t*>(yuv + y_plane_size + uv_plane_size);
+    std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]);
+    memset(empty.get(), 0, cinfo->image_width);
+
+    while (cinfo->next_scanline < cinfo->image_height) {
+        for (int i = 0; i < kCompressBatchSize; ++i) {
+            size_t scanline = cinfo->next_scanline + i;
+            if (scanline < cinfo->image_height) {
+                y[i] = y_plane + scanline * cinfo->image_width;
+            } else {
+                y[i] = empty.get();
+            }
+        }
+        // cb, cr only have half scanlines
+        for (int i = 0; i < kCompressBatchSize / 2; ++i) {
+            size_t scanline = cinfo->next_scanline / 2 + i;
+            if (scanline < cinfo->image_height / 2) {
+                int offset = scanline * (cinfo->image_width / 2);
+                cb[i] = u_plane + offset;
+                cr[i] = v_plane + offset;
+            } else {
+                cb[i] = cr[i] = empty.get();
+            }
+        }
+
+        int processed = jpeg_write_raw_data(cinfo, planes, kCompressBatchSize);
+        if (processed != kCompressBatchSize) {
+            ALOGE("Number of processed lines does not equal input lines.");
+            return false;
+        }
+    }
+    return true;
+}
+
+bool JpegEncoder::compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image) {
+    JSAMPROW y[kCompressBatchSize];
+    JSAMPARRAY planes[1] {y};
+
+    uint8_t* y_plane = const_cast<uint8_t*>(image);
+    std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]);
+    memset(empty.get(), 0, cinfo->image_width);
+
+    while (cinfo->next_scanline < cinfo->image_height) {
+        for (int i = 0; i < kCompressBatchSize; ++i) {
+            size_t scanline = cinfo->next_scanline + i;
+            if (scanline < cinfo->image_height) {
+                y[i] = y_plane + scanline * cinfo->image_width;
+            } else {
+                y[i] = empty.get();
+            }
+        }
+        int processed = jpeg_write_raw_data(cinfo, planes, kCompressBatchSize);
+        if (processed != kCompressBatchSize / 2) {
+            ALOGE("Number of processed lines does not equal input lines.");
+            return false;
+        }
+    }
+    return true;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp
index bd92652..5d25722 100644
--- a/libs/jpegrecoverymap/recoverymap.cpp
+++ b/libs/jpegrecoverymap/recoverymap.cpp
@@ -18,69 +18,123 @@
 
 namespace android::recoverymap {
 
-bool RecoveryMap::decodeRecoveryMap(j_r_compressed_ptr compressed_recovery_map,
-                                    j_r_uncompressed_ptr dest) {
+status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+                                  jr_uncompressed_ptr uncompressed_yuv_420_image,
+                                  void* dest) {
+  if (uncompressed_p010_image == nullptr
+   || uncompressed_yuv_420_image == nullptr
+   || dest == nullptr) {
+    return BAD_VALUE;
+  }
+
+  // TBD
+  return NO_ERROR;
+}
+
+status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+                                  jr_uncompressed_ptr uncompressed_yuv_420_image,
+                                  void* compressed_jpeg_image,
+                                  void* dest) {
+
+  if (uncompressed_p010_image == nullptr
+   || uncompressed_yuv_420_image == nullptr
+   || compressed_jpeg_image == nullptr
+   || dest == nullptr) {
+    return BAD_VALUE;
+  }
+
+  // TBD
+  return NO_ERROR;
+}
+
+status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+                                  void* compressed_jpeg_image,
+                                  void* dest) {
+  if (uncompressed_p010_image == nullptr
+   || compressed_jpeg_image == nullptr
+   || dest == nullptr) {
+    return BAD_VALUE;
+  }
+
+  // TBD
+  return NO_ERROR;
+}
+
+status_t RecoveryMap::decodeJPEGR(void* compressed_jpegr_image, jr_uncompressed_ptr dest) {
+  if (compressed_jpegr_image == nullptr || dest == nullptr) {
+    return BAD_VALUE;
+  }
+
+  // TBD
+  return NO_ERROR;
+}
+
+status_t RecoveryMap::decodeRecoveryMap(jr_compressed_ptr compressed_recovery_map,
+                                        jr_uncompressed_ptr dest) {
   if (compressed_recovery_map == nullptr || dest == nullptr) {
-    return false;
+    return BAD_VALUE;
   }
 
   // TBD
-  return true;
+  return NO_ERROR;
 }
 
-bool RecoveryMap::encodeRecoveryMap(j_r_uncompressed_ptr uncompressed_recovery_map,
-                                    j_r_compressed_ptr dest) {
+status_t RecoveryMap::encodeRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
+                                        jr_compressed_ptr dest) {
   if (uncompressed_recovery_map == nullptr || dest == nullptr) {
-    return false;
+    return BAD_VALUE;
   }
 
   // TBD
-  return true;
+  return NO_ERROR;
 }
 
-bool RecoveryMap::generateRecoveryMap(j_r_uncompressed_ptr uncompressed_yuv_420_image,
-                                      j_r_uncompressed_ptr uncompressed_p010_image,
-                                      j_r_uncompressed_ptr dest) {
+status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
+                                          jr_uncompressed_ptr uncompressed_p010_image,
+                                          jr_uncompressed_ptr dest) {
   if (uncompressed_yuv_420_image == nullptr
    || uncompressed_p010_image == nullptr
    || dest == nullptr) {
-    return false;
+    return BAD_VALUE;
   }
 
   // TBD
-  return true;
+  return NO_ERROR;
 }
 
-bool RecoveryMap::applyRecoveryMap(j_r_uncompressed_ptr uncompressed_yuv_420_image,
-                                   j_r_uncompressed_ptr uncompressed_recovery_map,
-                                   j_r_uncompressed_ptr dest) {
+status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
+                                       jr_uncompressed_ptr uncompressed_recovery_map,
+                                       jr_uncompressed_ptr dest) {
   if (uncompressed_yuv_420_image == nullptr
    || uncompressed_recovery_map == nullptr
    || dest == nullptr) {
-    return false;
+    return BAD_VALUE;
   }
 
   // TBD
-  return true;
+  return NO_ERROR;
 }
 
-j_r_compressed_ptr RecoveryMap::extractRecoveryMap(void* compressed_jpeg_r_image) {
-  if (compressed_jpeg_r_image == nullptr) {
-    return nullptr;
+status_t RecoveryMap::extractRecoveryMap(void* compressed_jpegr_image, jr_compressed_ptr dest) {
+  if (compressed_jpegr_image == nullptr || dest == nullptr) {
+    return BAD_VALUE;
   }
 
   // TBD
-  return nullptr;
+  return NO_ERROR;
 }
 
-void* RecoveryMap::appendRecoveryMap(void* compressed_jpeg_image,
-                                     j_r_compressed_ptr compressed_recovery_map) {
-  if (compressed_jpeg_image == nullptr || compressed_recovery_map == nullptr) {
-    return nullptr;
+status_t RecoveryMap::appendRecoveryMap(void* compressed_jpeg_image,
+                                     jr_compressed_ptr compressed_recovery_map,
+                                     void* dest) {
+  if (compressed_jpeg_image == nullptr
+   || compressed_recovery_map == nullptr
+   || dest == nullptr) {
+    return BAD_VALUE;
   }
 
   // TBD
-  return nullptr;
+  return NO_ERROR;
 }
 
 } // namespace android::recoverymap
diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp
index 79bf723..7f37f61 100644
--- a/libs/jpegrecoverymap/tests/Android.bp
+++ b/libs/jpegrecoverymap/tests/Android.bp
@@ -30,4 +30,36 @@
     static_libs: [
         "libjpegrecoverymap",
     ],
+}
+
+cc_test {
+    name: "libjpegencoder_test",
+    test_suites: ["device-tests"],
+    srcs: [
+        "jpegencoder_test.cpp",
+    ],
+    shared_libs: [
+        "libjpeg",
+        "liblog",
+    ],
+    static_libs: [
+        "libjpegencoder",
+        "libgtest",
+    ],
+}
+
+cc_test {
+    name: "libjpegdecoder_test",
+    test_suites: ["device-tests"],
+    srcs: [
+        "jpegdecoder_test.cpp",
+    ],
+    shared_libs: [
+        "libjpeg",
+        "liblog",
+    ],
+    static_libs: [
+        "libjpegdecoder",
+        "libgtest",
+    ],
 }
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/tests/data/minnie-318x240.yu12 b/libs/jpegrecoverymap/tests/data/minnie-318x240.yu12
new file mode 100644
index 0000000..7b2fc71
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/data/minnie-318x240.yu12
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240-y.jpg b/libs/jpegrecoverymap/tests/data/minnie-320x240-y.jpg
new file mode 100644
index 0000000..20b5a2c
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/data/minnie-320x240-y.jpg
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240-yuv.jpg b/libs/jpegrecoverymap/tests/data/minnie-320x240-yuv.jpg
new file mode 100644
index 0000000..41300f4
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/data/minnie-320x240-yuv.jpg
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240.y b/libs/jpegrecoverymap/tests/data/minnie-320x240.y
new file mode 100644
index 0000000..f9d8371
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/data/minnie-320x240.y
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240.yu12 b/libs/jpegrecoverymap/tests/data/minnie-320x240.yu12
new file mode 100644
index 0000000..0d66f53
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/data/minnie-320x240.yu12
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/jpegdecoder_test.cpp b/libs/jpegrecoverymap/tests/jpegdecoder_test.cpp
new file mode 100644
index 0000000..8e01351
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/jpegdecoder_test.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jpegrecoverymap/jpegdecoder.h>
+#include <gtest/gtest.h>
+#include <utils/Log.h>
+
+#include <fcntl.h>
+
+namespace android::recoverymap {
+
+#define YUV_IMAGE "/sdcard/Documents/minnie-320x240-yuv.jpg"
+#define YUV_IMAGE_SIZE 20193
+#define GREY_IMAGE "/sdcard/Documents/minnie-320x240-y.jpg"
+#define GREY_IMAGE_SIZE 20193
+
+class JpegDecoderTest : public testing::Test {
+public:
+    struct Image {
+        std::unique_ptr<uint8_t[]> buffer;
+        size_t size;
+    };
+    JpegDecoderTest();
+    ~JpegDecoderTest();
+protected:
+    virtual void SetUp();
+    virtual void TearDown();
+
+    Image mYuvImage, mGreyImage;
+};
+
+JpegDecoderTest::JpegDecoderTest() {}
+
+JpegDecoderTest::~JpegDecoderTest() {}
+
+static size_t getFileSize(int fd) {
+    struct stat st;
+    if (fstat(fd, &st) < 0) {
+        ALOGW("%s : fstat failed", __func__);
+        return 0;
+    }
+    return st.st_size; // bytes
+}
+
+static bool loadFile(const char filename[], JpegDecoderTest::Image* result) {
+    int fd = open(filename, O_CLOEXEC);
+    if (fd < 0) {
+        return false;
+    }
+    int length = getFileSize(fd);
+    if (length == 0) {
+        close(fd);
+        return false;
+    }
+    result->buffer.reset(new uint8_t[length]);
+    if (read(fd, result->buffer.get(), length) != static_cast<ssize_t>(length)) {
+        close(fd);
+        return false;
+    }
+    close(fd);
+    return true;
+}
+
+void JpegDecoderTest::SetUp() {
+    if (!loadFile(YUV_IMAGE, &mYuvImage)) {
+        FAIL() << "Load file " << YUV_IMAGE << " failed";
+    }
+    mYuvImage.size = YUV_IMAGE_SIZE;
+    if (!loadFile(GREY_IMAGE, &mGreyImage)) {
+        FAIL() << "Load file " << GREY_IMAGE << " failed";
+    }
+    mGreyImage.size = GREY_IMAGE_SIZE;
+}
+
+void JpegDecoderTest::TearDown() {}
+
+TEST_F(JpegDecoderTest, decodeYuvImage) {
+    JpegDecoder decoder;
+    EXPECT_TRUE(decoder.decompressImage(mYuvImage.buffer.get(), mYuvImage.size));
+    ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0));
+}
+
+TEST_F(JpegDecoderTest, decodeGreyImage) {
+    JpegDecoder decoder;
+    EXPECT_TRUE(decoder.decompressImage(mGreyImage.buffer.get(), mGreyImage.size));
+    ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0));
+}
+
+}
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/tests/jpegencoder_test.cpp b/libs/jpegrecoverymap/tests/jpegencoder_test.cpp
new file mode 100644
index 0000000..4cd2a5e
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/jpegencoder_test.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jpegrecoverymap/jpegencoder.h>
+#include <gtest/gtest.h>
+#include <utils/Log.h>
+
+#include <fcntl.h>
+
+namespace android::recoverymap {
+
+#define VALID_IMAGE "/sdcard/Documents/minnie-320x240.yu12"
+#define VALID_IMAGE_WIDTH 320
+#define VALID_IMAGE_HEIGHT 240
+#define SINGLE_CHANNEL_IMAGE "/sdcard/Documents/minnie-320x240.y"
+#define SINGLE_CHANNEL_IMAGE_WIDTH VALID_IMAGE_WIDTH
+#define SINGLE_CHANNEL_IMAGE_HEIGHT VALID_IMAGE_HEIGHT
+#define INVALID_SIZE_IMAGE "/sdcard/Documents/minnie-318x240.yu12"
+#define INVALID_SIZE_IMAGE_WIDTH 318
+#define INVALID_SIZE_IMAGE_HEIGHT 240
+#define JPEG_QUALITY 90
+
+class JpegEncoderTest : public testing::Test {
+public:
+    struct Image {
+        std::unique_ptr<uint8_t[]> buffer;
+        size_t width;
+        size_t height;
+    };
+    JpegEncoderTest();
+    ~JpegEncoderTest();
+protected:
+    virtual void SetUp();
+    virtual void TearDown();
+
+    Image mValidImage, mInvalidSizeImage, mSingleChannelImage;
+};
+
+JpegEncoderTest::JpegEncoderTest() {}
+
+JpegEncoderTest::~JpegEncoderTest() {}
+
+static size_t getFileSize(int fd) {
+    struct stat st;
+    if (fstat(fd, &st) < 0) {
+        ALOGW("%s : fstat failed", __func__);
+        return 0;
+    }
+    return st.st_size; // bytes
+}
+
+static bool loadFile(const char filename[], JpegEncoderTest::Image* result) {
+    int fd = open(filename, O_CLOEXEC);
+    if (fd < 0) {
+        return false;
+    }
+    int length = getFileSize(fd);
+    if (length == 0) {
+        close(fd);
+        return false;
+    }
+    result->buffer.reset(new uint8_t[length]);
+    if (read(fd, result->buffer.get(), length) != static_cast<ssize_t>(length)) {
+        close(fd);
+        return false;
+    }
+    close(fd);
+    return true;
+}
+
+void JpegEncoderTest::SetUp() {
+    if (!loadFile(VALID_IMAGE, &mValidImage)) {
+        FAIL() << "Load file " << VALID_IMAGE << " failed";
+    }
+    mValidImage.width = VALID_IMAGE_WIDTH;
+    mValidImage.height = VALID_IMAGE_HEIGHT;
+    if (!loadFile(INVALID_SIZE_IMAGE, &mInvalidSizeImage)) {
+        FAIL() << "Load file " << INVALID_SIZE_IMAGE << " failed";
+    }
+    mInvalidSizeImage.width = INVALID_SIZE_IMAGE_WIDTH;
+    mInvalidSizeImage.height = INVALID_SIZE_IMAGE_HEIGHT;
+    if (!loadFile(SINGLE_CHANNEL_IMAGE, &mSingleChannelImage)) {
+        FAIL() << "Load file " << SINGLE_CHANNEL_IMAGE << " failed";
+    }
+    mSingleChannelImage.width = SINGLE_CHANNEL_IMAGE_WIDTH;
+    mSingleChannelImage.height = SINGLE_CHANNEL_IMAGE_HEIGHT;
+}
+
+void JpegEncoderTest::TearDown() {}
+
+TEST_F(JpegEncoderTest, validImage) {
+    JpegEncoder encoder;
+    EXPECT_TRUE(encoder.compressImage(mValidImage.buffer.get(), mValidImage.width,
+                                         mValidImage.height, JPEG_QUALITY, NULL, 0));
+    ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
+}
+
+TEST_F(JpegEncoderTest, invalidSizeImage) {
+    JpegEncoder encoder;
+    EXPECT_FALSE(encoder.compressImage(mInvalidSizeImage.buffer.get(), mInvalidSizeImage.width,
+                                          mInvalidSizeImage.height, JPEG_QUALITY, NULL, 0));
+}
+
+TEST_F(JpegEncoderTest, singleChannelImage) {
+    JpegEncoder encoder;
+    EXPECT_TRUE(encoder.compressImage(mSingleChannelImage.buffer.get(), mSingleChannelImage.width,
+                                         mSingleChannelImage.height, JPEG_QUALITY, NULL, 0, true));
+    ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
+}
+
+}
+
diff --git a/libs/nativewindow/Android.bp b/libs/nativewindow/Android.bp
index 3503a9e..3b58265 100644
--- a/libs/nativewindow/Android.bp
+++ b/libs/nativewindow/Android.bp
@@ -74,6 +74,9 @@
         override_export_include_dirs: [
             "include",
         ],
+        export_llndk_headers: [
+            "libarect_headers",
+        ],
     },
     export_include_dirs: [
         "include",
@@ -110,16 +113,14 @@
     ],
 
     header_libs: [
+        "libarect_headers",
         "libnativebase_headers",
         "libnativewindow_headers",
     ],
 
     // headers we include in our public headers
-    export_static_lib_headers: [
-        "libarect",
-    ],
-
     export_header_lib_headers: [
+        "libarect_headers",
         "libnativebase_headers",
     ],
 
diff --git a/libs/ui/include/ui/DisplayId.h b/libs/ui/include/ui/DisplayId.h
index d0c03fe..3a31fa0 100644
--- a/libs/ui/include/ui/DisplayId.h
+++ b/libs/ui/include/ui/DisplayId.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <cstdint>
+#include <ostream>
 #include <string>
 
 #include <ftl/optional.h>
@@ -67,6 +68,11 @@
     return std::to_string(displayId.value);
 }
 
+// For tests.
+inline std::ostream& operator<<(std::ostream& stream, DisplayId displayId) {
+    return stream << "DisplayId{" << displayId.value << '}';
+}
+
 // DisplayId of a physical display, such as the internal display or externally connected display.
 struct PhysicalDisplayId : DisplayId {
     static constexpr ftl::Optional<PhysicalDisplayId> tryCast(DisplayId id) {
diff --git a/opengl/libs/EGL/getProcAddress.cpp b/opengl/libs/EGL/getProcAddress.cpp
index b3d6f74..e0ba946 100644
--- a/opengl/libs/EGL/getProcAddress.cpp
+++ b/opengl/libs/EGL/getProcAddress.cpp
@@ -118,70 +118,27 @@
             : "rax", "cc"                                       \
             );
 
-#elif defined(__mips64)
+#elif defined(__riscv)
+    #define API_ENTRY(_api) __attribute__((noinline)) _api
 
-        #define API_ENTRY(_api) __attribute__((noinline)) _api
-
-        #define CALL_GL_EXTENSION_API(_api, ...)                    \
-            register unsigned int _t0 asm("$12");                   \
-            register unsigned int _fn asm("$25");                   \
-            register unsigned int _tls asm("$3");                   \
-            asm volatile(                                           \
-                ".set  push\n\t"                                    \
-                ".set  noreorder\n\t"                               \
-                "rdhwr %[tls], $29\n\t"                             \
-                "ld    %[t0], %[OPENGL_API](%[tls])\n\t"            \
-                "beqz  %[t0], 1f\n\t"                               \
-                " move %[fn], $ra\n\t"                              \
-                "ld    %[t0], %[API](%[t0])\n\t"                    \
-                "beqz  %[t0], 1f\n\t"                               \
-                " nop\n\t"                                          \
-                "move  %[fn], %[t0]\n\t"                            \
-                "1:\n\t"                                            \
-                "jalr  $0, %[fn]\n\t"                               \
-                " nop\n\t"                                          \
-                ".set  pop\n\t"                                     \
-                : [fn] "=c"(_fn),                                   \
-                  [tls] "=&r"(_tls),                                \
-                  [t0] "=&r"(_t0)                                   \
-                : [OPENGL_API] "I"(TLS_SLOT_OPENGL_API*4),          \
-                  [API] "I"(__builtin_offsetof(gl_hooks_t,          \
-                                          ext.extensions[_api]))    \
-                :                                                   \
-            );
-
-#elif defined(__mips__)
-
-        #define API_ENTRY(_api) __attribute__((noinline)) _api
-
-        #define CALL_GL_EXTENSION_API(_api, ...)                    \
-            register unsigned int _t0 asm("$8");                    \
-            register unsigned int _fn asm("$25");                    \
-            register unsigned int _tls asm("$3");                   \
-            asm volatile(                                           \
-                ".set  push\n\t"                                    \
-                ".set  noreorder\n\t"                               \
-                ".set  mips32r2\n\t"                                \
-                "rdhwr %[tls], $29\n\t"                             \
-                "lw    %[t0], %[OPENGL_API](%[tls])\n\t"            \
-                "beqz  %[t0], 1f\n\t"                               \
-                " move %[fn], $ra\n\t"                              \
-                "lw    %[t0], %[API](%[t0])\n\t"                    \
-                "beqz  %[t0], 1f\n\t"                               \
-                " nop\n\t"                                          \
-                "move  %[fn], %[t0]\n\t"                            \
-                "1:\n\t"                                            \
-                "jalr  $0, %[fn]\n\t"                               \
-                " nop\n\t"                                          \
-                ".set  pop\n\t"                                     \
-                : [fn] "=c"(_fn),                                   \
-                  [tls] "=&r"(_tls),                                \
-                  [t0] "=&r"(_t0)                                   \
-                : [OPENGL_API] "I"(TLS_SLOT_OPENGL_API*4),          \
-                  [API] "I"(__builtin_offsetof(gl_hooks_t,          \
-                                          ext.extensions[_api]))    \
-                :                                                   \
-            );
+    #define CALL_GL_EXTENSION_API(_api)                             \
+        asm volatile(                                               \
+            "mv t0, tp\n"                                           \
+            "li t1, %[tls]\n"                                       \
+            "add t0, t0, t1\n"                                      \
+            "ld t0, 0(t0)\n"                                        \
+            "beqz t0, 1f\n"                                         \
+            "li t1, %[api]\n"                                       \
+            "add t0, t0, t1\n"                                      \
+            "ld t0, 0(t0)\n"                                        \
+            "jalr x0, t0\n"                                         \
+            "1: ret\n"                                              \
+            :                                                       \
+            : [tls] "i" (TLS_SLOT_OPENGL_API * sizeof(void*)),      \
+              [api] "i" (__builtin_offsetof(gl_hooks_t,             \
+                                        ext.extensions[_api]))      \
+            : "t0", "t1"                                            \
+        );
 
 #endif
 
diff --git a/opengl/libs/GLES2/gl2.cpp b/opengl/libs/GLES2/gl2.cpp
index 65f50f5..5bd5c14 100644
--- a/opengl/libs/GLES2/gl2.cpp
+++ b/opengl/libs/GLES2/gl2.cpp
@@ -191,73 +191,44 @@
             :                              \
         );
 
-#elif defined(__mips64)
+#elif defined(__riscv)
 
     #define API_ENTRY(_api) __attribute__((naked,noinline)) _api
 
-    // t0:  $12
-    // fn:  $25
-    // tls: $3
-    // v0:  $2
-    #define CALL_GL_API_INTERNAL_CALL(_api, ...)                  \
-        asm volatile(                                             \
-            ".set  push\n\t"                                      \
-            ".set  noreorder\n\t"                                 \
-            "rdhwr $3, $29\n\t"                                   \
-            "ld    $12, %[OPENGL_API]($3)\n\t"                    \
-            "beqz  $12, 1f\n\t"                                   \
-            " move $25, $ra\n\t"                                  \
-            "ld    $12, %[API]($12)\n\t"                          \
-            "beqz  $12, 1f\n\t"                                   \
-            " nop\n\t"                                            \
-            "move  $25, $12\n\t"                                  \
-            "1:\n\t"                                              \
-            "jalr  $0, $25\n\t"                                   \
-            " move $2, $0\n\t"                                    \
-            ".set  pop\n\t"                                       \
-            :                                                     \
-            : [OPENGL_API] "I"(TLS_SLOT_OPENGL_API*sizeof(void*)),\
-              [API] "I"(__builtin_offsetof(gl_hooks_t, gl._api))  \
-            : "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$9",     \
-              "$10", "$11", "$12", "$25"                          \
-        );
-
-    #define CALL_GL_API_INTERNAL_SET_RETURN_VALUE
-    #define CALL_GL_API_INTERNAL_DO_RETURN
-
-#elif defined(__mips__)
-
-    #define API_ENTRY(_api) __attribute__((naked,noinline)) _api
-
-    // t0:  $8
-    // fn:  $25
-    // tls: $3
-    // v0:  $2
     #define CALL_GL_API_INTERNAL_CALL(_api, ...)                 \
         asm volatile(                                            \
-            ".set  push\n\t"                                     \
-            ".set  noreorder\n\t"                                \
-            ".set  mips32r2\n\t"                                 \
-            "rdhwr $3, $29\n\t"                                  \
-            "lw    $3, %[OPENGL_API]($3)\n\t"                    \
-            "beqz  $3, 1f\n\t"                                   \
-            " move $25,$ra\n\t"                                  \
-            "lw    $3, %[API]($3)\n\t"                           \
-            "beqz  $3, 1f\n\t"                                   \
-            " nop\n\t"                                           \
-            "move  $25, $3\n\t"                                  \
-            "1:\n\t"                                             \
-            "jalr  $0, $25\n\t"                                  \
-            " move $2, $0\n\t"                                   \
-            ".set  pop\n\t"                                      \
+            "mv t0, tp\n"                                        \
+            "li t1, %[tls]\n"                                    \
+            "add t0, t0, t1\n"                                   \
+            "ld t0, 0(t0)\n"                                     \
+            "beqz t0, 1f\n"                                      \
+            "li t1, %[api]\n"                                    \
+            "add t0, t0, t1\n"                                   \
+            "ld t0, 0(t0)\n"                                     \
+            "jalr x0, t0\n"                                      \
+            "1:\n"                                               \
             :                                                    \
-            : [OPENGL_API] "I"(TLS_SLOT_OPENGL_API*4),           \
-              [API] "I"(__builtin_offsetof(gl_hooks_t, gl._api)) \
-            : "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$25"    \
+            : [tls] "i"(TLS_SLOT_OPENGL_API*sizeof(void *)),     \
+              [api] "i"(__builtin_offsetof(gl_hooks_t, gl._api)) \
+            : "t0", "t1", "t2", "a0", "a1", "a2", "a3", "a4",    \
+              "a5", "t6", "t3", "t4", "t5", "t6"                 \
         );
 
-    #define CALL_GL_API_INTERNAL_SET_RETURN_VALUE
-    #define CALL_GL_API_INTERNAL_DO_RETURN
+    #define CALL_GL_API_INTERNAL_SET_RETURN_VALUE \
+        asm volatile(                             \
+            "li a0, 0\n"                          \
+            :                                     \
+            :                                     \
+            : "a0"                                \
+        );
+
+    #define CALL_GL_API_INTERNAL_DO_RETURN \
+        asm volatile(                      \
+            "ret\n"                        \
+            :                              \
+            :                              \
+            :                              \
+        );
 
 #endif
 
diff --git a/opengl/libs/GLES_CM/gl.cpp b/opengl/libs/GLES_CM/gl.cpp
index bacd4b4..64c0f97 100644
--- a/opengl/libs/GLES_CM/gl.cpp
+++ b/opengl/libs/GLES_CM/gl.cpp
@@ -247,73 +247,44 @@
             :                              \
         );
 
-#elif defined(__mips64)
+#elif defined(__riscv)
 
     #define API_ENTRY(_api) __attribute__((naked,noinline)) _api
 
-    // t0:  $12
-    // fn:  $25
-    // tls: $3
-    // v0:  $2
-    #define CALL_GL_API_INTERNAL_CALL(_api, ...)                  \
-        asm volatile(                                             \
-            ".set  push\n\t"                                      \
-            ".set  noreorder\n\t"                                 \
-            "rdhwr $3, $29\n\t"                                   \
-            "ld    $12, %[OPENGL_API]($3)\n\t"                    \
-            "beqz  $12, 1f\n\t"                                   \
-            " move $25, $ra\n\t"                                  \
-            "ld    $12, %[API]($12)\n\t"                          \
-            "beqz  $12, 1f\n\t"                                   \
-            " nop\n\t"                                            \
-            "move  $25, $12\n\t"                                  \
-            "1:\n\t"                                              \
-            "jalr  $0, $25\n\t"                                   \
-            " move $2, $0\n\t"                                    \
-            ".set  pop\n\t"                                       \
-            :                                                     \
-            : [OPENGL_API] "I"(TLS_SLOT_OPENGL_API*sizeof(void*)),\
-              [API] "I"(__builtin_offsetof(gl_hooks_t, gl._api))  \
-            : "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$9",     \
-              "$10", "$11", "$12", "$25"                          \
-        );
-
-    #define CALL_GL_API_INTERNAL_SET_RETURN_VALUE
-    #define CALL_GL_API_INTERNAL_DO_RETURN
-
-#elif defined(__mips__)
-
-    #define API_ENTRY(_api) __attribute__((naked,noinline)) _api
-
-    // t0:  $8
-    // fn:  $25
-    // tls: $3
-    // v0:  $2
     #define CALL_GL_API_INTERNAL_CALL(_api, ...)                 \
         asm volatile(                                            \
-            ".set  push\n\t"                                     \
-            ".set  noreorder\n\t"                                \
-            ".set  mips32r2\n\t"                                 \
-            "rdhwr $3, $29\n\t"                                  \
-            "lw    $3, %[OPENGL_API]($3)\n\t"                    \
-            "beqz  $3, 1f\n\t"                                   \
-            " move $25,$ra\n\t"                                  \
-            "lw    $3, %[API]($3)\n\t"                           \
-            "beqz  $3, 1f\n\t"                                   \
-            " nop\n\t"                                           \
-            "move  $25, $3\n\t"                                  \
-            "1:\n\t"                                             \
-            "jalr  $0, $25\n\t"                                  \
-            " move $2, $0\n\t"                                   \
-            ".set  pop\n\t"                                      \
+            "mv t0, tp\n"                                        \
+            "li t1, %[tls]\n"                                    \
+            "add t0, t0, t1\n"                                   \
+            "ld t0, 0(t0)\n"                                     \
+            "beqz t0, 1f\n"                                      \
+            "li t1, %[api]\n"                                    \
+            "add t0, t0, t1\n"                                   \
+            "ld t0, 0(t0)\n"                                     \
+            "jalr x0, t0\n"                                      \
+            "1:\n"                                               \
             :                                                    \
-            : [OPENGL_API] "I"(TLS_SLOT_OPENGL_API*4),           \
-              [API] "I"(__builtin_offsetof(gl_hooks_t, gl._api)) \
-            : "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$25"    \
+            : [tls] "i"(TLS_SLOT_OPENGL_API*sizeof(void *)),     \
+              [api] "i"(__builtin_offsetof(gl_hooks_t, gl._api)) \
+            : "t0", "t1", "t2", "a0", "a1", "a2", "a3", "a4",    \
+              "a5", "t6", "t3", "t4", "t5", "t6"                 \
         );
 
-    #define CALL_GL_API_INTERNAL_SET_RETURN_VALUE
-    #define CALL_GL_API_INTERNAL_DO_RETURN
+    #define CALL_GL_API_INTERNAL_SET_RETURN_VALUE \
+        asm volatile(                             \
+            "li a0, 0\n"                          \
+            :                                     \
+            :                                     \
+            : "a0"                                \
+        );
+
+    #define CALL_GL_API_INTERNAL_DO_RETURN \
+        asm volatile(                      \
+            "ret\n"                        \
+            :                              \
+            :                              \
+            :                              \
+        );
 
 #endif
 
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 2ecd7bc..e191d93 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -1071,7 +1071,7 @@
                                                                 int32_t y, TouchState* touchState,
                                                                 bool isStylus,
                                                                 bool addOutsideTargets,
-                                                                bool ignoreDragWindow) {
+                                                                bool ignoreDragWindow) const {
     if (addOutsideTargets && touchState == nullptr) {
         LOG_ALWAYS_FATAL("Must provide a valid touch state if adding outside targets");
     }
@@ -2578,7 +2578,7 @@
 void InputDispatcher::addWindowTargetLocked(const sp<WindowInfoHandle>& windowHandle,
                                             int32_t targetFlags, BitSet32 pointerIds,
                                             std::optional<nsecs_t> firstDownTimeInTarget,
-                                            std::vector<InputTarget>& inputTargets) {
+                                            std::vector<InputTarget>& inputTargets) const {
     std::vector<InputTarget>::iterator it =
             std::find_if(inputTargets.begin(), inputTargets.end(),
                          [&windowHandle](const InputTarget& inputTarget) {
@@ -3984,13 +3984,14 @@
     if (DEBUG_INBOUND_EVENT_DETAILS) {
         ALOGD("notifyMotion - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=0x%x, "
               "displayId=%" PRId32 ", policyFlags=0x%x, "
-              "action=0x%x, actionButton=0x%x, flags=0x%x, metaState=0x%x, buttonState=0x%x, "
+              "action=%s, actionButton=0x%x, flags=0x%x, metaState=0x%x, buttonState=0x%x, "
               "edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, xCursorPosition=%f, "
               "yCursorPosition=%f, downTime=%" PRId64,
               args->id, args->eventTime, args->deviceId, args->source, args->displayId,
-              args->policyFlags, args->action, args->actionButton, args->flags, args->metaState,
-              args->buttonState, args->edgeFlags, args->xPrecision, args->yPrecision,
-              args->xCursorPosition, args->yCursorPosition, args->downTime);
+              args->policyFlags, MotionEvent::actionToString(args->action).c_str(),
+              args->actionButton, args->flags, args->metaState, args->buttonState, args->edgeFlags,
+              args->xPrecision, args->yPrecision, args->xCursorPosition, args->yCursorPosition,
+              args->downTime);
         for (uint32_t i = 0; i < args->pointerCount; i++) {
             ALOGD("  Pointer %d: id=%d, toolType=%d, "
                   "x=%f, y=%f, pressure=%f, size=%f, "
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 14b046a..8356f3d 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -239,7 +239,7 @@
 
     sp<android::gui::WindowInfoHandle> findTouchedWindowAtLocked(
             int32_t displayId, int32_t x, int32_t y, TouchState* touchState, bool isStylus = false,
-            bool addOutsideTargets = false, bool ignoreDragWindow = false) REQUIRES(mLock);
+            bool addOutsideTargets = false, bool ignoreDragWindow = false) const REQUIRES(mLock);
 
     std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAtLocked(
             int32_t displayId, int32_t x, int32_t y, bool isStylus) const REQUIRES(mLock);
@@ -554,7 +554,7 @@
     void addWindowTargetLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
                                int32_t targetFlags, BitSet32 pointerIds,
                                std::optional<nsecs_t> firstDownTimeInTarget,
-                               std::vector<InputTarget>& inputTargets) REQUIRES(mLock);
+                               std::vector<InputTarget>& inputTargets) const REQUIRES(mLock);
     void addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets, int32_t displayId)
             REQUIRES(mLock);
     void pokeUserActivityLocked(const EventEntry& eventEntry) REQUIRES(mLock);
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 98669f3..615889e 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -2844,54 +2844,20 @@
     // Otherwise choose an arbitrary remaining pointer.
     // This guarantees we always have an active touch id when there is at least one pointer.
     // We keep the same active touch id for as long as possible.
-    int32_t lastActiveTouchId = mPointerGesture.activeTouchId;
-    int32_t activeTouchId = lastActiveTouchId;
-    if (activeTouchId < 0) {
+    if (mPointerGesture.activeTouchId < 0) {
         if (!mCurrentCookedState.fingerIdBits.isEmpty()) {
-            activeTouchId = mPointerGesture.activeTouchId =
-                    mCurrentCookedState.fingerIdBits.firstMarkedBit();
+            mPointerGesture.activeTouchId = mCurrentCookedState.fingerIdBits.firstMarkedBit();
             mPointerGesture.firstTouchTime = when;
         }
-    } else if (!mCurrentCookedState.fingerIdBits.hasBit(activeTouchId)) {
-        if (!mCurrentCookedState.fingerIdBits.isEmpty()) {
-            activeTouchId = mPointerGesture.activeTouchId =
-                    mCurrentCookedState.fingerIdBits.firstMarkedBit();
-        } else {
-            activeTouchId = mPointerGesture.activeTouchId = -1;
-        }
+    } else if (!mCurrentCookedState.fingerIdBits.hasBit(mPointerGesture.activeTouchId)) {
+        mPointerGesture.activeTouchId = !mCurrentCookedState.fingerIdBits.isEmpty()
+                ? mCurrentCookedState.fingerIdBits.firstMarkedBit()
+                : -1;
     }
-
-    // Determine whether we are in quiet time.
-    bool isQuietTime = false;
-    if (activeTouchId < 0) {
-        mPointerGesture.resetQuietTime();
-    } else {
-        isQuietTime = when < mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval;
-        if (!isQuietTime) {
-            if ((mPointerGesture.lastGestureMode == PointerGesture::Mode::PRESS ||
-                 mPointerGesture.lastGestureMode == PointerGesture::Mode::SWIPE ||
-                 mPointerGesture.lastGestureMode == PointerGesture::Mode::FREEFORM) &&
-                currentFingerCount < 2) {
-                // Enter quiet time when exiting swipe or freeform state.
-                // This is to prevent accidentally entering the hover state and flinging the
-                // pointer when finishing a swipe and there is still one pointer left onscreen.
-                isQuietTime = true;
-            } else if (mPointerGesture.lastGestureMode ==
-                               PointerGesture::Mode::BUTTON_CLICK_OR_DRAG &&
-                       currentFingerCount >= 2 && !isPointerDown(mCurrentRawState.buttonState)) {
-                // Enter quiet time when releasing the button and there are still two or more
-                // fingers down.  This may indicate that one finger was used to press the button
-                // but it has not gone up yet.
-                isQuietTime = true;
-            }
-            if (isQuietTime) {
-                mPointerGesture.quietTime = when;
-            }
-        }
-    }
+    const int32_t& activeTouchId = mPointerGesture.activeTouchId;
 
     // Switch states based on button and pointer state.
-    if (isQuietTime) {
+    if (checkForTouchpadQuietTime(when)) {
         // Case 1: Quiet time. (QUIET)
         ALOGD_IF(DEBUG_GESTURES, "Gestures: QUIET for next %0.3fms",
                  (mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval - when) *
@@ -2931,24 +2897,9 @@
         // Switch pointers if needed.
         // Find the fastest pointer and follow it.
         if (activeTouchId >= 0 && currentFingerCount > 1) {
-            int32_t bestId = -1;
-            float bestSpeed = mConfig.pointerGestureDragMinSwitchSpeed;
-            for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) {
-                uint32_t id = idBits.clearFirstMarkedBit();
-                std::optional<float> vx =
-                        mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, id);
-                std::optional<float> vy =
-                        mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, id);
-                if (vx && vy) {
-                    float speed = hypotf(*vx, *vy);
-                    if (speed > bestSpeed) {
-                        bestId = id;
-                        bestSpeed = speed;
-                    }
-                }
-            }
+            const auto [bestId, bestSpeed] = getFastestFinger();
             if (bestId >= 0 && bestId != activeTouchId) {
-                mPointerGesture.activeTouchId = activeTouchId = bestId;
+                mPointerGesture.activeTouchId = bestId;
                 ALOGD_IF(DEBUG_GESTURES,
                          "Gestures: BUTTON_CLICK_OR_DRAG switched pointers, bestId=%d, "
                          "bestSpeed=%0.3f",
@@ -3114,345 +3065,7 @@
         }
     } else {
         // Case 5. At least two fingers down, button is not pressed. (PRESS, SWIPE or FREEFORM)
-        // We need to provide feedback for each finger that goes down so we cannot wait
-        // for the fingers to move before deciding what to do.
-        //
-        // The ambiguous case is deciding what to do when there are two fingers down but they
-        // have not moved enough to determine whether they are part of a drag or part of a
-        // freeform gesture, or just a press or long-press at the pointer location.
-        //
-        // When there are two fingers we start with the PRESS hypothesis and we generate a
-        // down at the pointer location.
-        //
-        // When the two fingers move enough or when additional fingers are added, we make
-        // a decision to transition into SWIPE or FREEFORM mode accordingly.
-        ALOG_ASSERT(activeTouchId >= 0);
-
-        bool settled = when >=
-                mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval;
-        if (mPointerGesture.lastGestureMode != PointerGesture::Mode::PRESS &&
-            mPointerGesture.lastGestureMode != PointerGesture::Mode::SWIPE &&
-            mPointerGesture.lastGestureMode != PointerGesture::Mode::FREEFORM) {
-            *outFinishPreviousGesture = true;
-        } else if (!settled && currentFingerCount > lastFingerCount) {
-            // Additional pointers have gone down but not yet settled.
-            // Reset the gesture.
-            ALOGD_IF(DEBUG_GESTURES,
-                     "Gestures: Resetting gesture since additional pointers went down for "
-                     "MULTITOUCH, settle time remaining %0.3fms",
-                     (mPointerGesture.firstTouchTime +
-                      mConfig.pointerGestureMultitouchSettleInterval - when) *
-                             0.000001f);
-            *outCancelPreviousGesture = true;
-        } else {
-            // Continue previous gesture.
-            mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode;
-        }
-
-        if (*outFinishPreviousGesture || *outCancelPreviousGesture) {
-            mPointerGesture.currentGestureMode = PointerGesture::Mode::PRESS;
-            mPointerGesture.activeGestureId = 0;
-            mPointerGesture.referenceIdBits.clear();
-            mPointerVelocityControl.reset();
-
-            // Use the centroid and pointer location as the reference points for the gesture.
-            ALOGD_IF(DEBUG_GESTURES,
-                     "Gestures: Using centroid as reference for MULTITOUCH, settle time remaining "
-                     "%0.3fms",
-                     (mPointerGesture.firstTouchTime +
-                      mConfig.pointerGestureMultitouchSettleInterval - when) *
-                             0.000001f);
-            mCurrentRawState.rawPointerData
-                    .getCentroidOfTouchingPointers(&mPointerGesture.referenceTouchX,
-                                                   &mPointerGesture.referenceTouchY);
-            mPointerController->getPosition(&mPointerGesture.referenceGestureX,
-                                            &mPointerGesture.referenceGestureY);
-        }
-
-        // Clear the reference deltas for fingers not yet included in the reference calculation.
-        for (BitSet32 idBits(mCurrentCookedState.fingerIdBits.value &
-                             ~mPointerGesture.referenceIdBits.value);
-             !idBits.isEmpty();) {
-            uint32_t id = idBits.clearFirstMarkedBit();
-            mPointerGesture.referenceDeltas[id].dx = 0;
-            mPointerGesture.referenceDeltas[id].dy = 0;
-        }
-        mPointerGesture.referenceIdBits = mCurrentCookedState.fingerIdBits;
-
-        // Add delta for all fingers and calculate a common movement delta.
-        int32_t commonDeltaRawX = 0, commonDeltaRawY = 0;
-        BitSet32 commonIdBits(mLastCookedState.fingerIdBits.value &
-                              mCurrentCookedState.fingerIdBits.value);
-        for (BitSet32 idBits(commonIdBits); !idBits.isEmpty();) {
-            bool first = (idBits == commonIdBits);
-            uint32_t id = idBits.clearFirstMarkedBit();
-            const RawPointerData::Pointer& cpd = mCurrentRawState.rawPointerData.pointerForId(id);
-            const RawPointerData::Pointer& lpd = mLastRawState.rawPointerData.pointerForId(id);
-            PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
-            delta.dx += cpd.x - lpd.x;
-            delta.dy += cpd.y - lpd.y;
-
-            if (first) {
-                commonDeltaRawX = delta.dx;
-                commonDeltaRawY = delta.dy;
-            } else {
-                commonDeltaRawX = calculateCommonVector(commonDeltaRawX, delta.dx);
-                commonDeltaRawY = calculateCommonVector(commonDeltaRawY, delta.dy);
-            }
-        }
-
-        // Consider transitions from PRESS to SWIPE or MULTITOUCH.
-        if (mPointerGesture.currentGestureMode == PointerGesture::Mode::PRESS) {
-            float dist[MAX_POINTER_ID + 1];
-            int32_t distOverThreshold = 0;
-            for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) {
-                uint32_t id = idBits.clearFirstMarkedBit();
-                PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
-                dist[id] = hypotf(delta.dx * mPointerXZoomScale, delta.dy * mPointerYZoomScale);
-                if (dist[id] > mConfig.pointerGestureMultitouchMinDistance) {
-                    distOverThreshold += 1;
-                }
-            }
-
-            // Only transition when at least two pointers have moved further than
-            // the minimum distance threshold.
-            if (distOverThreshold >= 2) {
-                if (currentFingerCount > 2) {
-                    // There are more than two pointers, switch to FREEFORM.
-                    ALOGD_IF(DEBUG_GESTURES,
-                             "Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2",
-                             currentFingerCount);
-                    *outCancelPreviousGesture = true;
-                    mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
-                } else {
-                    // There are exactly two pointers.
-                    BitSet32 idBits(mCurrentCookedState.fingerIdBits);
-                    uint32_t id1 = idBits.clearFirstMarkedBit();
-                    uint32_t id2 = idBits.firstMarkedBit();
-                    const RawPointerData::Pointer& p1 =
-                            mCurrentRawState.rawPointerData.pointerForId(id1);
-                    const RawPointerData::Pointer& p2 =
-                            mCurrentRawState.rawPointerData.pointerForId(id2);
-                    float mutualDistance = distance(p1.x, p1.y, p2.x, p2.y);
-                    if (mutualDistance > mPointerGestureMaxSwipeWidth) {
-                        // There are two pointers but they are too far apart for a SWIPE,
-                        // switch to FREEFORM.
-                        ALOGD_IF(DEBUG_GESTURES,
-                                 "Gestures: PRESS transitioned to FREEFORM, distance %0.3f > %0.3f",
-                                 mutualDistance, mPointerGestureMaxSwipeWidth);
-                        *outCancelPreviousGesture = true;
-                        mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
-                    } else {
-                        // There are two pointers.  Wait for both pointers to start moving
-                        // before deciding whether this is a SWIPE or FREEFORM gesture.
-                        float dist1 = dist[id1];
-                        float dist2 = dist[id2];
-                        if (dist1 >= mConfig.pointerGestureMultitouchMinDistance &&
-                            dist2 >= mConfig.pointerGestureMultitouchMinDistance) {
-                            // Calculate the dot product of the displacement vectors.
-                            // When the vectors are oriented in approximately the same direction,
-                            // the angle betweeen them is near zero and the cosine of the angle
-                            // approches 1.0.  Recall that dot(v1, v2) = cos(angle) * mag(v1) *
-                            // mag(v2).
-                            PointerGesture::Delta& delta1 = mPointerGesture.referenceDeltas[id1];
-                            PointerGesture::Delta& delta2 = mPointerGesture.referenceDeltas[id2];
-                            float dx1 = delta1.dx * mPointerXZoomScale;
-                            float dy1 = delta1.dy * mPointerYZoomScale;
-                            float dx2 = delta2.dx * mPointerXZoomScale;
-                            float dy2 = delta2.dy * mPointerYZoomScale;
-                            float dot = dx1 * dx2 + dy1 * dy2;
-                            float cosine = dot / (dist1 * dist2); // denominator always > 0
-                            if (cosine >= mConfig.pointerGestureSwipeTransitionAngleCosine) {
-                                // Pointers are moving in the same direction.  Switch to SWIPE.
-                                ALOGD_IF(DEBUG_GESTURES,
-                                         "Gestures: PRESS transitioned to SWIPE, "
-                                         "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, "
-                                         "cosine %0.3f >= %0.3f",
-                                         dist1, mConfig.pointerGestureMultitouchMinDistance, dist2,
-                                         mConfig.pointerGestureMultitouchMinDistance, cosine,
-                                         mConfig.pointerGestureSwipeTransitionAngleCosine);
-                                mPointerGesture.currentGestureMode = PointerGesture::Mode::SWIPE;
-                            } else {
-                                // Pointers are moving in different directions.  Switch to FREEFORM.
-                                ALOGD_IF(DEBUG_GESTURES,
-                                         "Gestures: PRESS transitioned to FREEFORM, "
-                                         "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, "
-                                         "cosine %0.3f < %0.3f",
-                                         dist1, mConfig.pointerGestureMultitouchMinDistance, dist2,
-                                         mConfig.pointerGestureMultitouchMinDistance, cosine,
-                                         mConfig.pointerGestureSwipeTransitionAngleCosine);
-                                *outCancelPreviousGesture = true;
-                                mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
-                            }
-                        }
-                    }
-                }
-            }
-        } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) {
-            // Switch from SWIPE to FREEFORM if additional pointers go down.
-            // Cancel previous gesture.
-            if (currentFingerCount > 2) {
-                ALOGD_IF(DEBUG_GESTURES,
-                         "Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2",
-                         currentFingerCount);
-                *outCancelPreviousGesture = true;
-                mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
-            }
-        }
-
-        // Move the reference points based on the overall group motion of the fingers
-        // except in PRESS mode while waiting for a transition to occur.
-        if (mPointerGesture.currentGestureMode != PointerGesture::Mode::PRESS &&
-            (commonDeltaRawX || commonDeltaRawY)) {
-            for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) {
-                uint32_t id = idBits.clearFirstMarkedBit();
-                PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
-                delta.dx = 0;
-                delta.dy = 0;
-            }
-
-            mPointerGesture.referenceTouchX += commonDeltaRawX;
-            mPointerGesture.referenceTouchY += commonDeltaRawY;
-
-            float commonDeltaX = commonDeltaRawX * mPointerXMovementScale;
-            float commonDeltaY = commonDeltaRawY * mPointerYMovementScale;
-
-            rotateDelta(mInputDeviceOrientation, &commonDeltaX, &commonDeltaY);
-            mPointerVelocityControl.move(when, &commonDeltaX, &commonDeltaY);
-
-            mPointerGesture.referenceGestureX += commonDeltaX;
-            mPointerGesture.referenceGestureY += commonDeltaY;
-        }
-
-        // Report gestures.
-        if (mPointerGesture.currentGestureMode == PointerGesture::Mode::PRESS ||
-            mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) {
-            // PRESS or SWIPE mode.
-            ALOGD_IF(DEBUG_GESTURES,
-                     "Gestures: PRESS or SWIPE activeTouchId=%d, activeGestureId=%d, "
-                     "currentTouchPointerCount=%d",
-                     activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
-            ALOG_ASSERT(mPointerGesture.activeGestureId >= 0);
-
-            mPointerGesture.currentGestureIdBits.clear();
-            mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
-            mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
-            mPointerGesture.currentGestureProperties[0].clear();
-            mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
-            mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
-            mPointerGesture.currentGestureCoords[0].clear();
-            mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
-                                                                 mPointerGesture.referenceGestureX);
-            mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y,
-                                                                 mPointerGesture.referenceGestureY);
-            mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
-            if (mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) {
-                float xOffset = static_cast<float>(commonDeltaRawX) /
-                        (mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue);
-                float yOffset = static_cast<float>(commonDeltaRawY) /
-                        (mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue);
-                mPointerGesture.currentGestureCoords[0]
-                        .setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, xOffset);
-                mPointerGesture.currentGestureCoords[0]
-                        .setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, yOffset);
-            }
-        } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::FREEFORM) {
-            // FREEFORM mode.
-            ALOGD_IF(DEBUG_GESTURES,
-                     "Gestures: FREEFORM activeTouchId=%d, activeGestureId=%d, "
-                     "currentTouchPointerCount=%d",
-                     activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
-            ALOG_ASSERT(mPointerGesture.activeGestureId >= 0);
-
-            mPointerGesture.currentGestureIdBits.clear();
-
-            BitSet32 mappedTouchIdBits;
-            BitSet32 usedGestureIdBits;
-            if (mPointerGesture.lastGestureMode != PointerGesture::Mode::FREEFORM) {
-                // Initially, assign the active gesture id to the active touch point
-                // if there is one.  No other touch id bits are mapped yet.
-                if (!*outCancelPreviousGesture) {
-                    mappedTouchIdBits.markBit(activeTouchId);
-                    usedGestureIdBits.markBit(mPointerGesture.activeGestureId);
-                    mPointerGesture.freeformTouchToGestureIdMap[activeTouchId] =
-                            mPointerGesture.activeGestureId;
-                } else {
-                    mPointerGesture.activeGestureId = -1;
-                }
-            } else {
-                // Otherwise, assume we mapped all touches from the previous frame.
-                // Reuse all mappings that are still applicable.
-                mappedTouchIdBits.value = mLastCookedState.fingerIdBits.value &
-                        mCurrentCookedState.fingerIdBits.value;
-                usedGestureIdBits = mPointerGesture.lastGestureIdBits;
-
-                // Check whether we need to choose a new active gesture id because the
-                // current went went up.
-                for (BitSet32 upTouchIdBits(mLastCookedState.fingerIdBits.value &
-                                            ~mCurrentCookedState.fingerIdBits.value);
-                     !upTouchIdBits.isEmpty();) {
-                    uint32_t upTouchId = upTouchIdBits.clearFirstMarkedBit();
-                    uint32_t upGestureId = mPointerGesture.freeformTouchToGestureIdMap[upTouchId];
-                    if (upGestureId == uint32_t(mPointerGesture.activeGestureId)) {
-                        mPointerGesture.activeGestureId = -1;
-                        break;
-                    }
-                }
-            }
-
-            ALOGD_IF(DEBUG_GESTURES,
-                     "Gestures: FREEFORM follow up mappedTouchIdBits=0x%08x, "
-                     "usedGestureIdBits=0x%08x, activeGestureId=%d",
-                     mappedTouchIdBits.value, usedGestureIdBits.value,
-                     mPointerGesture.activeGestureId);
-
-            BitSet32 idBits(mCurrentCookedState.fingerIdBits);
-            for (uint32_t i = 0; i < currentFingerCount; i++) {
-                uint32_t touchId = idBits.clearFirstMarkedBit();
-                uint32_t gestureId;
-                if (!mappedTouchIdBits.hasBit(touchId)) {
-                    gestureId = usedGestureIdBits.markFirstUnmarkedBit();
-                    mPointerGesture.freeformTouchToGestureIdMap[touchId] = gestureId;
-                    ALOGD_IF(DEBUG_GESTURES,
-                             "Gestures: FREEFORM new mapping for touch id %d -> gesture id %d",
-                             touchId, gestureId);
-                } else {
-                    gestureId = mPointerGesture.freeformTouchToGestureIdMap[touchId];
-                    ALOGD_IF(DEBUG_GESTURES,
-                             "Gestures: FREEFORM existing mapping for touch id %d -> gesture id %d",
-                             touchId, gestureId);
-                }
-                mPointerGesture.currentGestureIdBits.markBit(gestureId);
-                mPointerGesture.currentGestureIdToIndex[gestureId] = i;
-
-                const RawPointerData::Pointer& pointer =
-                        mCurrentRawState.rawPointerData.pointerForId(touchId);
-                float deltaX = (pointer.x - mPointerGesture.referenceTouchX) * mPointerXZoomScale;
-                float deltaY = (pointer.y - mPointerGesture.referenceTouchY) * mPointerYZoomScale;
-                rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY);
-
-                mPointerGesture.currentGestureProperties[i].clear();
-                mPointerGesture.currentGestureProperties[i].id = gestureId;
-                mPointerGesture.currentGestureProperties[i].toolType =
-                        AMOTION_EVENT_TOOL_TYPE_FINGER;
-                mPointerGesture.currentGestureCoords[i].clear();
-                mPointerGesture.currentGestureCoords[i]
-                        .setAxisValue(AMOTION_EVENT_AXIS_X,
-                                      mPointerGesture.referenceGestureX + deltaX);
-                mPointerGesture.currentGestureCoords[i]
-                        .setAxisValue(AMOTION_EVENT_AXIS_Y,
-                                      mPointerGesture.referenceGestureY + deltaY);
-                mPointerGesture.currentGestureCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE,
-                                                                     1.0f);
-            }
-
-            if (mPointerGesture.activeGestureId < 0) {
-                mPointerGesture.activeGestureId =
-                        mPointerGesture.currentGestureIdBits.firstMarkedBit();
-                ALOGD_IF(DEBUG_GESTURES, "Gestures: FREEFORM new activeGestureId=%d",
-                         mPointerGesture.activeGestureId);
-            }
-        }
+        prepareMultiFingerPointerGestures(when, outCancelPreviousGesture, outFinishPreviousGesture);
     }
 
     mPointerController->setButtonState(mCurrentRawState.buttonState);
@@ -3490,6 +3103,399 @@
     return true;
 }
 
+bool TouchInputMapper::checkForTouchpadQuietTime(nsecs_t when) {
+    if (mPointerGesture.activeTouchId < 0) {
+        mPointerGesture.resetQuietTime();
+        return false;
+    }
+
+    if (when < mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval) {
+        return true;
+    }
+
+    const uint32_t currentFingerCount = mCurrentCookedState.fingerIdBits.count();
+    bool isQuietTime = false;
+    if ((mPointerGesture.lastGestureMode == PointerGesture::Mode::PRESS ||
+         mPointerGesture.lastGestureMode == PointerGesture::Mode::SWIPE ||
+         mPointerGesture.lastGestureMode == PointerGesture::Mode::FREEFORM) &&
+        currentFingerCount < 2) {
+        // Enter quiet time when exiting swipe or freeform state.
+        // This is to prevent accidentally entering the hover state and flinging the
+        // pointer when finishing a swipe and there is still one pointer left onscreen.
+        isQuietTime = true;
+    } else if (mPointerGesture.lastGestureMode == PointerGesture::Mode::BUTTON_CLICK_OR_DRAG &&
+               currentFingerCount >= 2 && !isPointerDown(mCurrentRawState.buttonState)) {
+        // Enter quiet time when releasing the button and there are still two or more
+        // fingers down.  This may indicate that one finger was used to press the button
+        // but it has not gone up yet.
+        isQuietTime = true;
+    }
+    if (isQuietTime) {
+        mPointerGesture.quietTime = when;
+    }
+    return isQuietTime;
+}
+
+std::pair<int32_t, float> TouchInputMapper::getFastestFinger() {
+    int32_t bestId = -1;
+    float bestSpeed = mConfig.pointerGestureDragMinSwitchSpeed;
+    for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) {
+        uint32_t id = idBits.clearFirstMarkedBit();
+        std::optional<float> vx =
+                mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, id);
+        std::optional<float> vy =
+                mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, id);
+        if (vx && vy) {
+            float speed = hypotf(*vx, *vy);
+            if (speed > bestSpeed) {
+                bestId = id;
+                bestSpeed = speed;
+            }
+        }
+    }
+    return std::make_pair(bestId, bestSpeed);
+}
+
+void TouchInputMapper::prepareMultiFingerPointerGestures(nsecs_t when, bool* cancelPreviousGesture,
+                                                         bool* finishPreviousGesture) {
+    // We need to provide feedback for each finger that goes down so we cannot wait for the fingers
+    // to move before deciding what to do.
+    //
+    // The ambiguous case is deciding what to do when there are two fingers down but they have not
+    // moved enough to determine whether they are part of a drag or part of a freeform gesture, or
+    // just a press or long-press at the pointer location.
+    //
+    // When there are two fingers we start with the PRESS hypothesis and we generate a down at the
+    // pointer location.
+    //
+    // When the two fingers move enough or when additional fingers are added, we make a decision to
+    // transition into SWIPE or FREEFORM mode accordingly.
+    const int32_t activeTouchId = mPointerGesture.activeTouchId;
+    ALOG_ASSERT(activeTouchId >= 0);
+
+    const uint32_t currentFingerCount = mCurrentCookedState.fingerIdBits.count();
+    const uint32_t lastFingerCount = mLastCookedState.fingerIdBits.count();
+    bool settled =
+            when >= mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval;
+    if (mPointerGesture.lastGestureMode != PointerGesture::Mode::PRESS &&
+        mPointerGesture.lastGestureMode != PointerGesture::Mode::SWIPE &&
+        mPointerGesture.lastGestureMode != PointerGesture::Mode::FREEFORM) {
+        *finishPreviousGesture = true;
+    } else if (!settled && currentFingerCount > lastFingerCount) {
+        // Additional pointers have gone down but not yet settled.
+        // Reset the gesture.
+        ALOGD_IF(DEBUG_GESTURES,
+                 "Gestures: Resetting gesture since additional pointers went down for "
+                 "MULTITOUCH, settle time remaining %0.3fms",
+                 (mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval -
+                  when) * 0.000001f);
+        *cancelPreviousGesture = true;
+    } else {
+        // Continue previous gesture.
+        mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode;
+    }
+
+    if (*finishPreviousGesture || *cancelPreviousGesture) {
+        mPointerGesture.currentGestureMode = PointerGesture::Mode::PRESS;
+        mPointerGesture.activeGestureId = 0;
+        mPointerGesture.referenceIdBits.clear();
+        mPointerVelocityControl.reset();
+
+        // Use the centroid and pointer location as the reference points for the gesture.
+        ALOGD_IF(DEBUG_GESTURES,
+                 "Gestures: Using centroid as reference for MULTITOUCH, settle time remaining "
+                 "%0.3fms",
+                 (mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval -
+                  when) * 0.000001f);
+        mCurrentRawState.rawPointerData
+                .getCentroidOfTouchingPointers(&mPointerGesture.referenceTouchX,
+                                               &mPointerGesture.referenceTouchY);
+        mPointerController->getPosition(&mPointerGesture.referenceGestureX,
+                                        &mPointerGesture.referenceGestureY);
+    }
+
+    // Clear the reference deltas for fingers not yet included in the reference calculation.
+    for (BitSet32 idBits(mCurrentCookedState.fingerIdBits.value &
+                         ~mPointerGesture.referenceIdBits.value);
+         !idBits.isEmpty();) {
+        uint32_t id = idBits.clearFirstMarkedBit();
+        mPointerGesture.referenceDeltas[id].dx = 0;
+        mPointerGesture.referenceDeltas[id].dy = 0;
+    }
+    mPointerGesture.referenceIdBits = mCurrentCookedState.fingerIdBits;
+
+    // Add delta for all fingers and calculate a common movement delta.
+    int32_t commonDeltaRawX = 0, commonDeltaRawY = 0;
+    BitSet32 commonIdBits(mLastCookedState.fingerIdBits.value &
+                          mCurrentCookedState.fingerIdBits.value);
+    for (BitSet32 idBits(commonIdBits); !idBits.isEmpty();) {
+        bool first = (idBits == commonIdBits);
+        uint32_t id = idBits.clearFirstMarkedBit();
+        const RawPointerData::Pointer& cpd = mCurrentRawState.rawPointerData.pointerForId(id);
+        const RawPointerData::Pointer& lpd = mLastRawState.rawPointerData.pointerForId(id);
+        PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
+        delta.dx += cpd.x - lpd.x;
+        delta.dy += cpd.y - lpd.y;
+
+        if (first) {
+            commonDeltaRawX = delta.dx;
+            commonDeltaRawY = delta.dy;
+        } else {
+            commonDeltaRawX = calculateCommonVector(commonDeltaRawX, delta.dx);
+            commonDeltaRawY = calculateCommonVector(commonDeltaRawY, delta.dy);
+        }
+    }
+
+    // Consider transitions from PRESS to SWIPE or MULTITOUCH.
+    if (mPointerGesture.currentGestureMode == PointerGesture::Mode::PRESS) {
+        float dist[MAX_POINTER_ID + 1];
+        int32_t distOverThreshold = 0;
+        for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) {
+            uint32_t id = idBits.clearFirstMarkedBit();
+            PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
+            dist[id] = hypotf(delta.dx * mPointerXZoomScale, delta.dy * mPointerYZoomScale);
+            if (dist[id] > mConfig.pointerGestureMultitouchMinDistance) {
+                distOverThreshold += 1;
+            }
+        }
+
+        // Only transition when at least two pointers have moved further than
+        // the minimum distance threshold.
+        if (distOverThreshold >= 2) {
+            if (currentFingerCount > 2) {
+                // There are more than two pointers, switch to FREEFORM.
+                ALOGD_IF(DEBUG_GESTURES,
+                         "Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2",
+                         currentFingerCount);
+                *cancelPreviousGesture = true;
+                mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
+            } else {
+                // There are exactly two pointers.
+                BitSet32 idBits(mCurrentCookedState.fingerIdBits);
+                uint32_t id1 = idBits.clearFirstMarkedBit();
+                uint32_t id2 = idBits.firstMarkedBit();
+                const RawPointerData::Pointer& p1 =
+                        mCurrentRawState.rawPointerData.pointerForId(id1);
+                const RawPointerData::Pointer& p2 =
+                        mCurrentRawState.rawPointerData.pointerForId(id2);
+                float mutualDistance = distance(p1.x, p1.y, p2.x, p2.y);
+                if (mutualDistance > mPointerGestureMaxSwipeWidth) {
+                    // There are two pointers but they are too far apart for a SWIPE,
+                    // switch to FREEFORM.
+                    ALOGD_IF(DEBUG_GESTURES,
+                             "Gestures: PRESS transitioned to FREEFORM, distance %0.3f > %0.3f",
+                             mutualDistance, mPointerGestureMaxSwipeWidth);
+                    *cancelPreviousGesture = true;
+                    mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
+                } else {
+                    // There are two pointers.  Wait for both pointers to start moving
+                    // before deciding whether this is a SWIPE or FREEFORM gesture.
+                    float dist1 = dist[id1];
+                    float dist2 = dist[id2];
+                    if (dist1 >= mConfig.pointerGestureMultitouchMinDistance &&
+                        dist2 >= mConfig.pointerGestureMultitouchMinDistance) {
+                        // Calculate the dot product of the displacement vectors.
+                        // When the vectors are oriented in approximately the same direction,
+                        // the angle betweeen them is near zero and the cosine of the angle
+                        // approaches 1.0.  Recall that dot(v1, v2) = cos(angle) * mag(v1) *
+                        // mag(v2).
+                        PointerGesture::Delta& delta1 = mPointerGesture.referenceDeltas[id1];
+                        PointerGesture::Delta& delta2 = mPointerGesture.referenceDeltas[id2];
+                        float dx1 = delta1.dx * mPointerXZoomScale;
+                        float dy1 = delta1.dy * mPointerYZoomScale;
+                        float dx2 = delta2.dx * mPointerXZoomScale;
+                        float dy2 = delta2.dy * mPointerYZoomScale;
+                        float dot = dx1 * dx2 + dy1 * dy2;
+                        float cosine = dot / (dist1 * dist2); // denominator always > 0
+                        if (cosine >= mConfig.pointerGestureSwipeTransitionAngleCosine) {
+                            // Pointers are moving in the same direction.  Switch to SWIPE.
+                            ALOGD_IF(DEBUG_GESTURES,
+                                     "Gestures: PRESS transitioned to SWIPE, "
+                                     "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, "
+                                     "cosine %0.3f >= %0.3f",
+                                     dist1, mConfig.pointerGestureMultitouchMinDistance, dist2,
+                                     mConfig.pointerGestureMultitouchMinDistance, cosine,
+                                     mConfig.pointerGestureSwipeTransitionAngleCosine);
+                            mPointerGesture.currentGestureMode = PointerGesture::Mode::SWIPE;
+                        } else {
+                            // Pointers are moving in different directions.  Switch to FREEFORM.
+                            ALOGD_IF(DEBUG_GESTURES,
+                                     "Gestures: PRESS transitioned to FREEFORM, "
+                                     "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, "
+                                     "cosine %0.3f < %0.3f",
+                                     dist1, mConfig.pointerGestureMultitouchMinDistance, dist2,
+                                     mConfig.pointerGestureMultitouchMinDistance, cosine,
+                                     mConfig.pointerGestureSwipeTransitionAngleCosine);
+                            *cancelPreviousGesture = true;
+                            mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
+                        }
+                    }
+                }
+            }
+        }
+    } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) {
+        // Switch from SWIPE to FREEFORM if additional pointers go down.
+        // Cancel previous gesture.
+        if (currentFingerCount > 2) {
+            ALOGD_IF(DEBUG_GESTURES,
+                     "Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2",
+                     currentFingerCount);
+            *cancelPreviousGesture = true;
+            mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
+        }
+    }
+
+    // Move the reference points based on the overall group motion of the fingers
+    // except in PRESS mode while waiting for a transition to occur.
+    if (mPointerGesture.currentGestureMode != PointerGesture::Mode::PRESS &&
+        (commonDeltaRawX || commonDeltaRawY)) {
+        for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) {
+            uint32_t id = idBits.clearFirstMarkedBit();
+            PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
+            delta.dx = 0;
+            delta.dy = 0;
+        }
+
+        mPointerGesture.referenceTouchX += commonDeltaRawX;
+        mPointerGesture.referenceTouchY += commonDeltaRawY;
+
+        float commonDeltaX = commonDeltaRawX * mPointerXMovementScale;
+        float commonDeltaY = commonDeltaRawY * mPointerYMovementScale;
+
+        rotateDelta(mInputDeviceOrientation, &commonDeltaX, &commonDeltaY);
+        mPointerVelocityControl.move(when, &commonDeltaX, &commonDeltaY);
+
+        mPointerGesture.referenceGestureX += commonDeltaX;
+        mPointerGesture.referenceGestureY += commonDeltaY;
+    }
+
+    // Report gestures.
+    if (mPointerGesture.currentGestureMode == PointerGesture::Mode::PRESS ||
+        mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) {
+        // PRESS or SWIPE mode.
+        ALOGD_IF(DEBUG_GESTURES,
+                 "Gestures: PRESS or SWIPE activeTouchId=%d, activeGestureId=%d, "
+                 "currentTouchPointerCount=%d",
+                 activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
+        ALOG_ASSERT(mPointerGesture.activeGestureId >= 0);
+
+        mPointerGesture.currentGestureIdBits.clear();
+        mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
+        mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
+        mPointerGesture.currentGestureProperties[0].clear();
+        mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
+        mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+        mPointerGesture.currentGestureCoords[0].clear();
+        mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
+                                                             mPointerGesture.referenceGestureX);
+        mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y,
+                                                             mPointerGesture.referenceGestureY);
+        mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+        if (mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) {
+            float xOffset = static_cast<float>(commonDeltaRawX) /
+                    (mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue);
+            float yOffset = static_cast<float>(commonDeltaRawY) /
+                    (mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue);
+            mPointerGesture.currentGestureCoords[0]
+                    .setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, xOffset);
+            mPointerGesture.currentGestureCoords[0]
+                    .setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, yOffset);
+        }
+    } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::FREEFORM) {
+        // FREEFORM mode.
+        ALOGD_IF(DEBUG_GESTURES,
+                 "Gestures: FREEFORM activeTouchId=%d, activeGestureId=%d, "
+                 "currentTouchPointerCount=%d",
+                 activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
+        ALOG_ASSERT(mPointerGesture.activeGestureId >= 0);
+
+        mPointerGesture.currentGestureIdBits.clear();
+
+        BitSet32 mappedTouchIdBits;
+        BitSet32 usedGestureIdBits;
+        if (mPointerGesture.lastGestureMode != PointerGesture::Mode::FREEFORM) {
+            // Initially, assign the active gesture id to the active touch point
+            // if there is one.  No other touch id bits are mapped yet.
+            if (!*cancelPreviousGesture) {
+                mappedTouchIdBits.markBit(activeTouchId);
+                usedGestureIdBits.markBit(mPointerGesture.activeGestureId);
+                mPointerGesture.freeformTouchToGestureIdMap[activeTouchId] =
+                        mPointerGesture.activeGestureId;
+            } else {
+                mPointerGesture.activeGestureId = -1;
+            }
+        } else {
+            // Otherwise, assume we mapped all touches from the previous frame.
+            // Reuse all mappings that are still applicable.
+            mappedTouchIdBits.value =
+                    mLastCookedState.fingerIdBits.value & mCurrentCookedState.fingerIdBits.value;
+            usedGestureIdBits = mPointerGesture.lastGestureIdBits;
+
+            // Check whether we need to choose a new active gesture id because the
+            // current went went up.
+            for (BitSet32 upTouchIdBits(mLastCookedState.fingerIdBits.value &
+                                        ~mCurrentCookedState.fingerIdBits.value);
+                 !upTouchIdBits.isEmpty();) {
+                uint32_t upTouchId = upTouchIdBits.clearFirstMarkedBit();
+                uint32_t upGestureId = mPointerGesture.freeformTouchToGestureIdMap[upTouchId];
+                if (upGestureId == uint32_t(mPointerGesture.activeGestureId)) {
+                    mPointerGesture.activeGestureId = -1;
+                    break;
+                }
+            }
+        }
+
+        ALOGD_IF(DEBUG_GESTURES,
+                 "Gestures: FREEFORM follow up mappedTouchIdBits=0x%08x, usedGestureIdBits=0x%08x, "
+                 "activeGestureId=%d",
+                 mappedTouchIdBits.value, usedGestureIdBits.value, mPointerGesture.activeGestureId);
+
+        BitSet32 idBits(mCurrentCookedState.fingerIdBits);
+        for (uint32_t i = 0; i < currentFingerCount; i++) {
+            uint32_t touchId = idBits.clearFirstMarkedBit();
+            uint32_t gestureId;
+            if (!mappedTouchIdBits.hasBit(touchId)) {
+                gestureId = usedGestureIdBits.markFirstUnmarkedBit();
+                mPointerGesture.freeformTouchToGestureIdMap[touchId] = gestureId;
+                ALOGD_IF(DEBUG_GESTURES,
+                         "Gestures: FREEFORM new mapping for touch id %d -> gesture id %d", touchId,
+                         gestureId);
+            } else {
+                gestureId = mPointerGesture.freeformTouchToGestureIdMap[touchId];
+                ALOGD_IF(DEBUG_GESTURES,
+                         "Gestures: FREEFORM existing mapping for touch id %d -> gesture id %d",
+                         touchId, gestureId);
+            }
+            mPointerGesture.currentGestureIdBits.markBit(gestureId);
+            mPointerGesture.currentGestureIdToIndex[gestureId] = i;
+
+            const RawPointerData::Pointer& pointer =
+                    mCurrentRawState.rawPointerData.pointerForId(touchId);
+            float deltaX = (pointer.x - mPointerGesture.referenceTouchX) * mPointerXZoomScale;
+            float deltaY = (pointer.y - mPointerGesture.referenceTouchY) * mPointerYZoomScale;
+            rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY);
+
+            mPointerGesture.currentGestureProperties[i].clear();
+            mPointerGesture.currentGestureProperties[i].id = gestureId;
+            mPointerGesture.currentGestureProperties[i].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+            mPointerGesture.currentGestureCoords[i].clear();
+            mPointerGesture.currentGestureCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X,
+                                                                 mPointerGesture.referenceGestureX +
+                                                                         deltaX);
+            mPointerGesture.currentGestureCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y,
+                                                                 mPointerGesture.referenceGestureY +
+                                                                         deltaY);
+            mPointerGesture.currentGestureCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+        }
+
+        if (mPointerGesture.activeGestureId < 0) {
+            mPointerGesture.activeGestureId = mPointerGesture.currentGestureIdBits.firstMarkedBit();
+            ALOGD_IF(DEBUG_GESTURES, "Gestures: FREEFORM new activeGestureId=%d",
+                     mPointerGesture.activeGestureId);
+        }
+    }
+}
+
 void TouchInputMapper::moveMousePointerFromPointerDelta(nsecs_t when, uint32_t pointerId) {
     const RawPointerData::Pointer& currentPointer =
             mCurrentRawState.rawPointerData.pointerForId(pointerId);
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 7b0327e..7680090 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -773,6 +773,14 @@
     bool preparePointerGestures(nsecs_t when, bool* outCancelPreviousGesture,
                                 bool* outFinishPreviousGesture, bool isTimeout);
 
+    // Returns true if we're in a period of "quiet time" when touchpad gestures should be ignored.
+    bool checkForTouchpadQuietTime(nsecs_t when);
+
+    std::pair<int32_t, float> getFastestFinger();
+
+    void prepareMultiFingerPointerGestures(nsecs_t when, bool* outCancelPreviousGesture,
+                                           bool* outFinishPreviousGesture);
+
     // Moves the on-screen mouse pointer based on the movement of the pointer of the given ID
     // between the last and current events. Uses a relative motion.
     void moveMousePointerFromPointerDelta(nsecs_t when, uint32_t pointerId);
diff --git a/services/surfaceflinger/Display/DisplayModeRequest.h b/services/surfaceflinger/Display/DisplayModeRequest.h
new file mode 100644
index 0000000..ac25fe0
--- /dev/null
+++ b/services/surfaceflinger/Display/DisplayModeRequest.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <ftl/non_null.h>
+
+#include "DisplayHardware/DisplayMode.h"
+
+namespace android::display {
+
+struct DisplayModeRequest {
+    ftl::NonNull<DisplayModePtr> modePtr;
+
+    // Whether to emit DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE.
+    bool emitEvent = false;
+};
+
+inline bool operator==(const DisplayModeRequest& lhs, const DisplayModeRequest& rhs) {
+    return lhs.modePtr == rhs.modePtr && lhs.emitEvent == rhs.emitEvent;
+}
+
+} // namespace android::display
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 06a812b..7abb94b 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -41,6 +41,7 @@
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
 
+#include "Display/DisplayModeRequest.h"
 #include "DisplayHardware/DisplayMode.h"
 #include "DisplayHardware/Hal.h"
 #include "DisplayHardware/PowerAdvisor.h"
@@ -190,9 +191,20 @@
     /* ------------------------------------------------------------------------
      * Display mode management.
      */
+
+    // TODO(b/241285876): Replace ActiveModeInfo and DisplayModeEvent with DisplayModeRequest.
     struct ActiveModeInfo {
+        using Event = scheduler::DisplayModeEvent;
+
+        ActiveModeInfo() = default;
+        ActiveModeInfo(DisplayModePtr mode, Event event) : mode(std::move(mode)), event(event) {}
+
+        explicit ActiveModeInfo(display::DisplayModeRequest&& request)
+              : ActiveModeInfo(std::move(request.modePtr).take(),
+                               request.emitEvent ? Event::Changed : Event::None) {}
+
         DisplayModePtr mode;
-        scheduler::DisplayModeEvent event = scheduler::DisplayModeEvent::None;
+        Event event = Event::None;
 
         bool operator!=(const ActiveModeInfo& other) const {
             return mode != other.mode || event != other.event;
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 631cf65..9777092 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -1015,8 +1015,10 @@
     return priority == PRIORITY_FOCUSED_WITH_MODE || priority == PRIORITY_FOCUSED_WITHOUT_MODE;
 };
 
-ui::LayerStack Layer::getLayerStack() const {
-    if (const auto parent = mDrawingParent.promote()) {
+ui::LayerStack Layer::getLayerStack(LayerVector::StateSet state) const {
+    bool useDrawing = state == LayerVector::StateSet::Drawing;
+    const auto parent = useDrawing ? mDrawingParent.promote() : mCurrentParent.promote();
+    if (parent) {
         return parent->getLayerStack();
     }
     return getDrawingState().layerStack;
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index c2b8169..a3c4e59 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -280,7 +280,9 @@
     virtual bool setTrustedOverlay(bool);
     virtual bool setFlags(uint32_t flags, uint32_t mask);
     virtual bool setLayerStack(ui::LayerStack);
-    virtual ui::LayerStack getLayerStack() const;
+    virtual ui::LayerStack getLayerStack(
+            LayerVector::StateSet state = LayerVector::StateSet::Drawing) const;
+
     virtual bool setMetadata(const LayerMetadata& data);
     virtual void setChildrenDrawingParent(const sp<Layer>&);
     virtual bool reparent(const sp<IBinder>& newParentHandle);
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 30483a2..39850c7 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -23,6 +23,7 @@
 
 #include <chrono>
 #include <cmath>
+#include <deque>
 
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
@@ -143,8 +144,7 @@
 
         ATRACE_INT(name.c_str(), static_cast<int>(std::round(overallScore * 100)));
 
-        constexpr float kEpsilon = 0.0001f;
-        if (std::abs(overallScore - rhs.overallScore) > kEpsilon) {
+        if (!ScoredRefreshRate::scoresEqual(overallScore, rhs.overallScore)) {
             return overallScore > rhs.overallScore;
         }
 
@@ -288,8 +288,7 @@
 }
 
 auto RefreshRateConfigs::getRankedRefreshRates(const std::vector<LayerRequirement>& layers,
-                                               GlobalSignals signals) const
-        -> std::pair<std::vector<RefreshRateRanking>, GlobalSignals> {
+                                               GlobalSignals signals) const -> RankedRefreshRates {
     std::lock_guard lock(mLock);
 
     if (mGetRankedRefreshRatesCache &&
@@ -304,7 +303,7 @@
 
 auto RefreshRateConfigs::getRankedRefreshRatesLocked(const std::vector<LayerRequirement>& layers,
                                                      GlobalSignals signals) const
-        -> std::pair<std::vector<RefreshRateRanking>, GlobalSignals> {
+        -> RankedRefreshRates {
     using namespace fps_approx_ops;
     ATRACE_CALL();
     ALOGV("%s: %zu layers", __func__, layers.size());
@@ -314,8 +313,7 @@
     // Keep the display at max refresh rate for the duration of powering on the display.
     if (signals.powerOnImminent) {
         ALOGV("Power On Imminent");
-        return {getRefreshRatesByPolicyLocked(activeMode.getGroup(), RefreshRateOrder::Descending,
-                                              /*preferredDisplayModeOpt*/ std::nullopt),
+        return {rankRefreshRates(activeMode.getGroup(), RefreshRateOrder::Descending),
                 GlobalSignals{.powerOnImminent = true}};
     }
 
@@ -375,8 +373,7 @@
     // selected a refresh rate to see if we should apply touch boost.
     if (signals.touch && !hasExplicitVoteLayers) {
         ALOGV("Touch Boost");
-        return {getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Descending,
-                                              /*preferredDisplayModeOpt*/ std::nullopt),
+        return {rankRefreshRates(anchorGroup, RefreshRateOrder::Descending),
                 GlobalSignals{.touch = true}};
     }
 
@@ -388,24 +385,19 @@
 
     if (!signals.touch && signals.idle && !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {
         ALOGV("Idle");
-        return {getRefreshRatesByPolicyLocked(activeMode.getGroup(), RefreshRateOrder::Ascending,
-                                              /*preferredDisplayModeOpt*/ std::nullopt),
+        return {rankRefreshRates(activeMode.getGroup(), RefreshRateOrder::Ascending),
                 GlobalSignals{.idle = true}};
     }
 
     if (layers.empty() || noVoteLayers == layers.size()) {
         ALOGV("No layers with votes");
-        return {getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Descending,
-                                              /*preferredDisplayModeOpt*/ std::nullopt),
-                kNoSignals};
+        return {rankRefreshRates(anchorGroup, RefreshRateOrder::Descending), kNoSignals};
     }
 
     // Only if all layers want Min we should return Min
     if (noVoteLayers + minVoteLayers == layers.size()) {
         ALOGV("All layers Min");
-        return {getRefreshRatesByPolicyLocked(activeMode.getGroup(), RefreshRateOrder::Ascending,
-                                              /*preferredDisplayModeOpt*/ std::nullopt),
-                kNoSignals};
+        return {rankRefreshRates(activeMode.getGroup(), RefreshRateOrder::Ascending), kNoSignals};
     }
 
     // Find the best refresh rate based on score
@@ -557,12 +549,13 @@
             maxVoteLayers > 0 ? RefreshRateOrder::Descending : RefreshRateOrder::Ascending;
     std::sort(scores.begin(), scores.end(),
               RefreshRateScoreComparator{.refreshRateOrder = refreshRateOrder});
-    std::vector<RefreshRateRanking> rankedRefreshRates;
-    rankedRefreshRates.reserve(scores.size());
 
-    std::transform(scores.begin(), scores.end(), back_inserter(rankedRefreshRates),
+    RefreshRateRanking ranking;
+    ranking.reserve(scores.size());
+
+    std::transform(scores.begin(), scores.end(), back_inserter(ranking),
                    [](const RefreshRateScore& score) {
-                       return RefreshRateRanking{score.modeIt->second, score.overallScore};
+                       return ScoredRefreshRate{score.modeIt->second, score.overallScore};
                    });
 
     const bool noLayerScore = std::all_of(scores.begin(), scores.end(), [](RefreshRateScore score) {
@@ -574,11 +567,9 @@
         // range instead of picking a random score from the app range.
         if (noLayerScore) {
             ALOGV("Layers not scored");
-            return {getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Descending,
-                                                  /*preferredDisplayModeOpt*/ std::nullopt),
-                    kNoSignals};
+            return {rankRefreshRates(anchorGroup, RefreshRateOrder::Descending), kNoSignals};
         } else {
-            return {rankedRefreshRates, kNoSignals};
+            return {ranking, kNoSignals};
         }
     }
 
@@ -596,14 +587,12 @@
         }
     }();
 
-    const auto& touchRefreshRates =
-            getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Descending,
-                                          /*preferredDisplayModeOpt*/ std::nullopt);
+    const auto touchRefreshRates = rankRefreshRates(anchorGroup, RefreshRateOrder::Descending);
+
     using fps_approx_ops::operator<;
 
     if (signals.touch && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact &&
-        scores.front().modeIt->second->getFps() <
-                touchRefreshRates.front().displayModePtr->getFps()) {
+        scores.front().modeIt->second->getFps() < touchRefreshRates.front().modePtr->getFps()) {
         ALOGV("Touch Boost");
         return {touchRefreshRates, GlobalSignals{.touch = true}};
     }
@@ -612,12 +601,11 @@
     // current config
     if (noLayerScore && refreshRateOrder == RefreshRateOrder::Ascending) {
         const auto preferredDisplayMode = activeMode.getId();
-        return {getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Ascending,
-                                              preferredDisplayMode),
+        return {rankRefreshRates(anchorGroup, RefreshRateOrder::Ascending, preferredDisplayMode),
                 kNoSignals};
     }
 
-    return {rankedRefreshRates, kNoSignals};
+    return {ranking, kNoSignals};
 }
 
 std::unordered_map<uid_t, std::vector<const RefreshRateConfigs::LayerRequirement*>>
@@ -783,11 +771,12 @@
     return mPrimaryRefreshRates.back()->second;
 }
 
-std::vector<RefreshRateRanking> RefreshRateConfigs::getRefreshRatesByPolicyLocked(
+auto RefreshRateConfigs::rankRefreshRates(
         std::optional<int> anchorGroupOpt, RefreshRateOrder refreshRateOrder,
-        std::optional<DisplayModeId> preferredDisplayModeOpt) const {
-    std::deque<RefreshRateRanking> rankings;
-    const auto makeRanking = [&](const DisplayModeIterator it) REQUIRES(mLock) {
+        std::optional<DisplayModeId> preferredDisplayModeOpt) const -> RefreshRateRanking {
+    std::deque<ScoredRefreshRate> ranking;
+
+    const auto rankRefreshRate = [&](DisplayModeIterator it) REQUIRES(mLock) {
         const auto& mode = it->second;
         if (anchorGroupOpt && mode->getGroup() != anchorGroupOpt) {
             return;
@@ -800,31 +789,32 @@
         }
         if (preferredDisplayModeOpt) {
             if (*preferredDisplayModeOpt == mode->getId()) {
-                rankings.push_front(RefreshRateRanking{mode, /*score*/ 1.0f});
+                constexpr float kScore = std::numeric_limits<float>::max();
+                ranking.push_front(ScoredRefreshRate{mode, kScore});
                 return;
             }
             constexpr float kNonPreferredModePenalty = 0.95f;
             score *= kNonPreferredModePenalty;
         }
-        rankings.push_back(RefreshRateRanking{mode, score});
+        ranking.push_back(ScoredRefreshRate{mode, score});
     };
 
     if (refreshRateOrder == RefreshRateOrder::Ascending) {
-        std::for_each(mPrimaryRefreshRates.begin(), mPrimaryRefreshRates.end(), makeRanking);
+        std::for_each(mPrimaryRefreshRates.begin(), mPrimaryRefreshRates.end(), rankRefreshRate);
     } else {
-        std::for_each(mPrimaryRefreshRates.rbegin(), mPrimaryRefreshRates.rend(), makeRanking);
+        std::for_each(mPrimaryRefreshRates.rbegin(), mPrimaryRefreshRates.rend(), rankRefreshRate);
     }
 
-    if (!rankings.empty() || !anchorGroupOpt) {
-        return {rankings.begin(), rankings.end()};
+    if (!ranking.empty() || !anchorGroupOpt) {
+        return {ranking.begin(), ranking.end()};
     }
 
     ALOGW("Can't find %s refresh rate by policy with the same mode group"
           " as the mode group %d",
           refreshRateOrder == RefreshRateOrder::Ascending ? "min" : "max", anchorGroupOpt.value());
 
-    return getRefreshRatesByPolicyLocked(/*anchorGroupOpt*/ std::nullopt, refreshRateOrder,
-                                         preferredDisplayModeOpt);
+    constexpr std::optional<int> kNoAnchorGroup = std::nullopt;
+    return rankRefreshRates(kNoAnchorGroup, refreshRateOrder, preferredDisplayModeOpt);
 }
 
 DisplayModePtr RefreshRateConfigs::getActiveModePtr() const {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 7219584..99f81aa 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -23,6 +23,7 @@
 #include <utility>
 #include <variant>
 
+#include <ftl/concat.h>
 #include <gui/DisplayEventReceiver.h>
 
 #include <scheduler/Fps.h>
@@ -46,15 +47,6 @@
     return static_cast<DisplayModeEvent>(static_cast<T>(lhs) | static_cast<T>(rhs));
 }
 
-struct RefreshRateRanking {
-    DisplayModePtr displayModePtr;
-    float score = 0.0f;
-
-    bool operator==(const RefreshRateRanking& ranking) const {
-        return displayModePtr == ranking.displayModePtr && score == ranking.score;
-    }
-};
-
 using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
 
 /**
@@ -208,12 +200,46 @@
             return touch == other.touch && idle == other.idle &&
                     powerOnImminent == other.powerOnImminent;
         }
+
+        auto toString() const {
+            return ftl::Concat("{touch=", touch, ", idle=", idle,
+                               ", powerOnImminent=", powerOnImminent, '}');
+        }
     };
 
-    // Returns the list in the descending order of refresh rates desired
-    // based on their overall score, and the GlobalSignals that were considered.
-    std::pair<std::vector<RefreshRateRanking>, GlobalSignals> getRankedRefreshRates(
-            const std::vector<LayerRequirement>&, GlobalSignals) const EXCLUDES(mLock);
+    struct ScoredRefreshRate {
+        DisplayModePtr modePtr;
+        float score = 0.0f;
+
+        bool operator==(const ScoredRefreshRate& other) const {
+            return modePtr == other.modePtr && score == other.score;
+        }
+
+        static bool scoresEqual(float lhs, float rhs) {
+            constexpr float kEpsilon = 0.0001f;
+            return std::abs(lhs - rhs) <= kEpsilon;
+        }
+
+        struct DescendingScore {
+            bool operator()(const ScoredRefreshRate& lhs, const ScoredRefreshRate& rhs) const {
+                return lhs.score > rhs.score && !scoresEqual(lhs.score, rhs.score);
+            }
+        };
+    };
+
+    using RefreshRateRanking = std::vector<ScoredRefreshRate>;
+
+    struct RankedRefreshRates {
+        RefreshRateRanking ranking; // Ordered by descending score.
+        GlobalSignals consideredSignals;
+
+        bool operator==(const RankedRefreshRates& other) const {
+            return ranking == other.ranking && consideredSignals == other.consideredSignals;
+        }
+    };
+
+    RankedRefreshRates getRankedRefreshRates(const std::vector<LayerRequirement>&,
+                                             GlobalSignals) const EXCLUDES(mLock);
 
     FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) {
         std::lock_guard lock(mLock);
@@ -354,8 +380,8 @@
     // See mActiveModeIt for thread safety.
     DisplayModeIterator getActiveModeItLocked() const REQUIRES(mLock);
 
-    std::pair<std::vector<RefreshRateRanking>, GlobalSignals> getRankedRefreshRatesLocked(
-            const std::vector<LayerRequirement>&, GlobalSignals) const REQUIRES(mLock);
+    RankedRefreshRates getRankedRefreshRatesLocked(const std::vector<LayerRequirement>&,
+                                                   GlobalSignals) const REQUIRES(mLock);
 
     // Returns number of display frames and remainder when dividing the layer refresh period by
     // display refresh period.
@@ -373,11 +399,10 @@
 
     enum class RefreshRateOrder { Ascending, Descending };
 
-    // Returns the rankings in RefreshRateOrder. May change at runtime.
     // Only uses the primary range, not the app request range.
-    std::vector<RefreshRateRanking> getRefreshRatesByPolicyLocked(
-            std::optional<int> anchorGroupOpt, RefreshRateOrder,
-            std::optional<DisplayModeId> preferredDisplayModeOpt) const REQUIRES(mLock);
+    RefreshRateRanking rankRefreshRates(std::optional<int> anchorGroupOpt, RefreshRateOrder,
+                                        std::optional<DisplayModeId> preferredDisplayModeOpt =
+                                                std::nullopt) const REQUIRES(mLock);
 
     const Policy* getCurrentPolicyLocked() const REQUIRES(mLock);
     bool isPolicyValidLocked(const Policy& policy) const REQUIRES(mLock);
@@ -436,7 +461,7 @@
 
     struct GetRankedRefreshRatesCache {
         std::pair<std::vector<LayerRequirement>, GlobalSignals> arguments;
-        std::pair<std::vector<RefreshRateRanking>, GlobalSignals> result;
+        RankedRefreshRates result;
     };
     mutable std::optional<GetRankedRefreshRatesCache> mGetRankedRefreshRatesCache GUARDED_BY(mLock);
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 30f2c27..be3ebb7 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -127,11 +127,19 @@
 }
 
 void Scheduler::registerDisplay(sp<const DisplayDevice> display) {
+    if (display->isPrimary()) {
+        mLeaderDisplayId = display->getPhysicalId();
+    }
+
     const bool ok = mDisplays.try_emplace(display->getPhysicalId(), std::move(display)).second;
     ALOGE_IF(!ok, "%s: Duplicate display", __func__);
 }
 
 void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) {
+    if (mLeaderDisplayId == displayId) {
+        mLeaderDisplayId.reset();
+    }
+
     mDisplays.erase(displayId);
 }
 
@@ -631,9 +639,8 @@
 
 template <typename S, typename T>
 auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals {
-    DisplayModePtr newMode;
+    std::vector<display::DisplayModeRequest> modeRequests;
     GlobalSignals consideredSignals;
-    std::vector<DisplayModeConfig> displayModeConfigs;
 
     bool refreshRateChanged = false;
     bool frameRateOverridesChanged;
@@ -646,42 +653,41 @@
         if (currentState == newState) return {};
         currentState = std::forward<T>(newState);
 
-        displayModeConfigs = getBestDisplayModeConfigs();
+        auto modeChoices = chooseDisplayModes();
 
-        // mPolicy holds the current mode, using the current mode we find out
-        // what display is currently being tracked through the policy and
-        // then find the DisplayModeConfig for that display. So that
-        // later we check if the policy mode has changed for the same display in policy.
-        // If mPolicy mode isn't available then we take the first display from the best display
-        // modes as the candidate for policy changes and frame rate overrides.
-        // TODO(b/240743786) Update the single display based assumptions and make mode changes
-        // and mPolicy per display.
-        const DisplayModeConfig& displayModeConfigForCurrentPolicy = mPolicy.mode
-                ? *std::find_if(displayModeConfigs.begin(), displayModeConfigs.end(),
-                                [&](const auto& displayModeConfig) REQUIRES(mPolicyLock) {
-                                    return displayModeConfig.displayModePtr
-                                                   ->getPhysicalDisplayId() ==
-                                            mPolicy.mode->getPhysicalDisplayId();
-                                })
-                : displayModeConfigs.front();
+        // TODO(b/240743786): The leader display's mode must change for any DisplayModeRequest to go
+        // through. Fix this by tracking per-display Scheduler::Policy and timers.
+        DisplayModePtr modePtr;
+        std::tie(modePtr, consideredSignals) =
+                modeChoices.get(*mLeaderDisplayId)
+                        .transform([](const DisplayModeChoice& choice) {
+                            return std::make_pair(choice.modePtr, choice.consideredSignals);
+                        })
+                        .value();
 
-        newMode = displayModeConfigForCurrentPolicy.displayModePtr;
-        consideredSignals = displayModeConfigForCurrentPolicy.signals;
-        frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, newMode->getFps());
+        modeRequests.reserve(modeChoices.size());
+        for (auto& [id, choice] : modeChoices) {
+            modeRequests.emplace_back(
+                    display::DisplayModeRequest{.modePtr =
+                                                        ftl::as_non_null(std::move(choice.modePtr)),
+                                                .emitEvent = !choice.consideredSignals.idle});
+        }
 
-        if (mPolicy.mode == newMode) {
+        frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, modePtr->getFps());
+
+        if (mPolicy.mode != modePtr) {
+            mPolicy.mode = modePtr;
+            refreshRateChanged = true;
+        } else {
             // We don't need to change the display mode, but we might need to send an event
             // about a mode change, since it was suppressed if previously considered idle.
             if (!consideredSignals.idle) {
                 dispatchCachedReportedMode();
             }
-        } else {
-            mPolicy.mode = newMode;
-            refreshRateChanged = true;
         }
     }
     if (refreshRateChanged) {
-        mSchedulerCallback.requestDisplayModes(std::move(displayModeConfigs));
+        mSchedulerCallback.requestDisplayModes(std::move(modeRequests));
     }
     if (frameRateOverridesChanged) {
         mSchedulerCallback.triggerOnFrameRateOverridesChanged();
@@ -689,11 +695,11 @@
     return consideredSignals;
 }
 
-std::vector<DisplayModeConfig> Scheduler::getBestDisplayModeConfigs() const {
+auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap {
     ATRACE_CALL();
 
-    using Rankings = std::pair<std::vector<RefreshRateRanking>, GlobalSignals>;
-    display::PhysicalDisplayVector<Rankings> perDisplayRankings;
+    using RankedRefreshRates = RefreshRateConfigs::RankedRefreshRates;
+    display::PhysicalDisplayVector<RankedRefreshRates> perDisplayRanking;
 
     // Tallies the score of a refresh rate across `displayCount` displays.
     struct RefreshRateTally {
@@ -710,11 +716,11 @@
     const auto globalSignals = makeGlobalSignals();
 
     for (const auto& [id, display] : mDisplays) {
-        auto [rankings, signals] =
+        auto rankedRefreshRates =
                 display->holdRefreshRateConfigs()
                         ->getRankedRefreshRates(mPolicy.contentRequirements, globalSignals);
 
-        for (const auto& [modePtr, score] : rankings) {
+        for (const auto& [modePtr, score] : rankedRefreshRates.ranking) {
             const auto [it, inserted] = refreshRateTallies.try_emplace(modePtr->getFps(), score);
 
             if (!inserted) {
@@ -724,7 +730,7 @@
             }
         }
 
-        perDisplayRankings.emplace_back(std::move(rankings), signals);
+        perDisplayRanking.push_back(std::move(rankedRefreshRates));
     }
 
     auto maxScoreIt = refreshRateTallies.cbegin();
@@ -750,26 +756,27 @@
             ? std::make_optional(maxScoreIt->first)
             : std::nullopt;
 
-    std::vector<DisplayModeConfig> displayModeConfigs;
-    displayModeConfigs.reserve(mDisplays.size());
+    DisplayModeChoiceMap modeChoices;
 
     using fps_approx_ops::operator==;
 
-    for (const auto& [rankings, signals] : perDisplayRankings) {
+    for (auto& [ranking, signals] : perDisplayRanking) {
         if (!chosenFps) {
-            displayModeConfigs.emplace_back(signals, rankings.front().displayModePtr);
+            auto& [modePtr, _] = ranking.front();
+            modeChoices.try_emplace(modePtr->getPhysicalDisplayId(),
+                                    DisplayModeChoice{std::move(modePtr), signals});
             continue;
         }
 
-        for (const auto& ranking : rankings) {
-            const auto& modePtr = ranking.displayModePtr;
+        for (auto& [modePtr, _] : ranking) {
             if (modePtr->getFps() == *chosenFps) {
-                displayModeConfigs.emplace_back(signals, modePtr);
+                modeChoices.try_emplace(modePtr->getPhysicalDisplayId(),
+                                        DisplayModeChoice{std::move(modePtr), signals});
                 break;
             }
         }
     }
-    return displayModeConfigs;
+    return modeChoices;
 }
 
 GlobalSignals Scheduler::makeGlobalSignals() const {
@@ -787,11 +794,11 @@
     // Make sure the stored mode is up to date.
     if (mPolicy.mode) {
         const auto configs = holdRefreshRateConfigs();
-        const auto rankings =
+        const auto ranking =
                 configs->getRankedRefreshRates(mPolicy.contentRequirements, makeGlobalSignals())
-                        .first;
+                        .ranking;
 
-        mPolicy.mode = rankings.front().displayModePtr;
+        mPolicy.mode = ranking.front().modePtr;
     }
     return mPolicy.mode;
 }
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 6633b05..33f6126 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -38,6 +38,7 @@
 #include <ui/DisplayId.h>
 
 #include "Display/DisplayMap.h"
+#include "Display/DisplayModeRequest.h"
 #include "DisplayDevice.h"
 #include "EventThread.h"
 #include "FrameRateOverrideMappings.h"
@@ -88,20 +89,9 @@
 
 using GlobalSignals = RefreshRateConfigs::GlobalSignals;
 
-// Config representing the DisplayMode and considered signals for the Display.
-struct DisplayModeConfig {
-    const GlobalSignals signals;
-    const DisplayModePtr displayModePtr;
-
-    DisplayModeConfig(GlobalSignals signals, DisplayModePtr displayModePtr)
-          : signals(signals), displayModePtr(std::move(displayModePtr)) {}
-};
-
 struct ISchedulerCallback {
-    using DisplayModeEvent = scheduler::DisplayModeEvent;
-
     virtual void setVsyncEnabled(bool) = 0;
-    virtual void requestDisplayModes(std::vector<DisplayModeConfig>) = 0;
+    virtual void requestDisplayModes(std::vector<display::DisplayModeRequest>) = 0;
     virtual void kernelTimerChanged(bool expired) = 0;
     virtual void triggerOnFrameRateOverridesChanged() = 0;
 
@@ -278,8 +268,26 @@
     template <typename S, typename T>
     GlobalSignals applyPolicy(S Policy::*, T&&) EXCLUDES(mPolicyLock);
 
-    // Returns the best display mode per display.
-    std::vector<DisplayModeConfig> getBestDisplayModeConfigs() const REQUIRES(mPolicyLock);
+    struct DisplayModeChoice {
+        DisplayModeChoice(DisplayModePtr modePtr, GlobalSignals consideredSignals)
+              : modePtr(std::move(modePtr)), consideredSignals(consideredSignals) {}
+
+        DisplayModePtr modePtr;
+        GlobalSignals consideredSignals;
+
+        bool operator==(const DisplayModeChoice& other) const {
+            return modePtr == other.modePtr && consideredSignals == other.consideredSignals;
+        }
+
+        // For tests.
+        friend std::ostream& operator<<(std::ostream& stream, const DisplayModeChoice& choice) {
+            return stream << '{' << to_string(*choice.modePtr) << " considering "
+                          << choice.consideredSignals.toString().c_str() << '}';
+        }
+    };
+
+    using DisplayModeChoiceMap = display::PhysicalDisplayMap<PhysicalDisplayId, DisplayModeChoice>;
+    DisplayModeChoiceMap chooseDisplayModes() const REQUIRES(mPolicyLock);
 
     GlobalSignals makeGlobalSignals() const REQUIRES(mPolicyLock);
 
@@ -329,6 +337,7 @@
     mutable std::mutex mPolicyLock;
 
     display::PhysicalDisplayMap<PhysicalDisplayId, sp<const DisplayDevice>> mDisplays;
+    std::optional<PhysicalDisplayId> mLeaderDisplayId;
 
     struct Policy {
         // Policy for choosing the display mode.
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index f0271c6..cfebec7 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1065,30 +1065,28 @@
     return NO_ERROR;
 }
 
-void SurfaceFlinger::setDesiredActiveMode(const ActiveModeInfo& info) {
+void SurfaceFlinger::setDesiredActiveMode(display::DisplayModeRequest&& request) {
     ATRACE_CALL();
 
-    if (!info.mode) {
-        ALOGW("requested display mode is null");
-        return;
-    }
-    auto display = getDisplayDeviceLocked(info.mode->getPhysicalDisplayId());
+    auto display = getDisplayDeviceLocked(request.modePtr->getPhysicalDisplayId());
     if (!display) {
         ALOGW("%s: display is no longer valid", __func__);
         return;
     }
 
-    if (display->setDesiredActiveMode(info)) {
+    const Fps refreshRate = request.modePtr->getFps();
+
+    if (display->setDesiredActiveMode(DisplayDevice::ActiveModeInfo(std::move(request)))) {
         scheduleComposite(FrameHint::kNone);
 
         // Start receiving vsync samples now, so that we can detect a period
         // switch.
-        mScheduler->resyncToHardwareVsync(true, info.mode->getFps());
+        mScheduler->resyncToHardwareVsync(true, refreshRate);
         // As we called to set period, we will call to onRefreshRateChangeCompleted once
         // VsyncController model is locked.
         modulateVsync(&VsyncModulator::onRefreshRateChangeInitiated);
 
-        updatePhaseConfiguration(info.mode->getFps());
+        updatePhaseConfiguration(refreshRate);
         mScheduler->setModeChangePending(true);
     }
 }
@@ -1180,7 +1178,7 @@
     mRefreshRateStats->setRefreshRate(refreshRate);
     updatePhaseConfiguration(refreshRate);
 
-    if (upcomingModeInfo.event != DisplayModeEvent::None) {
+    if (upcomingModeInfo.event != scheduler::DisplayModeEvent::None) {
         mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, upcomingModeInfo.mode);
     }
 }
@@ -3335,34 +3333,33 @@
     mCompositionEngine->updateCursorAsync(refreshArgs);
 }
 
-void SurfaceFlinger::requestDisplayModes(
-        std::vector<scheduler::DisplayModeConfig> displayModeConfigs) {
+void SurfaceFlinger::requestDisplayModes(std::vector<display::DisplayModeRequest> modeRequests) {
     if (mBootStage != BootStage::FINISHED) {
         ALOGV("Currently in the boot stage, skipping display mode changes");
         return;
     }
 
     ATRACE_CALL();
+
     // If this is called from the main thread mStateLock must be locked before
     // Currently the only way to call this function from the main thread is from
     // Scheduler::chooseRefreshRateForContent
 
     ConditionalLock lock(mStateLock, std::this_thread::get_id() != mMainThreadId);
 
-    std::for_each(displayModeConfigs.begin(), displayModeConfigs.end(),
-                  [&](const auto& config) REQUIRES(mStateLock) {
-                      const auto& displayModePtr = config.displayModePtr;
-                      if (const auto display =
-                                  getDisplayDeviceLocked(displayModePtr->getPhysicalDisplayId());
-                          display->refreshRateConfigs().isModeAllowed(displayModePtr->getId())) {
-                          const auto event = config.signals.idle ? DisplayModeEvent::None
-                                                                 : DisplayModeEvent::Changed;
-                          setDesiredActiveMode({displayModePtr, event});
-                      } else {
-                          ALOGV("Skipping disallowed mode %d for display %" PRId64,
-                                displayModePtr->getId().value(), display->getPhysicalId().value);
-                      }
-                  });
+    for (auto& request : modeRequests) {
+        const auto& modePtr = request.modePtr;
+        const auto display = getDisplayDeviceLocked(modePtr->getPhysicalDisplayId());
+
+        if (!display) continue;
+
+        if (display->refreshRateConfigs().isModeAllowed(modePtr->getId())) {
+            setDesiredActiveMode(std::move(request));
+        } else {
+            ALOGV("%s: Mode %d is disallowed for display %s", __func__, modePtr->getId().value(),
+                  to_string(display->getId()).c_str());
+        }
+    }
 }
 
 void SurfaceFlinger::triggerOnFrameRateOverridesChanged() {
@@ -4127,7 +4124,7 @@
         return 0;
     }
 
-    ui::LayerStack oldLayerStack = layer->getLayerStack();
+    ui::LayerStack oldLayerStack = layer->getLayerStack(LayerVector::StateSet::Current);
 
     // Only set by BLAST adapter layers
     if (what & layer_state_t::eProducerDisconnect) {
@@ -4396,7 +4393,8 @@
     // setTransactionCompletedListener
 
     // if the layer has been parented on to a new display, update its transform hint.
-    if (((flags & eTransformHintUpdateNeeded) == 0) && oldLayerStack != layer->getLayerStack()) {
+    if (((flags & eTransformHintUpdateNeeded) == 0) &&
+        oldLayerStack != layer->getLayerStack(LayerVector::StateSet::Current)) {
         flags |= eTransformHintUpdateNeeded;
     }
 
@@ -4417,13 +4415,13 @@
 
     sp<Layer> mirrorLayer;
     sp<Layer> mirrorFrom;
+    LayerCreationArgs mirrorArgs(args);
     {
         Mutex::Autolock _l(mStateLock);
         mirrorFrom = LayerHandle::getLayer(mirrorFromHandle);
         if (!mirrorFrom) {
             return NAME_NOT_FOUND;
         }
-        LayerCreationArgs mirrorArgs(args);
         mirrorArgs.flags |= ISurfaceComposerClient::eNoColorFill;
         mirrorArgs.mirrorLayerHandle = mirrorFromHandle;
         mirrorArgs.addToRoot = false;
@@ -4442,8 +4440,8 @@
                                                 mirrorLayer->sequence, args.name,
                                                 mirrorFrom->sequence);
     }
-    return addClientLayer(args, outResult.handle, mirrorLayer /* layer */, nullptr /* parent */,
-                          nullptr /* outTransformHint */);
+    return addClientLayer(mirrorArgs, outResult.handle, mirrorLayer /* layer */,
+                          nullptr /* parent */, nullptr /* outTransformHint */);
 }
 
 status_t SurfaceFlinger::mirrorDisplay(DisplayId displayId, const LayerCreationArgs& args,
@@ -4474,7 +4472,7 @@
         result = createEffectLayer(mirrorArgs, &outResult.handle, &rootMirrorLayer);
         outResult.layerId = rootMirrorLayer->sequence;
         outResult.layerName = String16(rootMirrorLayer->getDebugName());
-        result |= addClientLayer(args, outResult.handle, rootMirrorLayer /* layer */,
+        result |= addClientLayer(mirrorArgs, outResult.handle, rootMirrorLayer /* layer */,
                                  nullptr /* parent */, nullptr /* outTransformHint */);
     }
 
@@ -6583,18 +6581,19 @@
     }
 }
 
-std::optional<DisplayModePtr> SurfaceFlinger::getPreferredDisplayMode(
+std::optional<ftl::NonNull<DisplayModePtr>> SurfaceFlinger::getPreferredDisplayMode(
         PhysicalDisplayId displayId, DisplayModeId defaultModeId) const {
     if (const auto schedulerMode = mScheduler->getPreferredDisplayMode();
         schedulerMode && schedulerMode->getPhysicalDisplayId() == displayId) {
-        return schedulerMode;
+        return ftl::as_non_null(schedulerMode);
     }
 
     return mPhysicalDisplays.get(displayId)
             .transform(&PhysicalDisplay::snapshotRef)
             .and_then([&](const display::DisplaySnapshot& snapshot) {
                 return snapshot.displayModes().get(defaultModeId);
-            });
+            })
+            .transform(&ftl::as_non_null<const DisplayModePtr&>);
 }
 
 status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal(
@@ -6650,7 +6649,7 @@
         return INVALID_OPERATION;
     }
 
-    setDesiredActiveMode({std::move(preferredMode), DisplayModeEvent::Changed});
+    setDesiredActiveMode({std::move(preferredMode), .emitEvent = true});
     return NO_ERROR;
 }
 
@@ -6888,7 +6887,7 @@
         parent->addChild(layer);
     }
 
-    ui::LayerStack layerStack = layer->getLayerStack();
+    ui::LayerStack layerStack = layer->getLayerStack(LayerVector::StateSet::Current);
     sp<const DisplayDevice> hintDisplay;
     // Find the display that includes the layer.
     for (const auto& [token, display] : mDisplays) {
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 9a2f186..85c194b 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -29,6 +29,7 @@
 #include <cutils/atomic.h>
 #include <cutils/compiler.h>
 #include <ftl/future.h>
+#include <ftl/non_null.h>
 #include <gui/BufferQueue.h>
 #include <gui/CompositorTiming.h>
 #include <gui/FrameTimestamps.h>
@@ -429,10 +430,6 @@
                 mCounterByLayerHandle GUARDED_BY(mLock);
     };
 
-    using ActiveModeInfo = DisplayDevice::ActiveModeInfo;
-    using KernelIdleTimerController =
-            ::android::scheduler::RefreshRateConfigs::KernelIdleTimerController;
-
     enum class BootStage {
         BOOTLOADER,
         BOOTANIMATION,
@@ -627,16 +624,17 @@
     // ISchedulerCallback overrides:
 
     // Toggles hardware VSYNC by calling into HWC.
+    // TODO(b/241286146): Rename for self-explanatory API.
     void setVsyncEnabled(bool) override;
-    // Sets the desired display mode per display if allowed by policy .
-    void requestDisplayModes(std::vector<scheduler::DisplayModeConfig>) override;
-    // Called when kernel idle timer has expired. Used to update the refresh rate overlay.
+    void requestDisplayModes(std::vector<display::DisplayModeRequest>) override;
     void kernelTimerChanged(bool expired) override;
-    // Called when the frame rate override list changed to trigger an event.
     void triggerOnFrameRateOverridesChanged() override;
 
     // Toggles the kernel idle timer on or off depending the policy decisions around refresh rates.
     void toggleKernelIdleTimer() REQUIRES(mStateLock);
+
+    using KernelIdleTimerController = scheduler::RefreshRateConfigs::KernelIdleTimerController;
+
     // Get the controller and timeout that will help decide how the kernel idle timer will be
     // configured and what value to use as the timeout.
     std::pair<std::optional<KernelIdleTimerController>, std::chrono::milliseconds>
@@ -651,8 +649,8 @@
     // Show spinner with refresh rate overlay
     bool mRefreshRateOverlaySpinner = false;
 
-    // Sets the desired active mode bit. It obtains the lock, and sets mDesiredActiveMode.
-    void setDesiredActiveMode(const ActiveModeInfo& info) REQUIRES(mStateLock);
+    void setDesiredActiveMode(display::DisplayModeRequest&&) REQUIRES(mStateLock);
+
     status_t setActiveModeFromBackdoor(const sp<display::DisplayToken>&, DisplayModeId);
     // Sets the active mode and a new refresh rate in SF.
     void updateInternalStateWithChangedMode() REQUIRES(mStateLock, kMainThreadContext);
@@ -671,9 +669,8 @@
 
     // Returns the preferred mode for PhysicalDisplayId if the Scheduler has selected one for that
     // display. Falls back to the display's defaultModeId otherwise.
-    std::optional<DisplayModePtr> getPreferredDisplayMode(PhysicalDisplayId,
-                                                          DisplayModeId defaultModeId) const
-            REQUIRES(mStateLock);
+    std::optional<ftl::NonNull<DisplayModePtr>> getPreferredDisplayMode(
+            PhysicalDisplayId, DisplayModeId defaultModeId) const REQUIRES(mStateLock);
 
     status_t setDesiredDisplayModeSpecsInternal(const sp<DisplayDevice>&,
                                                 const scheduler::RefreshRateConfigs::PolicyVariant&)
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index a350020..e555867 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -781,7 +781,7 @@
 
 private:
     void setVsyncEnabled(bool) override {}
-    void requestDisplayModes(std::vector<scheduler::DisplayModeConfig>) override {}
+    void requestDisplayModes(std::vector<display::DisplayModeRequest>) override {}
     void kernelTimerChanged(bool) override {}
     void triggerOnFrameRateOverridesChanged() override {}
 
diff --git a/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h b/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h
index 6ee4b9b..6e4bf2b 100644
--- a/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h
+++ b/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h
@@ -29,7 +29,7 @@
 using android::Hwc2::mock::PowerAdvisor;
 
 struct FakeDisplayInjectorArgs {
-    uint8_t port = 255u;
+    PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(255u);
     HWDisplayId hwcDisplayId = 0;
     bool isPrimary = true;
 };
@@ -67,7 +67,7 @@
         auto compositionDisplay = compositionengine::impl::
                 createDisplay(mFlinger.getCompositionEngine(),
                               compositionengine::DisplayCreationArgsBuilder()
-                                      .setId(PhysicalDisplayId::fromPort(args.port))
+                                      .setId(args.displayId)
                                       .setPixels(kResolution)
                                       .setPowerAdvisor(&mPowerAdvisor)
                                       .build());
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index 620825f..924c5be 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -17,6 +17,9 @@
 #undef LOG_TAG
 #define LOG_TAG "SchedulerUnittests"
 
+#include <algorithm>
+#include <array>
+
 #include <ftl/enum.h>
 #include <ftl/fake_guard.h>
 #include <gmock/gmock.h>
@@ -34,15 +37,17 @@
 
 namespace hal = android::hardware::graphics::composer::hal;
 
-using SetPolicyResult = RefreshRateConfigs::SetPolicyResult;
-using LayerVoteType = RefreshRateConfigs::LayerVoteType;
 using LayerRequirement = RefreshRateConfigs::LayerRequirement;
+using LayerVoteType = RefreshRateConfigs::LayerVoteType;
+using SetPolicyResult = RefreshRateConfigs::SetPolicyResult;
 
 using mock::createDisplayMode;
 
 struct TestableRefreshRateConfigs : RefreshRateConfigs {
-    using RefreshRateConfigs::RefreshRateConfigs;
     using RefreshRateConfigs::RefreshRateOrder;
+    using RefreshRateConfigs::RefreshRateRanking;
+
+    using RefreshRateConfigs::RefreshRateConfigs;
 
     void setActiveModeId(DisplayModeId modeId) {
         ftl::FakeGuard guard(kMainThreadContext);
@@ -74,12 +79,10 @@
         return getMaxRefreshRateByPolicyLocked(getActiveModeItLocked()->second->getGroup());
     }
 
-    std::vector<RefreshRateRanking> getRefreshRatesByPolicy(
-            std::optional<int> anchorGroupOpt, RefreshRateOrder refreshRateOrder) const {
+    RefreshRateRanking rankRefreshRates(std::optional<int> anchorGroupOpt,
+                                        RefreshRateOrder refreshRateOrder) const {
         std::lock_guard lock(mLock);
-        return RefreshRateConfigs::
-                getRefreshRatesByPolicyLocked(anchorGroupOpt, refreshRateOrder,
-                                              /*preferredDisplayModeOpt*/ std::nullopt);
+        return RefreshRateConfigs::rankRefreshRates(anchorGroupOpt, refreshRateOrder);
     }
 
     const std::vector<Fps>& knownFrameRates() const { return mKnownFrameRates; }
@@ -87,14 +90,25 @@
     using RefreshRateConfigs::GetRankedRefreshRatesCache;
     auto& mutableGetRankedRefreshRatesCache() { return mGetRankedRefreshRatesCache; }
 
-    auto getRankedRefreshRatesAndSignals(const std::vector<LayerRequirement>& layers,
-                                         GlobalSignals signals) const {
-        return RefreshRateConfigs::getRankedRefreshRates(layers, signals);
+    auto getRankedRefreshRates(const std::vector<LayerRequirement>& layers,
+                               GlobalSignals signals) const {
+        const auto result = RefreshRateConfigs::getRankedRefreshRates(layers, signals);
+
+        EXPECT_TRUE(std::is_sorted(result.ranking.begin(), result.ranking.end(),
+                                   ScoredRefreshRate::DescendingScore{}));
+
+        return result;
+    }
+
+    auto getRankedRefreshRatesAsPair(const std::vector<LayerRequirement>& layers,
+                                     GlobalSignals signals) const {
+        const auto [ranking, consideredSignals] = getRankedRefreshRates(layers, signals);
+        return std::make_pair(ranking, consideredSignals);
     }
 
     DisplayModePtr getBestRefreshRate(const std::vector<LayerRequirement>& layers = {},
                                       GlobalSignals signals = {}) const {
-        return getRankedRefreshRatesAndSignals(layers, signals).first.front().displayModePtr;
+        return getRankedRefreshRates(layers, signals).ranking.front().modePtr;
     }
 
     SetPolicyResult setPolicy(const PolicyVariant& policy) {
@@ -109,6 +123,8 @@
 
 class RefreshRateConfigsTest : public testing::Test {
 protected:
+    using RefreshRateOrder = TestableRefreshRateConfigs::RefreshRateOrder;
+
     RefreshRateConfigsTest();
     ~RefreshRateConfigsTest();
 
@@ -1050,20 +1066,17 @@
     // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
     // different group.
     TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60);
-    const std::vector<RefreshRateRanking>& expectedRefreshRates = {RefreshRateRanking{kMode90},
-                                                                   RefreshRateRanking{kMode60},
-                                                                   RefreshRateRanking{kMode30}};
 
-    const std::vector<RefreshRateRanking>& refreshRates =
-            configs.getRefreshRatesByPolicy(configs.getActiveMode().getGroup(),
-                                            TestableRefreshRateConfigs::RefreshRateOrder::
-                                                    Descending);
+    const auto refreshRates = configs.rankRefreshRates(configs.getActiveMode().getGroup(),
+                                                       RefreshRateOrder::Descending);
 
+    const std::array expectedRefreshRates = {kMode90, kMode60, kMode30};
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
     }
 }
 
@@ -1071,20 +1084,17 @@
     // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
     // different group.
     TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60);
-    const std::vector<RefreshRateRanking>& expectedRefreshRates = {RefreshRateRanking{kMode30},
-                                                                   RefreshRateRanking{kMode60},
-                                                                   RefreshRateRanking{kMode90}};
 
-    const std::vector<RefreshRateRanking>& refreshRates =
-            configs.getRefreshRatesByPolicy(configs.getActiveMode().getGroup(),
-                                            TestableRefreshRateConfigs::RefreshRateOrder::
-                                                    Ascending);
+    const auto refreshRates = configs.rankRefreshRates(configs.getActiveMode().getGroup(),
+                                                       RefreshRateOrder::Ascending);
 
+    const std::array expectedRefreshRates = {kMode30, kMode60, kMode90};
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
     }
 }
 
@@ -1092,23 +1102,20 @@
     // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
     // different group.
     TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId72);
-    const std::vector<RefreshRateRanking>& expectedRefreshRates = {RefreshRateRanking{kMode30},
-                                                                   RefreshRateRanking{kMode60},
-                                                                   RefreshRateRanking{kMode90}};
 
     EXPECT_EQ(SetPolicyResult::Changed,
               configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}}));
 
-    const std::vector<RefreshRateRanking>& refreshRates =
-            configs.getRefreshRatesByPolicy(/*anchorGroupOpt*/ std::nullopt,
-                                            TestableRefreshRateConfigs::RefreshRateOrder::
-                                                    Ascending);
+    const auto refreshRates =
+            configs.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt, RefreshRateOrder::Ascending);
 
+    const std::array expectedRefreshRates = {kMode30, kMode60, kMode90};
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
     }
 }
 
@@ -1116,47 +1123,48 @@
     // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
     // different group.
     TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId72);
-    const std::vector<RefreshRateRanking>& expectedRefreshRates = {RefreshRateRanking{kMode90},
-                                                                   RefreshRateRanking{kMode60},
-                                                                   RefreshRateRanking{kMode30}};
 
     EXPECT_EQ(SetPolicyResult::Changed,
               configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}}));
 
-    const std::vector<RefreshRateRanking>& refreshRates =
-            configs.getRefreshRatesByPolicy(/*anchorGroupOpt*/ std::nullopt,
-                                            TestableRefreshRateConfigs::RefreshRateOrder::
-                                                    Descending);
+    const auto refreshRates =
+            configs.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt, RefreshRateOrder::Descending);
 
+    const std::array expectedRefreshRates = {kMode90, kMode60, kMode30};
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
     }
 }
 
 TEST_F(RefreshRateConfigsTest, powerOnImminentConsidered) {
-    RefreshRateConfigs configs(kModes_60_90, kModeId60);
-    std::vector<RefreshRateRanking> expectedRefreshRates = {RefreshRateRanking{kMode90},
-                                                            RefreshRateRanking{kMode60}};
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
     auto [refreshRates, signals] = configs.getRankedRefreshRates({}, {});
     EXPECT_FALSE(signals.powerOnImminent);
+
+    std::array expectedRefreshRates = {kMode90, kMode60};
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
     }
 
-    std::tie(refreshRates, signals) = configs.getRankedRefreshRates({}, {.powerOnImminent = true});
+    std::tie(refreshRates, signals) =
+            configs.getRankedRefreshRatesAsPair({}, {.powerOnImminent = true});
     EXPECT_TRUE(signals.powerOnImminent);
+
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
     }
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
@@ -1166,34 +1174,38 @@
     lr1.name = "60Hz ExplicitExactOrMultiple";
 
     std::tie(refreshRates, signals) =
-            configs.getRankedRefreshRates(layers, {.powerOnImminent = true});
+            configs.getRankedRefreshRatesAsPair(layers, {.powerOnImminent = true});
     EXPECT_TRUE(signals.powerOnImminent);
+
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
     }
 
-    expectedRefreshRates = {RefreshRateRanking{kMode60}, RefreshRateRanking{kMode90}};
     std::tie(refreshRates, signals) =
-            configs.getRankedRefreshRates(layers, {.powerOnImminent = false});
+            configs.getRankedRefreshRatesAsPair(layers, {.powerOnImminent = false});
     EXPECT_FALSE(signals.powerOnImminent);
+
+    expectedRefreshRates = {kMode60, kMode90};
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
+                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
+                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
     }
 }
 
 TEST_F(RefreshRateConfigsTest, touchConsidered) {
-    RefreshRateConfigs configs(kModes_60_90, kModeId60);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
     auto [_, signals] = configs.getRankedRefreshRates({}, {});
     EXPECT_FALSE(signals.touch);
 
-    std::tie(std::ignore, signals) = configs.getRankedRefreshRates({}, {.touch = true});
+    std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair({}, {.touch = true});
     EXPECT_TRUE(signals.touch);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
@@ -1206,7 +1218,7 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 60_Hz;
     lr2.name = "60Hz Heuristic";
-    std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true});
+    std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair(layers, {.touch = true});
     EXPECT_TRUE(signals.touch);
 
     lr1.vote = LayerVoteType::ExplicitDefault;
@@ -1215,7 +1227,7 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 60_Hz;
     lr2.name = "60Hz Heuristic";
-    std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true});
+    std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair(layers, {.touch = true});
     EXPECT_FALSE(signals.touch);
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -1224,7 +1236,7 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 60_Hz;
     lr2.name = "60Hz Heuristic";
-    std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true});
+    std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair(layers, {.touch = true});
     EXPECT_TRUE(signals.touch);
 
     lr1.vote = LayerVoteType::ExplicitDefault;
@@ -1233,7 +1245,7 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 60_Hz;
     lr2.name = "60Hz Heuristic";
-    std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true});
+    std::tie(std::ignore, signals) = configs.getRankedRefreshRatesAsPair(layers, {.touch = true});
     EXPECT_FALSE(signals.touch);
 }
 
@@ -1352,7 +1364,7 @@
     const auto [mode, signals] =
             configs.getRankedRefreshRates(layers, {.touch = true, .idle = true});
 
-    EXPECT_EQ(mode.begin()->displayModePtr, kMode60);
+    EXPECT_EQ(mode.begin()->modePtr, kMode60);
     EXPECT_FALSE(signals.touch);
 }
 
@@ -1407,18 +1419,15 @@
     lr5.name = "30Hz";
     lr5.focused = true;
 
-    std::vector<RefreshRateRanking> expectedRankings = {
-            RefreshRateRanking{kMode120}, RefreshRateRanking{kMode90}, RefreshRateRanking{kMode72},
-            RefreshRateRanking{kMode60},  RefreshRateRanking{kMode30},
-    };
+    std::array expectedRanking = {kMode120, kMode90, kMode72, kMode60, kMode30};
+    auto actualRanking = configs.getRankedRefreshRates(layers, {}).ranking;
 
-    std::vector<RefreshRateRanking> actualOrder =
-            configs.getRankedRefreshRatesAndSignals(layers, {}).first;
-    ASSERT_EQ(expectedRankings.size(), actualOrder.size());
-    for (size_t i = 0; i < expectedRankings.size(); ++i) {
-        EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr)
-                << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue();
+    ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+
+    for (size_t i = 0; i < expectedRanking.size(); ++i) {
+        EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr)
+                << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps "
+                << actualRanking[i].modePtr->getFps().getIntValue();
     }
 
     lr1.vote = LayerVoteType::Max;
@@ -1436,18 +1445,15 @@
     lr5.desiredRefreshRate = 120_Hz;
     lr5.name = "120Hz";
 
-    expectedRankings = {
-            RefreshRateRanking{kMode120}, RefreshRateRanking{kMode90}, RefreshRateRanking{kMode72},
-            RefreshRateRanking{kMode60},  RefreshRateRanking{kMode30},
-    };
+    expectedRanking = {kMode120, kMode90, kMode72, kMode60, kMode30};
+    actualRanking = configs.getRankedRefreshRates(layers, {}).ranking;
 
-    actualOrder = configs.getRankedRefreshRatesAndSignals(layers, {}).first;
+    ASSERT_EQ(expectedRanking.size(), actualRanking.size());
 
-    ASSERT_EQ(expectedRankings.size(), actualOrder.size());
-    for (size_t i = 0; i < expectedRankings.size(); ++i) {
-        EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr)
-                << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue();
+    for (size_t i = 0; i < expectedRanking.size(); ++i) {
+        EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr)
+                << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps "
+                << actualRanking[i].modePtr->getFps().getIntValue();
     }
 
     lr1.vote = LayerVoteType::Heuristic;
@@ -1463,17 +1469,15 @@
     lr5.desiredRefreshRate = 72_Hz;
     lr5.name = "72Hz";
 
-    expectedRankings = {
-            RefreshRateRanking{kMode30},  RefreshRateRanking{kMode60}, RefreshRateRanking{kMode90},
-            RefreshRateRanking{kMode120}, RefreshRateRanking{kMode72},
-    };
+    expectedRanking = {kMode30, kMode60, kMode90, kMode120, kMode72};
+    actualRanking = configs.getRankedRefreshRates(layers, {}).ranking;
 
-    actualOrder = configs.getRankedRefreshRatesAndSignals(layers, {}).first;
-    ASSERT_EQ(expectedRankings.size(), actualOrder.size());
-    for (size_t i = 0; i < expectedRankings.size(); ++i) {
-        EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr)
-                << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue();
+    ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+
+    for (size_t i = 0; i < expectedRanking.size(); ++i) {
+        EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr)
+                << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps "
+                << actualRanking[i].modePtr->getFps().getIntValue();
     }
 
     lr1.desiredRefreshRate = 120_Hz;
@@ -1492,17 +1496,15 @@
     lr5.desiredRefreshRate = 120_Hz;
     lr5.name = "120Hz-2";
 
-    expectedRankings = {
-            RefreshRateRanking{kMode90}, RefreshRateRanking{kMode60}, RefreshRateRanking{kMode120},
-            RefreshRateRanking{kMode72}, RefreshRateRanking{kMode30},
-    };
+    expectedRanking = {kMode90, kMode60, kMode120, kMode72, kMode30};
+    actualRanking = configs.getRankedRefreshRates(layers, {}).ranking;
 
-    actualOrder = configs.getRankedRefreshRatesAndSignals(layers, {}).first;
-    ASSERT_EQ(expectedRankings.size(), actualOrder.size());
-    for (size_t i = 0; i < expectedRankings.size(); ++i) {
-        EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr)
-                << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue();
+    ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+
+    for (size_t i = 0; i < expectedRanking.size(); ++i) {
+        EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr)
+                << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps "
+                << actualRanking[i].modePtr->getFps().getIntValue();
     }
 }
 
@@ -1513,8 +1515,8 @@
     EXPECT_EQ(SetPolicyResult::Changed,
               configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}}));
 
-    const auto [mode, signals] = configs.getRankedRefreshRatesAndSignals({}, {});
-    EXPECT_EQ(mode.front().displayModePtr, kMode90);
+    const auto [ranking, signals] = configs.getRankedRefreshRates({}, {});
+    EXPECT_EQ(ranking.front().modePtr, kMode90);
     EXPECT_FALSE(signals.touch);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
@@ -1892,13 +1894,12 @@
         layers[0].vote = voteType;
         layers[0].desiredRefreshRate = 90_Hz;
 
-        const auto [refreshRate, signals] =
-                configs.getRankedRefreshRatesAndSignals(layers,
-                                                        {.touch = touchActive, .idle = true});
+        const auto [ranking, signals] =
+                configs.getRankedRefreshRates(layers, {.touch = touchActive, .idle = true});
 
         // Refresh rate will be chosen by either touch state or idle state.
         EXPECT_EQ(!touchActive, signals.idle);
-        return refreshRate.front().displayModePtr->getId();
+        return ranking.front().modePtr->getId();
     };
 
     EXPECT_EQ(SetPolicyResult::Changed,
@@ -2059,12 +2060,13 @@
     const auto args = std::make_pair(std::vector<LayerRequirement>{},
                                      GlobalSignals{.touch = true, .idle = true});
 
-    const auto result = std::make_pair(std::vector<RefreshRateRanking>{RefreshRateRanking{kMode90}},
-                                       GlobalSignals{.touch = true});
+    const RefreshRateConfigs::RankedRefreshRates result = {{RefreshRateConfigs::ScoredRefreshRate{
+                                                                   kMode90}},
+                                                           {.touch = true}};
 
     configs.mutableGetRankedRefreshRatesCache() = {args, result};
 
-    EXPECT_EQ(result, configs.getRankedRefreshRatesAndSignals(args.first, args.second));
+    EXPECT_EQ(result, configs.getRankedRefreshRates(args.first, args.second));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_WritesCache) {
@@ -2075,7 +2077,7 @@
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
     RefreshRateConfigs::GlobalSignals globalSignals{.touch = true, .idle = true};
 
-    const auto result = configs.getRankedRefreshRatesAndSignals(layers, globalSignals);
+    const auto result = configs.getRankedRefreshRates(layers, globalSignals);
 
     const auto& cache = configs.mutableGetRankedRefreshRatesCache();
     ASSERT_TRUE(cache);
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 392398d..147433b 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -43,8 +43,6 @@
 using MockLayer = android::mock::MockLayer;
 using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
 
-constexpr PhysicalDisplayId PHYSICAL_DISPLAY_ID = PhysicalDisplayId::fromPort(255u);
-
 class SchedulerTest : public testing::Test {
 protected:
     class MockEventThreadConnection : public android::EventThreadConnection {
@@ -61,14 +59,28 @@
 
     SchedulerTest();
 
-    static inline const DisplayModePtr kMode60_1 = createDisplayMode(DisplayModeId(0), 60_Hz);
-    static inline const DisplayModePtr kMode120_1 = createDisplayMode(DisplayModeId(1), 120_Hz);
-    static inline const DisplayModePtr kMode60_2 = createDisplayMode(DisplayModeId(2), 60_Hz);
-    static inline const DisplayModePtr kMode120_2 = createDisplayMode(DisplayModeId(3), 120_Hz);
-    static inline const DisplayModePtr kMode60_3 = createDisplayMode(DisplayModeId(4), 60_Hz);
+    static constexpr PhysicalDisplayId kDisplayId1 = PhysicalDisplayId::fromPort(255u);
+    static inline const DisplayModePtr kDisplay1Mode60 =
+            createDisplayMode(kDisplayId1, DisplayModeId(0), 60_Hz);
+    static inline const DisplayModePtr kDisplay1Mode120 =
+            createDisplayMode(kDisplayId1, DisplayModeId(1), 120_Hz);
+    static inline const DisplayModes kDisplay1Modes = makeModes(kDisplay1Mode60, kDisplay1Mode120);
+
+    static constexpr PhysicalDisplayId kDisplayId2 = PhysicalDisplayId::fromPort(254u);
+    static inline const DisplayModePtr kDisplay2Mode60 =
+            createDisplayMode(kDisplayId2, DisplayModeId(0), 60_Hz);
+    static inline const DisplayModePtr kDisplay2Mode120 =
+            createDisplayMode(kDisplayId2, DisplayModeId(1), 120_Hz);
+    static inline const DisplayModes kDisplay2Modes = makeModes(kDisplay2Mode60, kDisplay2Mode120);
+
+    static constexpr PhysicalDisplayId kDisplayId3 = PhysicalDisplayId::fromPort(253u);
+    static inline const DisplayModePtr kDisplay3Mode60 =
+            createDisplayMode(kDisplayId3, DisplayModeId(0), 60_Hz);
+    static inline const DisplayModes kDisplay3Modes = makeModes(kDisplay3Mode60);
 
     std::shared_ptr<RefreshRateConfigs> mConfigs =
-            std::make_shared<RefreshRateConfigs>(makeModes(kMode60_1), kMode60_1->getId());
+            std::make_shared<RefreshRateConfigs>(makeModes(kDisplay1Mode60),
+                                                 kDisplay1Mode60->getId());
 
     mock::SchedulerCallback mSchedulerCallback;
     TestableScheduler* mScheduler = new TestableScheduler{mConfigs, mSchedulerCallback};
@@ -114,7 +126,7 @@
 
     // The EXPECT_CALLS make sure we don't call the functions on the subsequent event threads.
     EXPECT_CALL(*mEventThread, onHotplugReceived(_, _)).Times(0);
-    mScheduler->onHotplugReceived(handle, PHYSICAL_DISPLAY_ID, false);
+    mScheduler->onHotplugReceived(handle, kDisplayId1, false);
 
     EXPECT_CALL(*mEventThread, onScreenAcquired()).Times(0);
     mScheduler->onScreenAcquired(handle);
@@ -138,8 +150,8 @@
     ASSERT_EQ(mEventThreadConnection, connection);
     EXPECT_TRUE(mScheduler->getEventConnection(mConnectionHandle));
 
-    EXPECT_CALL(*mEventThread, onHotplugReceived(PHYSICAL_DISPLAY_ID, false)).Times(1);
-    mScheduler->onHotplugReceived(mConnectionHandle, PHYSICAL_DISPLAY_ID, false);
+    EXPECT_CALL(*mEventThread, onHotplugReceived(kDisplayId1, false)).Times(1);
+    mScheduler->onHotplugReceived(mConnectionHandle, kDisplayId1, false);
 
     EXPECT_CALL(*mEventThread, onScreenAcquired()).Times(1);
     mScheduler->onScreenAcquired(mConnectionHandle);
@@ -185,8 +197,7 @@
     ASSERT_EQ(1u, mScheduler->layerHistorySize());
 
     mScheduler->setRefreshRateConfigs(
-            std::make_shared<RefreshRateConfigs>(makeModes(kMode60_1, kMode120_1),
-                                                 kMode60_1->getId()));
+            std::make_shared<RefreshRateConfigs>(kDisplay1Modes, kDisplay1Mode60->getId()));
 
     ASSERT_EQ(0u, mScheduler->getNumActiveLayers());
     mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer);
@@ -203,7 +214,7 @@
 TEST_F(SchedulerTest, onNonPrimaryDisplayModeChanged_invalidParameters) {
     const auto mode = DisplayMode::Builder(hal::HWConfigId(0))
                               .setId(DisplayModeId(111))
-                              .setPhysicalDisplayId(PHYSICAL_DISPLAY_ID)
+                              .setPhysicalDisplayId(kDisplayId1)
                               .setVsyncPeriod(111111)
                               .build();
 
@@ -225,14 +236,15 @@
 }
 
 MATCHER(Is120Hz, "") {
-    return isApproxEqual(arg.front().displayModePtr->getFps(), 120_Hz);
+    return isApproxEqual(arg.front().modePtr->getFps(), 120_Hz);
 }
 
 TEST_F(SchedulerTest, chooseRefreshRateForContentSelectsMaxRefreshRate) {
-    auto display =
-            mFakeDisplayInjector.injectInternalDisplay([&](FakeDisplayDeviceInjector& injector) {
-                injector.setDisplayModes(makeModes(kMode60_1, kMode120_1), kMode60_1->getId());
-            });
+    const auto display = mFakeDisplayInjector.injectInternalDisplay(
+            [&](FakeDisplayDeviceInjector& injector) {
+                injector.setDisplayModes(kDisplay1Modes, kDisplay1Mode60->getId());
+            },
+            {.displayId = kDisplayId1});
 
     mScheduler->registerDisplay(display);
     mScheduler->setRefreshRateConfigs(display->holdRefreshRateConfigs());
@@ -256,11 +268,12 @@
     mScheduler->chooseRefreshRateForContent();
 }
 
-TEST_F(SchedulerTest, getBestDisplayMode_singleDisplay) {
-    auto display =
-            mFakeDisplayInjector.injectInternalDisplay([&](FakeDisplayDeviceInjector& injector) {
-                injector.setDisplayModes(makeModes(kMode60_1, kMode120_1), kMode60_1->getId());
-            });
+TEST_F(SchedulerTest, chooseDisplayModesSingleDisplay) {
+    const auto display = mFakeDisplayInjector.injectInternalDisplay(
+            [&](FakeDisplayDeviceInjector& injector) {
+                injector.setDisplayModes(kDisplay1Modes, kDisplay1Mode60->getId());
+            },
+            {.displayId = kDisplayId1});
 
     mScheduler->registerDisplay(display);
 
@@ -270,115 +283,125 @@
     GlobalSignals globalSignals = {.idle = true};
     mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
 
-    std::vector<DisplayModeConfig> displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
-    ASSERT_EQ(1ul, displayModeConfigs.size());
-    EXPECT_EQ(displayModeConfigs.front().displayModePtr, kMode60_1);
-    EXPECT_EQ(displayModeConfigs.front().signals, globalSignals);
+    using DisplayModeChoice = TestableScheduler::DisplayModeChoice;
+
+    auto modeChoices = mScheduler->chooseDisplayModes();
+    ASSERT_EQ(1u, modeChoices.size());
+
+    auto choice = modeChoices.get(kDisplayId1);
+    ASSERT_TRUE(choice);
+    EXPECT_EQ(choice->get(), DisplayModeChoice(kDisplay1Mode60, globalSignals));
 
     globalSignals = {.idle = false};
     mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
-    displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
-    ASSERT_EQ(1ul, displayModeConfigs.size());
-    EXPECT_EQ(displayModeConfigs.front().displayModePtr, kMode120_1);
-    EXPECT_EQ(displayModeConfigs.front().signals, globalSignals);
+
+    modeChoices = mScheduler->chooseDisplayModes();
+    ASSERT_EQ(1u, modeChoices.size());
+
+    choice = modeChoices.get(kDisplayId1);
+    ASSERT_TRUE(choice);
+    EXPECT_EQ(choice->get(), DisplayModeChoice(kDisplay1Mode120, globalSignals));
 
     globalSignals = {.touch = true};
     mScheduler->replaceTouchTimer(10);
     mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
-    displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
-    ASSERT_EQ(1ul, displayModeConfigs.size());
-    EXPECT_EQ(displayModeConfigs.front().displayModePtr, kMode120_1);
-    EXPECT_EQ(displayModeConfigs.front().signals, globalSignals);
 
-    mScheduler->unregisterDisplay(display->getPhysicalId());
+    modeChoices = mScheduler->chooseDisplayModes();
+    ASSERT_EQ(1u, modeChoices.size());
+
+    choice = modeChoices.get(kDisplayId1);
+    ASSERT_TRUE(choice);
+    EXPECT_EQ(choice->get(), DisplayModeChoice(kDisplay1Mode120, globalSignals));
+
+    mScheduler->unregisterDisplay(kDisplayId1);
     EXPECT_TRUE(mScheduler->mutableDisplays().empty());
 }
 
-TEST_F(SchedulerTest, getBestDisplayModes_multipleDisplays) {
-    auto display1 =
-            mFakeDisplayInjector.injectInternalDisplay([&](FakeDisplayDeviceInjector& injector) {
-                injector.setDisplayModes(makeModes(kMode60_1, kMode120_1), kMode60_1->getId());
-            });
-    auto display2 = mFakeDisplayInjector.injectInternalDisplay(
+TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) {
+    const auto display1 = mFakeDisplayInjector.injectInternalDisplay(
             [&](FakeDisplayDeviceInjector& injector) {
-                injector.setDisplayModes(makeModes(kMode60_2, kMode120_2), kMode60_2->getId());
+                injector.setDisplayModes(kDisplay1Modes, kDisplay1Mode60->getId());
             },
-            {.port = 253u, .hwcDisplayId = 42, .isPrimary = false});
+            {.displayId = kDisplayId1, .hwcDisplayId = 42, .isPrimary = true});
+    const auto display2 = mFakeDisplayInjector.injectInternalDisplay(
+            [&](FakeDisplayDeviceInjector& injector) {
+                injector.setDisplayModes(kDisplay2Modes, kDisplay2Mode60->getId());
+            },
+            {.displayId = kDisplayId2, .hwcDisplayId = 41, .isPrimary = false});
 
     mScheduler->registerDisplay(display1);
     mScheduler->registerDisplay(display2);
 
-    std::vector<sp<DisplayDevice>> expectedDisplays = {display1, display2};
-    std::vector<RefreshRateConfigs::LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
-    GlobalSignals globalSignals = {.idle = true};
-    std::vector<DisplayModeConfig> expectedConfigs = {DisplayModeConfig{globalSignals, kMode60_1},
-                                                      DisplayModeConfig{globalSignals, kMode60_2}};
+    using DisplayModeChoice = TestableScheduler::DisplayModeChoice;
+    TestableScheduler::DisplayModeChoiceMap expectedChoices;
 
-    mScheduler->setContentRequirements(layers);
-    mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
-    std::vector<DisplayModeConfig> displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
-    ASSERT_EQ(displayModeConfigs.size(), expectedConfigs.size());
-    for (size_t i = 0; i < expectedConfigs.size(); ++i) {
-        EXPECT_EQ(expectedConfigs.at(i).displayModePtr, displayModeConfigs.at(i).displayModePtr)
-                << "Expected fps " << expectedConfigs.at(i).displayModePtr->getFps().getIntValue()
-                << " Actual fps "
-                << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue();
-        EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals);
+    {
+        const GlobalSignals globalSignals = {.idle = true};
+        expectedChoices =
+                ftl::init::map<const PhysicalDisplayId&,
+                               DisplayModeChoice>(kDisplayId1, kDisplay1Mode60,
+                                                  globalSignals)(kDisplayId2, kDisplay2Mode60,
+                                                                 globalSignals);
+
+        std::vector<RefreshRateConfigs::LayerRequirement> layers = {{.weight = 1.f},
+                                                                    {.weight = 1.f}};
+        mScheduler->setContentRequirements(layers);
+        mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
+
+        const auto actualChoices = mScheduler->chooseDisplayModes();
+        EXPECT_EQ(expectedChoices, actualChoices);
     }
+    {
+        const GlobalSignals globalSignals = {.idle = false};
+        expectedChoices =
+                ftl::init::map<const PhysicalDisplayId&,
+                               DisplayModeChoice>(kDisplayId1, kDisplay1Mode120,
+                                                  globalSignals)(kDisplayId2, kDisplay2Mode120,
+                                                                 globalSignals);
 
-    expectedConfigs = std::vector<DisplayModeConfig>{DisplayModeConfig{globalSignals, kMode120_1},
-                                                     DisplayModeConfig{globalSignals, kMode120_2}};
+        mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
 
-    globalSignals = {.idle = false};
-    mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
-    displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
-    ASSERT_EQ(expectedConfigs.size(), displayModeConfigs.size());
-    for (size_t i = 0; i < expectedConfigs.size(); ++i) {
-        EXPECT_EQ(expectedConfigs.at(i).displayModePtr, displayModeConfigs.at(i).displayModePtr)
-                << "Expected fps " << expectedConfigs.at(i).displayModePtr->getFps().getIntValue()
-                << " Actual fps "
-                << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue();
-        EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals);
+        const auto actualChoices = mScheduler->chooseDisplayModes();
+        EXPECT_EQ(expectedChoices, actualChoices);
     }
+    {
+        const GlobalSignals globalSignals = {.touch = true};
+        mScheduler->replaceTouchTimer(10);
+        mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
 
-    globalSignals = {.touch = true};
-    mScheduler->replaceTouchTimer(10);
-    mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
-    displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
-    ASSERT_EQ(expectedConfigs.size(), displayModeConfigs.size());
-    for (size_t i = 0; i < expectedConfigs.size(); ++i) {
-        EXPECT_EQ(expectedConfigs.at(i).displayModePtr, displayModeConfigs.at(i).displayModePtr)
-                << "Expected fps " << expectedConfigs.at(i).displayModePtr->getFps().getIntValue()
-                << " Actual fps "
-                << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue();
-        EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals);
+        expectedChoices =
+                ftl::init::map<const PhysicalDisplayId&,
+                               DisplayModeChoice>(kDisplayId1, kDisplay1Mode120,
+                                                  globalSignals)(kDisplayId2, kDisplay2Mode120,
+                                                                 globalSignals);
+
+        const auto actualChoices = mScheduler->chooseDisplayModes();
+        EXPECT_EQ(expectedChoices, actualChoices);
     }
+    {
+        // This display does not support 120 Hz, so we should choose 60 Hz despite the touch signal.
+        const auto display3 = mFakeDisplayInjector.injectInternalDisplay(
+                [&](FakeDisplayDeviceInjector& injector) {
+                    injector.setDisplayModes(kDisplay3Modes, kDisplay3Mode60->getId());
+                },
+                {.displayId = kDisplayId3, .hwcDisplayId = 40, .isPrimary = false});
 
-    // Filters out the 120Hz as it's not present on the display3, even with touch active
-    // we select 60Hz here.
-    auto display3 = mFakeDisplayInjector.injectInternalDisplay(
-            [&](FakeDisplayDeviceInjector& injector) {
-                injector.setDisplayModes(makeModes(kMode60_3), kMode60_3->getId());
-            },
-            {.port = 252u, .hwcDisplayId = 41, .isPrimary = false});
+        mScheduler->registerDisplay(display3);
 
-    mScheduler->registerDisplay(display3);
+        const GlobalSignals globalSignals = {.touch = true};
+        mScheduler->replaceTouchTimer(10);
+        mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
 
-    expectedDisplays = {display1, display2, display3};
-    globalSignals = {.touch = true};
-    mScheduler->replaceTouchTimer(10);
-    expectedConfigs = std::vector<DisplayModeConfig>{DisplayModeConfig{globalSignals, kMode60_1},
-                                                     DisplayModeConfig{globalSignals, kMode60_2},
-                                                     DisplayModeConfig{globalSignals, kMode60_3}};
-    mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
-    displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
-    ASSERT_EQ(expectedConfigs.size(), displayModeConfigs.size());
-    for (size_t i = 0; i < expectedConfigs.size(); ++i) {
-        EXPECT_EQ(expectedConfigs.at(i).displayModePtr, displayModeConfigs.at(i).displayModePtr)
-                << "Expected fps " << expectedConfigs.at(i).displayModePtr->getFps().getIntValue()
-                << " Actual fps "
-                << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue();
-        EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals);
+        expectedChoices =
+                ftl::init::map<const PhysicalDisplayId&,
+                               DisplayModeChoice>(kDisplayId1, kDisplay1Mode60,
+                                                  globalSignals)(kDisplayId2, kDisplay2Mode60,
+                                                                 globalSignals)(kDisplayId3,
+                                                                                kDisplay3Mode60,
+                                                                                globalSignals);
+
+        const auto actualChoices = mScheduler->chooseDisplayModes();
+        EXPECT_EQ(expectedChoices, actualChoices);
     }
 }
 
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 68df987..26b2b67 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -107,9 +107,12 @@
         mPolicy.contentRequirements = std::move(layers);
     }
 
-    std::vector<DisplayModeConfig> getBestDisplayModeConfigs() {
+    using Scheduler::DisplayModeChoice;
+    using Scheduler::DisplayModeChoiceMap;
+
+    DisplayModeChoiceMap chooseDisplayModes() {
         std::lock_guard<std::mutex> lock(mPolicyLock);
-        return Scheduler::getBestDisplayModeConfigs();
+        return Scheduler::chooseDisplayModes();
     }
 
     void dispatchCachedReportedMode() {
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
index a83ecbc..c78b6bd 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
@@ -33,4 +33,9 @@
             .build();
 }
 
+inline DisplayModePtr createDisplayMode(PhysicalDisplayId displayId, DisplayModeId modeId,
+                                        Fps refreshRate) {
+    return createDisplayMode(modeId, refreshRate, {}, {}, displayId);
+}
+
 } // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
index 8af2dfa..7d4b159 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
@@ -24,14 +24,14 @@
 
 struct SchedulerCallback final : ISchedulerCallback {
     MOCK_METHOD(void, setVsyncEnabled, (bool), (override));
-    MOCK_METHOD(void, requestDisplayModes, (std::vector<scheduler::DisplayModeConfig>), (override));
+    MOCK_METHOD(void, requestDisplayModes, (std::vector<display::DisplayModeRequest>), (override));
     MOCK_METHOD(void, kernelTimerChanged, (bool), (override));
     MOCK_METHOD(void, triggerOnFrameRateOverridesChanged, (), (override));
 };
 
 struct NoOpSchedulerCallback final : ISchedulerCallback {
     void setVsyncEnabled(bool) override {}
-    void requestDisplayModes(std::vector<scheduler::DisplayModeConfig>) override {}
+    void requestDisplayModes(std::vector<display::DisplayModeRequest>) override {}
     void kernelTimerChanged(bool) override {}
     void triggerOnFrameRateOverridesChanged() override {}
 };