Merge "Fix wallpaper window multi-touch"
diff --git a/cmds/atrace/README.md b/cmds/atrace/README.md
new file mode 100644
index 0000000..faa43b2
--- /dev/null
+++ b/cmds/atrace/README.md
@@ -0,0 +1,48 @@
+# Atrace categories
+
+The atrace command (and the perfetto configuration) allow listing **categories**
+to select subsets of events to be traced.
+
+Each category can include some userspace events and some ftrace events.
+
+## Vendor categories
+
+It's possible to extend exiting categories (or to define new categories) from
+the /vendor partition in order to add hardware specific ftrace events.
+
+Since android 14, if the file `/vendor/etc/atrace/atrace_categories.txt`, atrace
+and perfetto will consider the categories and ftrace events listed there.
+
+The file contains a list of categories, and for each category (on the following
+lines, indented with one or more spaces of time), a list of ftrace events that
+should be enabled when the category is enabled.
+
+Each ftrace event should be a subdirectory in `/sys/kernel/tracing/events/` and
+should be of the form `group/event`. Listing a whole group is not supported,
+each event needs to be listed explicitly.
+
+It is not an error if an ftrace event is listed in the file, but not present on
+the tracing file system.
+
+Example:
+
+```
+gfx
+ mali/gpu_power_state
+ mali/mali_pm_status
+thermal_tj
+ thermal_exynos/thermal_cpu_pressure
+ thermal_exynos/thermal_exynos_arm_update
+```
+
+The file lists two categories (`gfx` and `thermal_tj`). When the `gfx` category
+is enabled, atrace (or perfetto) will enable
+`/sys/kernel/tracing/events/mali/gpu_power_state` and
+`/sys/kernel/tracing/events/mali/mali_pm_status`. When the `thermal_tj` category
+is enabled, atrace (or perfetto) will enable
+`/sys/kernel/tracing/events/thermal_exynos/thermal_cpu_pressure` and
+`/sys/kernel/tracing/events/thermal_exynos/thermal_exynos_arm_update`.
+
+Since android 14, if the file `/vendor/etc/atrace/atrace_categories.txt` exists
+on the file system, perfetto and atrace do not query the android.hardware.atrace
+HAL (which is deprecated).
diff --git a/cmds/atrace/atrace.cpp b/cmds/atrace/atrace.cpp
index 48d48ac..8105626 100644
--- a/cmds/atrace/atrace.cpp
+++ b/cmds/atrace/atrace.cpp
@@ -49,6 +49,7 @@
 #include <android-base/file.h>
 #include <android-base/macros.h>
 #include <android-base/properties.h>
+#include <android-base/strings.h>
 #include <android-base/stringprintf.h>
 
 using namespace android;
@@ -62,7 +63,7 @@
 
 using std::string;
 
-#define MAX_SYS_FILES 12
+#define MAX_SYS_FILES 13
 
 const char* k_traceTagsProperty = "debug.atrace.tags.enableflags";
 const char* k_userInitiatedTraceProperty = "debug.atrace.user_initiated";
@@ -73,7 +74,9 @@
 const char* k_pdxServiceCategory = "pdx";
 const char* k_coreServicesProp = "ro.atrace.core.services";
 
-typedef enum { OPT, REQ } requiredness  ;
+const char* kVendorCategoriesPath = "/vendor/etc/atrace/atrace_categories.txt";
+
+typedef enum { OPT, REQ } requiredness;
 
 struct TracingCategory {
     // The name identifying the category.
@@ -189,6 +192,8 @@
         { OPT,      "events/f2fs/f2fs_sync_file_exit/enable" },
         { OPT,      "events/f2fs/f2fs_write_begin/enable" },
         { OPT,      "events/f2fs/f2fs_write_end/enable" },
+        { OPT,      "events/f2fs/f2fs_iostat/enable" },
+        { OPT,      "events/f2fs/f2fs_iostat_latency/enable" },
         { OPT,      "events/ext4/ext4_da_write_begin/enable" },
         { OPT,      "events/ext4/ext4_da_write_end/enable" },
         { OPT,      "events/ext4/ext4_sync_file_enter/enable" },
@@ -253,7 +258,20 @@
     } },
 };
 
-struct TracingVendorCategory {
+// A category in the vendor categories file.
+struct TracingVendorFileCategory {
+    // The name identifying the category.
+    std::string name;
+
+    // If the category is enabled through command.
+    bool enabled = false;
+
+    // Paths to the ftrace enable files (relative to g_traceFolder).
+    std::vector<std::string> ftrace_enable_paths;
+};
+
+// A category in the vendor HIDL HAL.
+struct TracingVendorHalCategory {
     // The name identifying the category.
     std::string name;
 
@@ -263,11 +281,8 @@
     // If the category is enabled through command.
     bool enabled;
 
-    TracingVendorCategory(string &&name, string &&description, bool enabled)
-            : name(std::move(name))
-            , description(std::move(description))
-            , enabled(enabled)
-    {}
+    TracingVendorHalCategory(string&& name, string&& description, bool enabled)
+          : name(std::move(name)), description(std::move(description)), enabled(enabled) {}
 };
 
 /* Command line options */
@@ -287,8 +302,9 @@
 static bool g_traceAborted = false;
 static bool g_categoryEnables[arraysize(k_categories)] = {};
 static std::string g_traceFolder;
+static std::vector<TracingVendorFileCategory> g_vendorFileCategories;
 static sp<IAtraceDevice> g_atraceHal;
-static std::vector<TracingVendorCategory> g_vendorCategories;
+static std::vector<TracingVendorHalCategory> g_vendorHalCategories;
 
 /* Sys file paths */
 static const char* k_traceClockPath =
@@ -645,6 +661,13 @@
             }
         }
     }
+    for (const TracingVendorFileCategory& c : g_vendorFileCategories) {
+        for (const std::string& path : c.ftrace_enable_paths) {
+            if (fileIsWritable(path.c_str())) {
+                ok &= setKernelOptionEnable(path.c_str(), false);
+            }
+        }
+    }
     return ok;
 }
 
@@ -724,7 +747,13 @@
 static bool setCategoryEnable(const char* name)
 {
     bool vendor_found = false;
-    for (auto &c : g_vendorCategories) {
+    for (auto& c : g_vendorFileCategories) {
+        if (strcmp(name, c.name.c_str()) == 0) {
+            c.enabled = true;
+            vendor_found = true;
+        }
+    }
+    for (auto& c : g_vendorHalCategories) {
         if (strcmp(name, c.name.c_str()) == 0) {
             c.enabled = true;
             vendor_found = true;
@@ -870,6 +899,16 @@
         }
     }
 
+    for (const TracingVendorFileCategory& c : g_vendorFileCategories) {
+        if (c.enabled) {
+            for (const std::string& path : c.ftrace_enable_paths) {
+                if (fileIsWritable(path.c_str())) {
+                    ok &= setKernelOptionEnable(path.c_str(), true);
+                }
+            }
+        }
+    }
+
     return ok;
 }
 
@@ -1055,7 +1094,10 @@
             printf("  %10s - %s\n", c.name, c.longname);
         }
     }
-    for (const auto &c : g_vendorCategories) {
+    for (const auto& c : g_vendorFileCategories) {
+        printf("  %10s - (VENDOR)\n", c.name.c_str());
+    }
+    for (const auto& c : g_vendorHalCategories) {
         printf("  %10s - %s (HAL)\n", c.name.c_str(), c.description.c_str());
     }
 }
@@ -1114,8 +1156,38 @@
     return true;
 }
 
-void initVendorCategories()
-{
+void initVendorCategoriesFromFile() {
+    std::ifstream is(kVendorCategoriesPath);
+    for (std::string line; std::getline(is, line);) {
+        if (line.empty()) {
+            continue;
+        }
+        if (android::base::StartsWith(line, ' ') || android::base::StartsWith(line, '\t')) {
+            if (g_vendorFileCategories.empty()) {
+                fprintf(stderr, "Malformed vendor categories file\n");
+                exit(1);
+                return;
+            }
+            std::string_view path = std::string_view(line).substr(1);
+            while (android::base::StartsWith(path, ' ') || android::base::StartsWith(path, '\t')) {
+                path.remove_prefix(1);
+            }
+            if (path.empty()) {
+                continue;
+            }
+            std::string enable_path = "events/";
+            enable_path += path;
+            enable_path += "/enable";
+            g_vendorFileCategories.back().ftrace_enable_paths.push_back(std::move(enable_path));
+        } else {
+            TracingVendorFileCategory cat;
+            cat.name = line;
+            g_vendorFileCategories.push_back(std::move(cat));
+        }
+    }
+}
+
+void initVendorCategoriesFromHal() {
     g_atraceHal = IAtraceDevice::getService();
 
     if (g_atraceHal == nullptr) {
@@ -1123,27 +1195,34 @@
         return;
     }
 
-    Return<void> ret = g_atraceHal->listCategories(
-        [](const auto& list) {
-            g_vendorCategories.reserve(list.size());
-            for (const auto& category : list) {
-                g_vendorCategories.emplace_back(category.name, category.description, false);
-            }
-        });
+    Return<void> ret = g_atraceHal->listCategories([](const auto& list) {
+        g_vendorHalCategories.reserve(list.size());
+        for (const auto& category : list) {
+            g_vendorHalCategories.emplace_back(category.name, category.description, false);
+        }
+    });
     if (!ret.isOk()) {
         fprintf(stderr, "calling atrace HAL failed: %s\n", ret.description().c_str());
     }
 }
 
-static bool setUpVendorTracing()
-{
+void initVendorCategories() {
+    // If kVendorCategoriesPath exists on the filesystem, do not use the HAL.
+    if (access(kVendorCategoriesPath, F_OK) != -1) {
+        initVendorCategoriesFromFile();
+    } else {
+        initVendorCategoriesFromHal();
+    }
+}
+
+static bool setUpVendorTracingWithHal() {
     if (g_atraceHal == nullptr) {
         // No atrace HAL
         return true;
     }
 
     std::vector<hidl_string> categories;
-    for (const auto &c : g_vendorCategories) {
+    for (const auto& c : g_vendorHalCategories) {
         if (c.enabled) {
             categories.emplace_back(c.name);
         }
@@ -1164,15 +1243,14 @@
     return true;
 }
 
-static bool cleanUpVendorTracing()
-{
+static bool cleanUpVendorTracingWithHal() {
     if (g_atraceHal == nullptr) {
         // No atrace HAL
         return true;
     }
 
-    if (!g_vendorCategories.size()) {
-        // No vendor categories
+    if (!g_vendorHalCategories.size()) {
+        // No vendor HAL categories
         return true;
     }
 
@@ -1326,7 +1404,7 @@
 
     if (ok && traceStart && !onlyUserspace) {
         ok &= setUpKernelTracing();
-        ok &= setUpVendorTracing();
+        ok &= setUpVendorTracingWithHal();
         ok &= startTrace();
     }
 
@@ -1397,7 +1475,7 @@
     if (traceStop) {
         cleanUpUserspaceTracing();
         if (!onlyUserspace) {
-            cleanUpVendorTracing();
+            cleanUpVendorTracingWithHal();
             cleanUpKernelTracing();
         }
     }
diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h
index eed6b33..4a5bd5e 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -88,36 +88,6 @@
 typedef struct APerformanceHintSession APerformanceHintSession;
 
 /**
- * Hints for the session used by {@link APerformanceHint_sendHint} to signal upcoming changes
- * in the mode or workload.
- */
-enum SessionHint {
-    /**
-     * This hint indicates a sudden increase in CPU workload intensity. It means
-     * that this hint session needs extra CPU resources immediately to meet the
-     * target duration for the current work cycle.
-     */
-    CPU_LOAD_UP = 0,
-    /**
-     * This hint indicates a decrease in CPU workload intensity. It means that
-     * this hint session can reduce CPU resources and still meet the target duration.
-     */
-    CPU_LOAD_DOWN = 1,
-    /*
-     * This hint indicates an upcoming CPU workload that is completely changed and
-     * unknown. It means that the hint session should reset CPU resources to a known
-     * baseline to prepare for an arbitrary load, and must wake up if inactive.
-     */
-    CPU_LOAD_RESET = 2,
-    /*
-     * This hint indicates that the most recent CPU workload is resuming after a
-     * period of inactivity. It means that the hint session should allocate similar
-     * CPU resources to what was used previously, and must wake up if inactive.
-     */
-    CPU_LOAD_RESUME = 3,
-};
-
-/**
   * Acquire an instance of the performance hint manager.
   *
   * @return manager instance on success, nullptr on failure.
@@ -190,15 +160,21 @@
         APerformanceHintSession* session) __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
- * Sends performance hints to inform the hint session of changes in the workload.
+ * Set a list of threads to the performance hint session. This operation will replace
+ * the current list of threads with the given list of threads.
  *
- * @param session The performance hint session instance to update.
- * @param hint The hint to send to the session.
- * @return 0 on success
+ * @param session The performance hint session instance for the threads.
+ * @param threadIds The list of threads to be associated with this session. They must be part of
+ *     this app's thread group.
+ * @param size the size of the list of threadIds.
+ * @return 0 on success.
+ *         EINVAL if the list of thread ids is empty or if  any of the thread ids is not part of the thread group.
  *         EPIPE if communication with the system service has failed.
  */
-int APerformanceHint_sendHint(
-        APerformanceHintSession* session, int hint) __INTRODUCED_IN(__ANDROID_API_U__);
+int APerformanceHint_setThreads(
+        APerformanceHintSession* session,
+        const int32_t* threadIds,
+        size_t size) __INTRODUCED_IN(__ANDROID_API_U__);
 
 __END_DECLS
 
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index e911734..5fa9fda 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -205,6 +205,16 @@
     int32_t id;
 };
 
+struct KeyboardLayoutInfo {
+    explicit KeyboardLayoutInfo(std::string languageTag, std::string layoutType)
+          : languageTag(languageTag), layoutType(layoutType) {}
+
+    // A BCP 47 conformant language tag such as "en-US".
+    std::string languageTag;
+    // The layout type such as QWERTY or AZERTY.
+    std::string layoutType;
+};
+
 /*
  * Describes the characteristics and capabilities of an input device.
  */
@@ -256,6 +266,11 @@
     void setKeyboardType(int32_t keyboardType);
     inline int32_t getKeyboardType() const { return mKeyboardType; }
 
+    void setKeyboardLayoutInfo(KeyboardLayoutInfo keyboardLayoutInfo);
+    inline const std::optional<KeyboardLayoutInfo>& getKeyboardLayoutInfo() const {
+        return mKeyboardLayoutInfo;
+    }
+
     inline void setKeyCharacterMap(const std::shared_ptr<KeyCharacterMap> value) {
         mKeyCharacterMap = value;
     }
@@ -296,6 +311,7 @@
     bool mIsExternal;
     bool mHasMic;
     hardware::input::InputDeviceCountryCode mCountryCode;
+    std::optional<KeyboardLayoutInfo> mKeyboardLayoutInfo;
     uint32_t mSources;
     int32_t mKeyboardType;
     std::shared_ptr<KeyCharacterMap> mKeyCharacterMap;
diff --git a/include/private/performance_hint_private.h b/include/private/performance_hint_private.h
index f27f5f1..eaf3b5e 100644
--- a/include/private/performance_hint_private.h
+++ b/include/private/performance_hint_private.h
@@ -24,6 +24,51 @@
  */
 void APerformanceHint_setIHintManagerForTesting(void* iManager);
 
+/**
+ * Hints for the session used to signal upcoming changes in the mode or workload.
+ */
+enum SessionHint {
+    /**
+     * This hint indicates a sudden increase in CPU workload intensity. It means
+     * that this hint session needs extra CPU resources immediately to meet the
+     * target duration for the current work cycle.
+     */
+    CPU_LOAD_UP = 0,
+    /**
+     * This hint indicates a decrease in CPU workload intensity. It means that
+     * this hint session can reduce CPU resources and still meet the target duration.
+     */
+    CPU_LOAD_DOWN = 1,
+    /*
+     * This hint indicates an upcoming CPU workload that is completely changed and
+     * unknown. It means that the hint session should reset CPU resources to a known
+     * baseline to prepare for an arbitrary load, and must wake up if inactive.
+     */
+    CPU_LOAD_RESET = 2,
+    /*
+     * This hint indicates that the most recent CPU workload is resuming after a
+     * period of inactivity. It means that the hint session should allocate similar
+     * CPU resources to what was used previously, and must wake up if inactive.
+     */
+    CPU_LOAD_RESUME = 3,
+};
+
+/**
+ * Sends performance hints to inform the hint session of changes in the workload.
+ *
+ * @param session The performance hint session instance to update.
+ * @param hint The hint to send to the session.
+ * @return 0 on success
+ *         EPIPE if communication with the system service has failed.
+ */
+int APerformanceHint_sendHint(void* session, int hint);
+
+/**
+ * Return the list of thread ids, this API should only be used for testing only.
+ */
+int APerformanceHint_getThreadIds(void* aPerformanceHintSession,
+                                  int32_t* const threadIds, size_t* const size);
+
 __END_DECLS
 
 #endif // ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index c0f3e30..6d64e1e 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -132,12 +132,21 @@
     } else {
         out << "\ttarget.ptr=" << btd->target.ptr;
     }
-    out << "\t (cookie " << btd->cookie << ")"
-        << "\n"
+    out << "\t (cookie " << btd->cookie << ")\n"
         << "\tcode=" << TypeCode(btd->code) << ", flags=" << (void*)(uint64_t)btd->flags << "\n"
-        << "\tdata=" << btd->data.ptr.buffer << " (" << (void*)btd->data_size << " bytes)"
-        << "\n"
-        << "\toffsets=" << btd->data.ptr.offsets << " (" << (void*)btd->offsets_size << " bytes)";
+        << "\tdata=" << btd->data.ptr.buffer << " (" << (void*)btd->data_size << " bytes)\n"
+        << "\toffsets=" << btd->data.ptr.offsets << " (" << (void*)btd->offsets_size << " bytes)\n";
+    return btd + 1;
+}
+
+static const void* printBinderTransactionDataSecCtx(std::ostream& out, const void* data) {
+    const binder_transaction_data_secctx* btd = (const binder_transaction_data_secctx*)data;
+
+    printBinderTransactionData(out, &btd->transaction_data);
+
+    char* secctx = (char*)btd->secctx;
+    out << "\tsecctx=" << secctx << "\n";
+
     return btd+1;
 }
 
@@ -156,6 +165,11 @@
     out << "\t" << kReturnStrings[cmdIndex];
 
     switch (code) {
+        case BR_TRANSACTION_SEC_CTX: {
+            out << ": ";
+            cmd = (const int32_t*)printBinderTransactionDataSecCtx(out, cmd);
+        } break;
+
         case BR_TRANSACTION:
         case BR_REPLY: {
             out << ": ";
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index ee081c4..44ff62b 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -1475,7 +1475,7 @@
 #ifdef BINDER_WITH_KERNEL_IPC
     flat_binder_object obj;
     obj.hdr.type = BINDER_TYPE_FD;
-    obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
+    obj.flags = 0;
     obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */
     obj.handle = fd;
     obj.cookie = takeOwnership ? 1 : 0;
diff --git a/libs/binder/RpcServer.cpp b/libs/binder/RpcServer.cpp
index fedc1d9..0d06e9e 100644
--- a/libs/binder/RpcServer.cpp
+++ b/libs/binder/RpcServer.cpp
@@ -50,7 +50,8 @@
 
 RpcServer::RpcServer(std::unique_ptr<RpcTransportCtx> ctx) : mCtx(std::move(ctx)) {}
 RpcServer::~RpcServer() {
-    (void)shutdown();
+    RpcMutexUniqueLock _l(mLock);
+    LOG_ALWAYS_FATAL_IF(mShutdownTrigger != nullptr, "Must call shutdown() before destructor");
 }
 
 sp<RpcServer> RpcServer::make(std::unique_ptr<RpcTransportCtxFactory> rpcTransportCtxFactory) {
@@ -203,11 +204,15 @@
     iovec iov{&zero, sizeof(zero)};
     std::vector<std::variant<base::unique_fd, base::borrowed_fd>> fds;
 
-    if (receiveMessageFromSocket(server.mServer, &iov, 1, &fds) < 0) {
+    ssize_t num_bytes = receiveMessageFromSocket(server.mServer, &iov, 1, &fds);
+    if (num_bytes < 0) {
         int savedErrno = errno;
         ALOGE("Failed recvmsg: %s", strerror(savedErrno));
         return -savedErrno;
     }
+    if (num_bytes == 0) {
+        return DEAD_OBJECT;
+    }
     if (fds.size() != 1) {
         ALOGE("Expected exactly one fd from recvmsg, got %zu", fds.size());
         return -EINVAL;
@@ -242,8 +247,11 @@
         socklen_t addrLen = addr.size();
 
         RpcTransportFd clientSocket;
-        if (mAcceptFn(*this, &clientSocket) != OK) {
-            continue;
+        if ((status = mAcceptFn(*this, &clientSocket)) != OK) {
+            if (status == DEAD_OBJECT)
+                break;
+            else
+                continue;
         }
 
         LOG_RPC_DETAIL("accept on fd %d yields fd %d", mServer.fd.get(), clientSocket.fd.get());
diff --git a/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp b/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
index 3ec049e..3ebbed6 100644
--- a/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
+++ b/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
@@ -23,6 +23,13 @@
 
 struct AIBinder;
 struct ARpcServer;
+struct ARpcSession;
+
+enum class ARpcSession_FileDescriptorTransportMode {
+    None,
+    Unix,
+    Trusty,
+};
 
 // Starts an RPC server on a given port and a given root IBinder object.
 // The server will only accept connections from the given CID.
@@ -40,6 +47,22 @@
 // could not be started.
 [[nodiscard]] ARpcServer* ARpcServer_newInitUnixDomain(AIBinder* service, const char* name);
 
+// Starts an RPC server that bootstraps sessions using an existing Unix domain
+// socket pair, with a given root IBinder object.
+// Callers should create a pair of SOCK_STREAM Unix domain sockets, pass one to
+// this function and the other to UnixDomainBootstrapClient(). Multiple client
+// session can be created from the client end of the pair.
+// Does not take ownership of `service`.
+// Returns an opaque handle to the running server instance, or null if the server
+// could not be started.
+[[nodiscard]] ARpcServer* ARpcServer_newUnixDomainBootstrap(AIBinder* service, int bootstrapFd);
+
+// Sets the list of supported file descriptor transport modes of this RPC server.
+void ARpcServer_setSupportedFileDescriptorTransportModes(
+        ARpcServer* handle,
+        const ARpcSession_FileDescriptorTransportMode modes[],
+        size_t modes_len);
+
 // Runs ARpcServer_join() in a background thread. Immediately returns.
 void ARpcServer_start(ARpcServer* server);
 
@@ -50,34 +73,52 @@
 void ARpcServer_join(ARpcServer* server);
 
 // Shuts down any running ARpcServer_join().
-void ARpcServer_shutdown(ARpcServer* server);
+[[nodiscard]] bool ARpcServer_shutdown(ARpcServer* server);
 
 // Frees the ARpcServer handle and drops the reference count on the underlying
 // RpcServer instance. The handle must not be reused afterwards.
 // This automatically calls ARpcServer_shutdown().
 void ARpcServer_free(ARpcServer* server);
 
-// Starts an RPC server on a given port and a given root IBinder factory.
-// RunVsockRpcServerWithFactory acts like RunVsockRpcServerCallback, but instead of
-// assigning single root IBinder object to all connections, factory is called
-// whenever a client connects, making it possible to assign unique IBinder
-// object to each client.
-bool RunVsockRpcServerWithFactory(AIBinder* (*factory)(unsigned int cid, void* context),
-                                  void* factoryContext, unsigned int port);
+// Allocates a new RpcSession object and returns an opaque handle to it.
+[[nodiscard]] ARpcSession* ARpcSession_new();
 
-AIBinder* VsockRpcClient(unsigned int cid, unsigned int port);
+// Connects to an RPC server over vsock at a given CID on a given port.
+// Returns the root Binder object of the server.
+AIBinder* ARpcSession_setupVsockClient(ARpcSession* session, unsigned int cid,
+                                       unsigned int port);
 
-// Gets the service via the RPC binder with Unix domain socket with the given
-// Unix socket `name`.
-// The final Unix domain socket path name is /dev/socket/`name`.
-AIBinder* UnixDomainRpcClient(const char* name);
+// Connects to an RPC server over a Unix Domain Socket of the given name.
+// The final Unix Domain Socket path name is /dev/socket/`name`.
+// Returns the root Binder object of the server.
+AIBinder* ARpcSession_setupUnixDomainClient(ARpcSession* session, const char* name);
 
-// Connect to an RPC server with preconnected file descriptors.
+// Connects to an RPC server over the given bootstrap Unix domain socket.
+// Does NOT take ownership of `bootstrapFd`.
+AIBinder* ARpcSession_setupUnixDomainBootstrapClient(ARpcSession* session,
+                                                     int bootstrapFd);
+
+// Connects to an RPC server with preconnected file descriptors.
 //
 // requestFd should connect to the server and return a valid file descriptor, or
 // -1 if connection fails.
 //
 // param will be passed to requestFd. Callers can use param to pass contexts to
 // the requestFd function.
-AIBinder* RpcPreconnectedClient(int (*requestFd)(void* param), void* param);
+AIBinder* ARpcSession_setupPreconnectedClient(ARpcSession* session,
+                                              int (*requestFd)(void* param),
+                                              void* param);
+
+// Sets the file descriptor transport mode for this session.
+void ARpcSession_setFileDescriptorTransportMode(ARpcSession* session,
+                                                ARpcSession_FileDescriptorTransportMode mode);
+
+// Sets the maximum number of incoming threads.
+void ARpcSession_setMaxIncomingThreads(ARpcSession* session, size_t threads);
+
+// Sets the maximum number of outgoing threads.
+void ARpcSession_setMaxOutgoingThreads(ARpcSession* session, size_t threads);
+
+// Decrements the refcount of the underlying RpcSession object.
+void ARpcSession_free(ARpcSession* session);
 }
diff --git a/libs/binder/libbinder_rpc_unstable.cpp b/libs/binder/libbinder_rpc_unstable.cpp
index 88f8c94..78dae4b 100644
--- a/libs/binder/libbinder_rpc_unstable.cpp
+++ b/libs/binder/libbinder_rpc_unstable.cpp
@@ -35,51 +35,46 @@
 // Opaque handle for RpcServer.
 struct ARpcServer {};
 
-static sp<RpcServer> toRpcServer(ARpcServer* handle) {
-    auto ref = reinterpret_cast<RpcServer*>(handle);
-    return sp<RpcServer>::fromExisting(ref);
-}
+// Opaque handle for RpcSession.
+struct ARpcSession {};
 
-static ARpcServer* createRpcServerHandle(sp<RpcServer>& server) {
+template <typename A, typename T>
+static A* createObjectHandle(sp<T>& server) {
     auto ref = server.get();
     ref->incStrong(ref);
-    return reinterpret_cast<ARpcServer*>(ref);
+    return reinterpret_cast<A*>(ref);
 }
 
-static void freeRpcServerHandle(ARpcServer* handle) {
-    auto ref = reinterpret_cast<RpcServer*>(handle);
+template <typename T, typename A>
+static void freeObjectHandle(A* handle) {
+    LOG_ALWAYS_FATAL_IF(handle == nullptr, "Handle cannot be null");
+    auto ref = reinterpret_cast<T*>(handle);
     ref->decStrong(ref);
 }
 
-static unsigned int cidFromStructAddr(const void* addr, size_t addrlen) {
-    LOG_ALWAYS_FATAL_IF(addrlen < sizeof(sockaddr_vm), "sockaddr is truncated");
-    const sockaddr_vm* vaddr = reinterpret_cast<const sockaddr_vm*>(addr);
-    LOG_ALWAYS_FATAL_IF(vaddr->svm_family != AF_VSOCK, "address is not a vsock");
-    return vaddr->svm_cid;
+template <typename T, typename A>
+static sp<T> handleToStrongPointer(A* handle) {
+    LOG_ALWAYS_FATAL_IF(handle == nullptr, "Handle cannot be null");
+    auto ref = reinterpret_cast<T*>(handle);
+    return sp<T>::fromExisting(ref);
+}
+
+RpcSession::FileDescriptorTransportMode toTransportMode(
+        ARpcSession_FileDescriptorTransportMode mode) {
+    switch (mode) {
+        case ARpcSession_FileDescriptorTransportMode::None:
+            return RpcSession::FileDescriptorTransportMode::NONE;
+        case ARpcSession_FileDescriptorTransportMode::Unix:
+            return RpcSession::FileDescriptorTransportMode::UNIX;
+        case ARpcSession_FileDescriptorTransportMode::Trusty:
+            return RpcSession::FileDescriptorTransportMode::TRUSTY;
+        default:
+            return RpcSession::FileDescriptorTransportMode::NONE;
+    }
 }
 
 extern "C" {
 
-bool RunVsockRpcServerWithFactory(AIBinder* (*factory)(unsigned int cid, void* context),
-                                  void* factoryContext, unsigned int port) {
-    auto server = RpcServer::make();
-    if (status_t status = server->setupVsockServer(VMADDR_CID_ANY, port); status != OK) {
-        LOG(ERROR) << "Failed to set up vsock server with port " << port
-                   << " error: " << statusToString(status).c_str();
-        return false;
-    }
-    server->setPerSessionRootObject([=](const void* addr, size_t addrlen) {
-        unsigned int cid = cidFromStructAddr(addr, addrlen);
-        return AIBinder_toPlatformBinder(factory(cid, factoryContext));
-    });
-
-    server->join();
-
-    // Shutdown any open sessions since server failed.
-    (void)server->shutdown();
-    return true;
-}
-
 ARpcServer* ARpcServer_newVsock(AIBinder* service, unsigned int cid, unsigned int port) {
     auto server = RpcServer::make();
 
@@ -96,16 +91,18 @@
     }
     if (cid != VMADDR_CID_ANY) {
         server->setConnectionFilter([=](const void* addr, size_t addrlen) {
-            unsigned int remoteCid = cidFromStructAddr(addr, addrlen);
-            if (cid != remoteCid) {
-                LOG(ERROR) << "Rejected vsock connection from CID " << remoteCid;
+            LOG_ALWAYS_FATAL_IF(addrlen < sizeof(sockaddr_vm), "sockaddr is truncated");
+            const sockaddr_vm* vaddr = reinterpret_cast<const sockaddr_vm*>(addr);
+            LOG_ALWAYS_FATAL_IF(vaddr->svm_family != AF_VSOCK, "address is not a vsock");
+            if (cid != vaddr->svm_cid) {
+                LOG(ERROR) << "Rejected vsock connection from CID " << vaddr->svm_cid;
                 return false;
             }
             return true;
         });
     }
     server->setRootObject(AIBinder_toPlatformBinder(service));
-    return createRpcServerHandle(server);
+    return createObjectHandle<ARpcServer>(server);
 }
 
 ARpcServer* ARpcServer_newInitUnixDomain(AIBinder* service, const char* name) {
@@ -121,27 +118,67 @@
         return nullptr;
     }
     server->setRootObject(AIBinder_toPlatformBinder(service));
-    return createRpcServerHandle(server);
+    return createObjectHandle<ARpcServer>(server);
+}
+
+ARpcServer* ARpcServer_newUnixDomainBootstrap(AIBinder* service, int bootstrapFd) {
+    auto server = RpcServer::make();
+    auto fd = unique_fd(bootstrapFd);
+    if (!fd.ok()) {
+        LOG(ERROR) << "Invalid bootstrap fd " << bootstrapFd;
+        return nullptr;
+    }
+    if (status_t status = server->setupUnixDomainSocketBootstrapServer(std::move(fd));
+        status != OK) {
+        LOG(ERROR) << "Failed to set up Unix Domain RPC server with bootstrap fd " << bootstrapFd
+                   << " error: " << statusToString(status).c_str();
+        return nullptr;
+    }
+    server->setRootObject(AIBinder_toPlatformBinder(service));
+    return createObjectHandle<ARpcServer>(server);
+}
+
+void ARpcServer_setSupportedFileDescriptorTransportModes(
+        ARpcServer* handle, const ARpcSession_FileDescriptorTransportMode modes[],
+        size_t modes_len) {
+    auto server = handleToStrongPointer<RpcServer>(handle);
+    std::vector<RpcSession::FileDescriptorTransportMode> modevec;
+    for (size_t i = 0; i < modes_len; i++) {
+        modevec.push_back(toTransportMode(modes[i]));
+    }
+    server->setSupportedFileDescriptorTransportModes(modevec);
 }
 
 void ARpcServer_start(ARpcServer* handle) {
-    toRpcServer(handle)->start();
+    handleToStrongPointer<RpcServer>(handle)->start();
 }
 
 void ARpcServer_join(ARpcServer* handle) {
-    toRpcServer(handle)->join();
+    handleToStrongPointer<RpcServer>(handle)->join();
 }
 
-void ARpcServer_shutdown(ARpcServer* handle) {
-    toRpcServer(handle)->shutdown();
+bool ARpcServer_shutdown(ARpcServer* handle) {
+    return handleToStrongPointer<RpcServer>(handle)->shutdown();
 }
 
 void ARpcServer_free(ARpcServer* handle) {
-    freeRpcServerHandle(handle);
+    // Ignore the result of ARpcServer_shutdown - either it had been called
+    // earlier, or the RpcServer destructor will panic.
+    (void)ARpcServer_shutdown(handle);
+    freeObjectHandle<RpcServer>(handle);
 }
 
-AIBinder* VsockRpcClient(unsigned int cid, unsigned int port) {
+ARpcSession* ARpcSession_new() {
     auto session = RpcSession::make();
+    return createObjectHandle<ARpcSession>(session);
+}
+
+void ARpcSession_free(ARpcSession* handle) {
+    freeObjectHandle<RpcSession>(handle);
+}
+
+AIBinder* ARpcSession_setupVsockClient(ARpcSession* handle, unsigned int cid, unsigned int port) {
+    auto session = handleToStrongPointer<RpcSession>(handle);
     if (status_t status = session->setupVsockClient(cid, port); status != OK) {
         LOG(ERROR) << "Failed to set up vsock client with CID " << cid << " and port " << port
                    << " error: " << statusToString(status).c_str();
@@ -150,10 +187,10 @@
     return AIBinder_fromPlatformBinder(session->getRootObject());
 }
 
-AIBinder* UnixDomainRpcClient(const char* name) {
+AIBinder* ARpcSession_setupUnixDomainClient(ARpcSession* handle, const char* name) {
     std::string pathname(name);
     pathname = ANDROID_SOCKET_DIR "/" + pathname;
-    auto session = RpcSession::make();
+    auto session = handleToStrongPointer<RpcSession>(handle);
     if (status_t status = session->setupUnixDomainClient(pathname.c_str()); status != OK) {
         LOG(ERROR) << "Failed to set up Unix Domain RPC client with path: " << pathname
                    << " error: " << statusToString(status).c_str();
@@ -162,8 +199,25 @@
     return AIBinder_fromPlatformBinder(session->getRootObject());
 }
 
-AIBinder* RpcPreconnectedClient(int (*requestFd)(void* param), void* param) {
-    auto session = RpcSession::make();
+AIBinder* ARpcSession_setupUnixDomainBootstrapClient(ARpcSession* handle, int bootstrapFd) {
+    auto session = handleToStrongPointer<RpcSession>(handle);
+    auto fd = unique_fd(dup(bootstrapFd));
+    if (!fd.ok()) {
+        LOG(ERROR) << "Invalid bootstrap fd " << bootstrapFd;
+        return nullptr;
+    }
+    if (status_t status = session->setupUnixDomainSocketBootstrapClient(std::move(fd));
+        status != OK) {
+        LOG(ERROR) << "Failed to set up Unix Domain RPC client with bootstrap fd: " << bootstrapFd
+                   << " error: " << statusToString(status).c_str();
+        return nullptr;
+    }
+    return AIBinder_fromPlatformBinder(session->getRootObject());
+}
+
+AIBinder* ARpcSession_setupPreconnectedClient(ARpcSession* handle, int (*requestFd)(void* param),
+                                              void* param) {
+    auto session = handleToStrongPointer<RpcSession>(handle);
     auto request = [=] { return unique_fd{requestFd(param)}; };
     if (status_t status = session->setupPreconnectedClient(unique_fd{}, request); status != OK) {
         LOG(ERROR) << "Failed to set up vsock client. error: " << statusToString(status).c_str();
@@ -171,4 +225,20 @@
     }
     return AIBinder_fromPlatformBinder(session->getRootObject());
 }
+
+void ARpcSession_setFileDescriptorTransportMode(ARpcSession* handle,
+                                                ARpcSession_FileDescriptorTransportMode mode) {
+    auto session = handleToStrongPointer<RpcSession>(handle);
+    session->setFileDescriptorTransportMode(toTransportMode(mode));
+}
+
+void ARpcSession_setMaxIncomingThreads(ARpcSession* handle, size_t threads) {
+    auto session = handleToStrongPointer<RpcSession>(handle);
+    session->setMaxIncomingThreads(threads);
+}
+
+void ARpcSession_setMaxOutgoingThreads(ARpcSession* handle, size_t threads) {
+    auto session = handleToStrongPointer<RpcSession>(handle);
+    session->setMaxOutgoingThreads(threads);
+}
 }
diff --git a/libs/binder/rust/rpcbinder/Android.bp b/libs/binder/rust/rpcbinder/Android.bp
index f70ebfc..afb73e9 100644
--- a/libs/binder/rust/rpcbinder/Android.bp
+++ b/libs/binder/rust/rpcbinder/Android.bp
@@ -68,10 +68,13 @@
     visibility: [":__subpackages__"],
     source_stem: "bindings",
     bindgen_flags: [
+        "--size_t-is-usize",
         "--blocklist-type",
         "AIBinder",
         "--raw-line",
         "use binder_ndk_sys::AIBinder;",
+        "--rustified-enum",
+        "ARpcSession_FileDescriptorTransportMode",
     ],
     rustlibs: [
         "libbinder_ndk_sys",
diff --git a/libs/binder/rust/rpcbinder/src/client.rs b/libs/binder/rust/rpcbinder/src/client.rs
deleted file mode 100644
index 48c787b..0000000
--- a/libs/binder/rust/rpcbinder/src/client.rs
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-use binder::{unstable_api::new_spibinder, FromIBinder, SpIBinder, StatusCode, Strong};
-use std::ffi::CString;
-use std::os::{
-    raw::{c_int, c_void},
-    unix::io::RawFd,
-};
-
-/// Connects to an RPC Binder server over vsock.
-pub fn get_vsock_rpc_service(cid: u32, port: u32) -> Option<SpIBinder> {
-    // 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.
-pub fn get_vsock_rpc_interface<T: FromIBinder + ?Sized>(
-    cid: u32,
-    port: u32,
-) -> Result<Strong<T>, StatusCode> {
-    interface_cast(get_vsock_rpc_service(cid, port))
-}
-
-/// Connects to an RPC Binder server over Unix domain socket.
-pub fn get_unix_domain_rpc_service(socket_name: &str) -> Option<SpIBinder> {
-    let socket_name = match CString::new(socket_name) {
-        Ok(s) => s,
-        Err(e) => {
-            log::error!("Cannot convert {} to CString. Error: {:?}", socket_name, e);
-            return None;
-        }
-    };
-    // SAFETY: AIBinder returned by UnixDomainRpcClient has correct reference count,
-    // and the ownership can safely be taken by new_spibinder.
-    unsafe { new_spibinder(binder_rpc_unstable_bindgen::UnixDomainRpcClient(socket_name.as_ptr())) }
-}
-
-/// Connects to an RPC Binder server for a particular interface over Unix domain socket.
-pub fn get_unix_domain_rpc_interface<T: FromIBinder + ?Sized>(
-    socket_name: &str,
-) -> Result<Strong<T>, StatusCode> {
-    interface_cast(get_unix_domain_rpc_service(socket_name))
-}
-
-/// Connects to an RPC Binder server, using the given callback to get (and take ownership of)
-/// file descriptors already connected to it.
-pub fn get_preconnected_rpc_service(
-    mut request_fd: impl FnMut() -> Option<RawFd>,
-) -> Option<SpIBinder> {
-    // Double reference the factory because trait objects aren't FFI safe.
-    let mut request_fd_ref: RequestFd = &mut request_fd;
-    let param = &mut request_fd_ref as *mut RequestFd as *mut c_void;
-
-    // SAFETY: AIBinder returned by RpcPreconnectedClient has correct reference count, and the
-    // ownership can be safely taken by new_spibinder. RpcPreconnectedClient does not take ownership
-    // of param, only passing it to request_fd_wrapper.
-    unsafe {
-        new_spibinder(binder_rpc_unstable_bindgen::RpcPreconnectedClient(
-            Some(request_fd_wrapper),
-            param,
-        ))
-    }
-}
-
-type RequestFd<'a> = &'a mut dyn FnMut() -> Option<RawFd>;
-
-unsafe extern "C" fn request_fd_wrapper(param: *mut c_void) -> c_int {
-    // SAFETY: This is only ever called by RpcPreconnectedClient, within the lifetime of the
-    // BinderFdFactory reference, with param being a properly aligned non-null pointer to an
-    // initialized instance.
-    let request_fd_ptr = param as *mut RequestFd;
-    let request_fd = request_fd_ptr.as_mut().unwrap();
-    if let Some(fd) = request_fd() {
-        fd
-    } else {
-        -1
-    }
-}
-
-/// Connects to an RPC Binder server for a particular interface, using the given callback to get
-/// (and take ownership of) file descriptors already connected to it.
-pub fn get_preconnected_rpc_interface<T: FromIBinder + ?Sized>(
-    request_fd: impl FnMut() -> Option<RawFd>,
-) -> Result<Strong<T>, StatusCode> {
-    interface_cast(get_preconnected_rpc_service(request_fd))
-}
-
-fn interface_cast<T: FromIBinder + ?Sized>(
-    service: Option<SpIBinder>,
-) -> Result<Strong<T>, StatusCode> {
-    if let Some(service) = service {
-        FromIBinder::try_from(service)
-    } else {
-        Err(StatusCode::NAME_NOT_FOUND)
-    }
-}
diff --git a/libs/binder/rust/rpcbinder/src/lib.rs b/libs/binder/rust/rpcbinder/src/lib.rs
index 1b719aa..a957385 100644
--- a/libs/binder/rust/rpcbinder/src/lib.rs
+++ b/libs/binder/rust/rpcbinder/src/lib.rs
@@ -16,11 +16,8 @@
 
 //! API for RPC Binder services.
 
-mod client;
 mod server;
+mod session;
 
-pub use client::{
-    get_preconnected_rpc_interface, get_preconnected_rpc_service, get_unix_domain_rpc_interface,
-    get_unix_domain_rpc_service, get_vsock_rpc_interface, get_vsock_rpc_service,
-};
-pub use server::{run_vsock_rpc_server_with_factory, RpcServer, RpcServerRef};
+pub use server::{RpcServer, RpcServerRef};
+pub use session::{FileDescriptorTransportMode, RpcSession, RpcSessionRef};
diff --git a/libs/binder/rust/rpcbinder/src/server.rs b/libs/binder/rust/rpcbinder/src/server.rs
index d5f1219..761b306 100644
--- a/libs/binder/rust/rpcbinder/src/server.rs
+++ b/libs/binder/rust/rpcbinder/src/server.rs
@@ -14,14 +14,13 @@
  * limitations under the License.
  */
 
-use binder::{
-    unstable_api::{AIBinder, AsNative},
-    SpIBinder,
-};
+use crate::session::FileDescriptorTransportMode;
+use binder::{unstable_api::AsNative, SpIBinder};
 use binder_rpc_unstable_bindgen::ARpcServer;
 use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
+use std::ffi::CString;
 use std::io::{Error, ErrorKind};
-use std::{ffi::CString, os::raw, ptr::null_mut};
+use std::os::unix::io::{IntoRawFd, OwnedFd};
 
 foreign_type! {
     type CType = binder_rpc_unstable_bindgen::ARpcServer;
@@ -82,6 +81,27 @@
         }
     }
 
+    /// Creates a binder RPC server that bootstraps sessions using an existing Unix domain socket
+    /// pair, with a given root IBinder object. Callers should create a pair of SOCK_STREAM Unix
+    /// domain sockets, pass one to the server and the other to the client. Multiple client session
+    /// can be created from the client end of the pair.
+    pub fn new_unix_domain_bootstrap(
+        mut service: SpIBinder,
+        bootstrap_fd: OwnedFd,
+    ) -> Result<RpcServer, Error> {
+        let service = service.as_native_mut();
+
+        // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
+        // Plus the binder objects are threadsafe.
+        // The server takes ownership of the bootstrap FD.
+        unsafe {
+            Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newUnixDomainBootstrap(
+                service,
+                bootstrap_fd.into_raw_fd(),
+            ))
+        }
+    }
+
     unsafe fn checked_from_ptr(ptr: *mut ARpcServer) -> Result<RpcServer, Error> {
         if ptr.is_null() {
             return Err(Error::new(ErrorKind::Other, "Failed to start server"));
@@ -91,6 +111,22 @@
 }
 
 impl RpcServerRef {
+    /// Sets the list of file descriptor transport modes supported by this server.
+    pub fn set_supported_file_descriptor_transport_modes(
+        &self,
+        modes: &[FileDescriptorTransportMode],
+    ) {
+        // SAFETY - Does not keep the pointer after returning does, nor does it
+        // read past its boundary. Only passes the 'self' pointer as an opaque handle.
+        unsafe {
+            binder_rpc_unstable_bindgen::ARpcServer_setSupportedFileDescriptorTransportModes(
+                self.as_ptr(),
+                modes.as_ptr(),
+                modes.len(),
+            )
+        }
+    }
+
     /// Starts a new background thread and calls join(). Returns immediately.
     pub fn start(&self) {
         unsafe { binder_rpc_unstable_bindgen::ARpcServer_start(self.as_ptr()) };
@@ -104,51 +140,11 @@
 
     /// Shuts down the running RpcServer. Can be called multiple times and from
     /// multiple threads. Called automatically during drop().
-    pub fn shutdown(&self) {
-        unsafe { binder_rpc_unstable_bindgen::ARpcServer_shutdown(self.as_ptr()) };
-    }
-}
-
-type RpcServerFactoryRef<'a> = &'a mut (dyn FnMut(u32) -> Option<SpIBinder> + Send + Sync);
-
-/// Runs a binder RPC server, using the given factory function to construct a binder service
-/// implementation for each connection.
-///
-/// The current thread is joined to the binder thread pool to handle incoming messages.
-///
-/// Returns true if the server has shutdown normally, false if it failed in some way.
-pub fn run_vsock_rpc_server_with_factory(
-    port: u32,
-    mut factory: impl FnMut(u32) -> Option<SpIBinder> + Send + Sync,
-) -> bool {
-    // Double reference the factory because trait objects aren't FFI safe.
-    // NB: The type annotation is necessary to ensure that we have a `dyn` rather than an `impl`.
-    let mut factory_ref: RpcServerFactoryRef = &mut factory;
-    let context = &mut factory_ref as *mut RpcServerFactoryRef as *mut raw::c_void;
-
-    // SAFETY: `factory_wrapper` is only ever called by `RunVsockRpcServerWithFactory`, with context
-    // taking the pointer value above (so a properly aligned non-null pointer to an initialized
-    // `RpcServerFactoryRef`), within the lifetime of `factory_ref` (i.e. no more calls will be made
-    // after `RunVsockRpcServerWithFactory` returns).
-    unsafe {
-        binder_rpc_unstable_bindgen::RunVsockRpcServerWithFactory(
-            Some(factory_wrapper),
-            context,
-            port,
-        )
-    }
-}
-
-unsafe extern "C" fn factory_wrapper(cid: u32, context: *mut raw::c_void) -> *mut AIBinder {
-    // SAFETY: `context` was created from an `&mut RpcServerFactoryRef` by
-    // `run_vsock_rpc_server_with_factory`, and we are still within the lifetime of the value it is
-    // pointing to.
-    let factory_ptr = context as *mut RpcServerFactoryRef;
-    let factory = factory_ptr.as_mut().unwrap();
-
-    if let Some(mut service) = factory(cid) {
-        service.as_native_mut()
-    } else {
-        null_mut()
+    pub fn shutdown(&self) -> Result<(), Error> {
+        if unsafe { binder_rpc_unstable_bindgen::ARpcServer_shutdown(self.as_ptr()) } {
+            Ok(())
+        } else {
+            Err(Error::from(ErrorKind::UnexpectedEof))
+        }
     }
 }
diff --git a/libs/binder/rust/rpcbinder/src/session.rs b/libs/binder/rust/rpcbinder/src/session.rs
new file mode 100644
index 0000000..62fedb1
--- /dev/null
+++ b/libs/binder/rust/rpcbinder/src/session.rs
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 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.
+ */
+
+use binder::unstable_api::new_spibinder;
+use binder::{FromIBinder, SpIBinder, StatusCode, Strong};
+use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
+use std::ffi::CString;
+use std::os::{
+    raw::{c_int, c_void},
+    unix::io::{AsRawFd, BorrowedFd, RawFd},
+};
+
+pub use binder_rpc_unstable_bindgen::ARpcSession_FileDescriptorTransportMode as FileDescriptorTransportMode;
+
+foreign_type! {
+    type CType = binder_rpc_unstable_bindgen::ARpcSession;
+    fn drop = binder_rpc_unstable_bindgen::ARpcSession_free;
+
+    /// A type that represents a foreign instance of RpcSession.
+    #[derive(Debug)]
+    pub struct RpcSession;
+    /// A borrowed RpcSession.
+    pub struct RpcSessionRef;
+}
+
+/// SAFETY - The opaque handle can be cloned freely.
+unsafe impl Send for RpcSession {}
+/// SAFETY - The underlying C++ RpcSession class is thread-safe.
+unsafe impl Sync for RpcSession {}
+
+impl RpcSession {
+    /// Allocates a new RpcSession object.
+    pub fn new() -> RpcSession {
+        // SAFETY - Takes ownership of the returned handle, which has correct refcount.
+        unsafe { RpcSession::from_ptr(binder_rpc_unstable_bindgen::ARpcSession_new()) }
+    }
+}
+
+impl Default for RpcSession {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl RpcSessionRef {
+    /// Sets the file descriptor transport mode for this session.
+    pub fn set_file_descriptor_transport_mode(&self, mode: FileDescriptorTransportMode) {
+        // SAFETY - Only passes the 'self' pointer as an opaque handle.
+        unsafe {
+            binder_rpc_unstable_bindgen::ARpcSession_setFileDescriptorTransportMode(
+                self.as_ptr(),
+                mode,
+            )
+        };
+    }
+
+    /// Sets the maximum number of incoming threads.
+    pub fn set_max_incoming_threads(&self, threads: usize) {
+        // SAFETY - Only passes the 'self' pointer as an opaque handle.
+        unsafe {
+            binder_rpc_unstable_bindgen::ARpcSession_setMaxIncomingThreads(self.as_ptr(), threads)
+        };
+    }
+
+    /// Sets the maximum number of outgoing threads.
+    pub fn set_max_outgoing_threads(&self, threads: usize) {
+        // SAFETY - Only passes the 'self' pointer as an opaque handle.
+        unsafe {
+            binder_rpc_unstable_bindgen::ARpcSession_setMaxOutgoingThreads(self.as_ptr(), threads)
+        };
+    }
+
+    /// Connects to an RPC Binder server over vsock for a particular interface.
+    pub fn setup_vsock_client<T: FromIBinder + ?Sized>(
+        &self,
+        cid: u32,
+        port: u32,
+    ) -> Result<Strong<T>, StatusCode> {
+        // SAFETY: AIBinder returned by ARpcSession_setupVsockClient has correct
+        // reference count, and the ownership can safely be taken by new_spibinder.
+        let service = unsafe {
+            new_spibinder(binder_rpc_unstable_bindgen::ARpcSession_setupVsockClient(
+                self.as_ptr(),
+                cid,
+                port,
+            ))
+        };
+        Self::get_interface(service)
+    }
+
+    /// Connects to an RPC Binder server over a names Unix Domain Socket for
+    /// a particular interface.
+    pub fn setup_unix_domain_client<T: FromIBinder + ?Sized>(
+        &self,
+        socket_name: &str,
+    ) -> Result<Strong<T>, StatusCode> {
+        let socket_name = match CString::new(socket_name) {
+            Ok(s) => s,
+            Err(e) => {
+                log::error!("Cannot convert {} to CString. Error: {:?}", socket_name, e);
+                return Err(StatusCode::NAME_NOT_FOUND);
+            }
+        };
+
+        // SAFETY: AIBinder returned by ARpcSession_setupUnixDomainClient has correct
+        // reference count, and the ownership can safely be taken by new_spibinder.
+        let service = unsafe {
+            new_spibinder(binder_rpc_unstable_bindgen::ARpcSession_setupUnixDomainClient(
+                self.as_ptr(),
+                socket_name.as_ptr(),
+            ))
+        };
+        Self::get_interface(service)
+    }
+
+    /// Connects to an RPC Binder server over a bootstrap Unix Domain Socket
+    /// for a particular interface.
+    pub fn setup_unix_domain_bootstrap_client<T: FromIBinder + ?Sized>(
+        &self,
+        bootstrap_fd: BorrowedFd,
+    ) -> Result<Strong<T>, StatusCode> {
+        // SAFETY: ARpcSession_setupUnixDomainBootstrapClient does not take
+        // ownership of bootstrap_fd. The returned AIBinder has correct
+        // reference count, and the ownership can safely be taken by new_spibinder.
+        let service = unsafe {
+            new_spibinder(binder_rpc_unstable_bindgen::ARpcSession_setupUnixDomainBootstrapClient(
+                self.as_ptr(),
+                bootstrap_fd.as_raw_fd(),
+            ))
+        };
+        Self::get_interface(service)
+    }
+
+    /// Connects to an RPC Binder server, using the given callback to get (and
+    /// take ownership of) file descriptors already connected to it.
+    pub fn setup_preconnected_client<T: FromIBinder + ?Sized>(
+        &self,
+        mut request_fd: impl FnMut() -> Option<RawFd>,
+    ) -> Result<Strong<T>, StatusCode> {
+        // Double reference the factory because trait objects aren't FFI safe.
+        let mut request_fd_ref: RequestFd = &mut request_fd;
+        let param = &mut request_fd_ref as *mut RequestFd as *mut c_void;
+
+        // SAFETY: AIBinder returned by RpcPreconnectedClient has correct reference count, and the
+        // ownership can be safely taken by new_spibinder. RpcPreconnectedClient does not take ownership
+        // of param, only passing it to request_fd_wrapper.
+        let service = unsafe {
+            new_spibinder(binder_rpc_unstable_bindgen::ARpcSession_setupPreconnectedClient(
+                self.as_ptr(),
+                Some(request_fd_wrapper),
+                param,
+            ))
+        };
+        Self::get_interface(service)
+    }
+
+    fn get_interface<T: FromIBinder + ?Sized>(
+        service: Option<SpIBinder>,
+    ) -> Result<Strong<T>, StatusCode> {
+        if let Some(service) = service {
+            FromIBinder::try_from(service)
+        } else {
+            Err(StatusCode::NAME_NOT_FOUND)
+        }
+    }
+}
+
+type RequestFd<'a> = &'a mut dyn FnMut() -> Option<RawFd>;
+
+unsafe extern "C" fn request_fd_wrapper(param: *mut c_void) -> c_int {
+    // SAFETY: This is only ever called by RpcPreconnectedClient, within the lifetime of the
+    // BinderFdFactory reference, with param being a properly aligned non-null pointer to an
+    // initialized instance.
+    let request_fd_ptr = param as *mut RequestFd;
+    let request_fd = request_fd_ptr.as_mut().unwrap();
+    request_fd().unwrap_or(-1)
+}
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index a988e39..6c9c28a 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -192,6 +192,7 @@
         "BitTube.cpp",
         "BLASTBufferQueue.cpp",
         "BufferItemConsumer.cpp",
+        "Choreographer.cpp",
         "CompositorTiming.cpp",
         "ConsumerBase.cpp",
         "CpuConsumer.cpp",
@@ -234,6 +235,7 @@
 
     export_header_lib_headers: [
         "libgui_aidl_headers",
+        "jni_headers",
     ],
 
     aidl: {
@@ -241,6 +243,7 @@
     },
 
     header_libs: [
+        "jni_headers",
         "libdvr_headers",
         "libgui_aidl_headers",
         "libpdx_headers",
diff --git a/libs/nativedisplay/Choreographer.cpp b/libs/gui/Choreographer.cpp
similarity index 99%
rename from libs/nativedisplay/Choreographer.cpp
rename to libs/gui/Choreographer.cpp
index 01e9f04..6b25b26 100644
--- a/libs/nativedisplay/Choreographer.cpp
+++ b/libs/gui/Choreographer.cpp
@@ -16,8 +16,8 @@
 
 // #define LOG_NDEBUG 0
 
+#include <gui/Choreographer.h>
 #include <jni.h>
-#include <nativedisplay/Choreographer.h>
 
 #undef LOG_TAG
 #define LOG_TAG "AChoreographer"
diff --git a/libs/gui/TEST_MAPPING b/libs/gui/TEST_MAPPING
index 1c43530..9415035 100644
--- a/libs/gui/TEST_MAPPING
+++ b/libs/gui/TEST_MAPPING
@@ -3,5 +3,11 @@
     {
       "path": "frameworks/native/libs/nativewindow"
     }
+  ],
+  "postsubmit": [
+    {
+      // TODO(257123981): move this to presubmit after dealing with existing breakages.
+      "name": "libgui_test"
+    }
   ]
 }
diff --git a/libs/nativedisplay/include/nativedisplay/Choreographer.h b/libs/gui/include/gui/Choreographer.h
similarity index 98%
rename from libs/nativedisplay/include/nativedisplay/Choreographer.h
rename to libs/gui/include/gui/Choreographer.h
index bb63f29..89a7058 100644
--- a/libs/nativedisplay/include/nativedisplay/Choreographer.h
+++ b/libs/gui/include/gui/Choreographer.h
@@ -16,8 +16,9 @@
 
 #pragma once
 
+#include <android/choreographer.h>
 #include <gui/DisplayEventDispatcher.h>
-#include <private/android/choreographer.h>
+#include <jni.h>
 #include <utils/Looper.h>
 
 #include <mutex>
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index d70a7f0..06a246e 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -103,7 +103,7 @@
         // (sf vsync offset - debug.sf.early_phase_offset_ns). SurfaceFlinger will continue to be
         // in the early configuration until it receives eEarlyWakeupEnd. These flags are
         // expected to be used by WindowManager only and are guarded by
-        // android.permission.ACCESS_SURFACE_FLINGER
+        // android.permission.WAKEUP_SURFACE_FLINGER
         eEarlyWakeupStart = 0x08,
         eEarlyWakeupEnd = 0x10,
         eOneWay = 0x20
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index 4751a7d..fb6c590 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -179,6 +179,7 @@
         mIsExternal(other.mIsExternal),
         mHasMic(other.mHasMic),
         mCountryCode(other.mCountryCode),
+        mKeyboardLayoutInfo(other.mKeyboardLayoutInfo),
         mSources(other.mSources),
         mKeyboardType(other.mKeyboardType),
         mKeyCharacterMap(other.mKeyCharacterMap),
@@ -270,6 +271,10 @@
     mKeyboardType = std::max(mKeyboardType, keyboardType);
 }
 
+void InputDeviceInfo::setKeyboardLayoutInfo(KeyboardLayoutInfo layoutInfo) {
+    mKeyboardLayoutInfo = std::move(layoutInfo);
+}
+
 std::vector<InputDeviceSensorInfo> InputDeviceInfo::getSensors() {
     std::vector<InputDeviceSensorInfo> infos;
     infos.reserve(mSensors.size());
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
index 5c9c8b6..39c79c9 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
@@ -47,7 +47,7 @@
      */
     void* getDecompressedImagePtr();
     /*
-     * Returns the decompressed raw image buffer size. This method must be called only after
+     * Returns the decompressed raw image buffer size. This mgit ethod must be called only after
      * calling decompressImage().
      */
     size_t getDecompressedImageSize();
@@ -67,14 +67,35 @@
     void* getXMPPtr();
     /*
      * Returns the decompressed XMP buffer size. This method must be called only after
-     * calling decompressImage().
+     * calling decompressImage() or getCompressedImageParameters().
      */
     size_t getXMPSize();
-
+    /*
+     * Returns the EXIF data from the image.
+     */
+    void* getEXIFPtr();
+    /*
+     * Returns the decompressed EXIF buffer size. This method must be called only after
+     * calling decompressImage() or getCompressedImageParameters().
+     */
+    size_t getEXIFSize();
+    /*
+     * Returns the position offset of EXIF package
+     * (4 bypes offset to FF sign, the byte after FF E1 XX XX <this byte>),
+     * or -1  if no EXIF exists.
+     */
+    int getEXIFPos() { return mExifPos; }
+    /*
+     * Decompresses metadata of the image.
+     */
     bool getCompressedImageParameters(const void* image, int length,
-                              size_t* pWidth, size_t* pHeight,
-                              std::vector<uint8_t>* &iccData,
-                              std::vector<uint8_t>* &exifData);
+                                      size_t* pWidth, size_t* pHeight,
+                                      std::vector<uint8_t>* &iccData,
+                                      std::vector<uint8_t>* &exifData);
+    /*
+     * Extracts EXIF package and updates the EXIF position / length without decoding the image.
+     */
+    bool extractEXIF(const void* image, int length);
 
 private:
     bool decode(const void* image, int length);
@@ -89,10 +110,14 @@
     std::vector<JOCTET> mResultBuffer;
     // The buffer that holds XMP Data.
     std::vector<JOCTET> mXMPBuffer;
+    // The buffer that holds EXIF Data.
+    std::vector<JOCTET> mEXIFBuffer;
 
     // Resolution of the decompressed image.
     size_t mWidth;
     size_t mHeight;
+    // Position of EXIF package, default value is -1 which means no EXIF package appears.
+    size_t mExifPos;
 };
 } /* namespace android  */
 
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
index 50ccdff..905bf16 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
@@ -325,12 +325,14 @@
      *
      * @param compressed_jpeg_image compressed 8-bit JPEG image
      * @param compress_recovery_map compressed recover map
+     * @param exif EXIF package
      * @param metadata JPEG/R metadata to encode in XMP of the jpeg
      * @param dest compressed JPEGR image
      * @return NO_ERROR if calculation succeeds, error code if error occurs.
      */
     status_t appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
                                jr_compressed_ptr compressed_recovery_map,
+                               jr_exif_ptr exif,
                                jr_metadata_ptr metadata,
                                jr_compressed_ptr dest);
 
diff --git a/libs/jpegrecoverymap/jpegdecoder.cpp b/libs/jpegrecoverymap/jpegdecoder.cpp
index 0185e55..c2a8f45 100644
--- a/libs/jpegrecoverymap/jpegdecoder.cpp
+++ b/libs/jpegrecoverymap/jpegdecoder.cpp
@@ -26,8 +26,12 @@
 
 namespace android::recoverymap {
 
-const uint32_t kExifMarker = JPEG_APP0 + 1;
-const uint32_t kICCMarker = JPEG_APP0 + 2;
+const uint32_t kAPP0Marker = JPEG_APP0;      // JFIF
+const uint32_t kAPP1Marker = JPEG_APP0 + 1;  // EXIF, XMP
+const uint32_t kAPP2Marker = JPEG_APP0 + 2;  // ICC
+
+const std::string kXmpNameSpace = "http://ns.adobe.com/xap/1.0/";
+const std::string kExifIdCode = "Exif";
 
 struct jpegr_source_mgr : jpeg_source_mgr {
     jpegr_source_mgr(const uint8_t* ptr, int len);
@@ -83,6 +87,7 @@
 }
 
 JpegDecoder::JpegDecoder() {
+  mExifPos = 0;
 }
 
 JpegDecoder::~JpegDecoder() {
@@ -119,6 +124,13 @@
     return mXMPBuffer.size();
 }
 
+void* JpegDecoder::getEXIFPtr() {
+    return mEXIFBuffer.data();
+}
+
+size_t JpegDecoder::getEXIFSize() {
+    return mEXIFBuffer.size();
+}
 
 size_t JpegDecoder::getDecompressedImageWidth() {
     return mWidth;
@@ -132,7 +144,6 @@
     jpeg_decompress_struct cinfo;
     jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
     jpegrerror_mgr myerr;
-    string nameSpace = "http://ns.adobe.com/xap/1.0/";
 
     cinfo.err = jpeg_std_error(&myerr.pub);
     myerr.pub.error_exit = jpegrerror_exit;
@@ -143,26 +154,59 @@
     }
     jpeg_create_decompress(&cinfo);
 
-    jpeg_save_markers(&cinfo, kExifMarker, 0xFFFF);
+    jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF);
+    jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
+    jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
 
     cinfo.src = &mgr;
     jpeg_read_header(&cinfo, TRUE);
 
-    // Save XMP Data
-    for (jpeg_marker_struct* marker = cinfo.marker_list; marker; marker = marker->next) {
-        if (marker->marker == kExifMarker) {
-            const unsigned int len = marker->data_length;
-            if (len > nameSpace.size() &&
-                !strncmp(reinterpret_cast<const char*>(marker->data),
-                         nameSpace.c_str(), nameSpace.size())) {
-                mXMPBuffer.resize(len+1, 0);
-                memcpy(static_cast<void*>(mXMPBuffer.data()), marker->data, len);
-                break;
-            }
+    // Save XMP data and EXIF data.
+    // Here we only handle the first XMP / EXIF package.
+    // The parameter pos is used for capturing start offset of EXIF, which is hacky, but working...
+    // We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package),
+    // two bytes of package length which is stored in marker->original_length, and the real data
+    // which is stored in marker->data. The pos is adding up all previous package lengths (
+    // 4 bytes marker and length, marker->original_length) before EXIF appears. Note that here we
+    // we are using marker->original_length instead of marker->data_length because in case the real
+    // package length is larger than the limitation, jpeg-turbo will only copy the data within the
+    // limitation (represented by data_length) and this may vary from original_length / real offset.
+    // A better solution is making jpeg_marker_struct holding the offset, but currently it doesn't.
+    bool exifAppears = false;
+    bool xmpAppears = false;
+    size_t pos = 2;  // position after SOI
+    for (jpeg_marker_struct* marker = cinfo.marker_list;
+         marker && !(exifAppears && xmpAppears);
+         marker = marker->next) {
+
+        pos += 4;
+        pos += marker->original_length;
+
+        if (marker->marker != kAPP1Marker) {
+            continue;
+        }
+
+        const unsigned int len = marker->data_length;
+        if (!xmpAppears &&
+            len > kXmpNameSpace.size() &&
+            !strncmp(reinterpret_cast<const char*>(marker->data),
+                     kXmpNameSpace.c_str(),
+                     kXmpNameSpace.size())) {
+            mXMPBuffer.resize(len+1, 0);
+            memcpy(static_cast<void*>(mXMPBuffer.data()), marker->data, len);
+            xmpAppears = true;
+        } else if (!exifAppears &&
+                   len > kExifIdCode.size() &&
+                   !strncmp(reinterpret_cast<const char*>(marker->data),
+                            kExifIdCode.c_str(),
+                            kExifIdCode.size())) {
+            mEXIFBuffer.resize(len, 0);
+            memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
+            exifAppears = true;
+            mExifPos = pos - marker->original_length;
         }
     }
 
-
     mWidth = cinfo.image_width;
     mHeight = cinfo.image_height;
 
@@ -189,6 +233,60 @@
     return true;
 }
 
+// TODO (Fyodor/Dichen): merge this method with getCompressedImageParameters() since they have
+// similar functionality. Yet Dichen is not familiar with who's calling
+// getCompressedImageParameters(), looks like it's used by some pending CLs.
+bool JpegDecoder::extractEXIF(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);
+
+    jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF);
+    jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
+    jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
+
+    cinfo.src = &mgr;
+    jpeg_read_header(&cinfo, TRUE);
+
+    bool exifAppears = false;
+    size_t pos = 2;  // position after SOI
+    for (jpeg_marker_struct* marker = cinfo.marker_list;
+         marker && !exifAppears;
+         marker = marker->next) {
+
+        pos += 4;
+        pos += marker->original_length;
+
+        if (marker->marker != kAPP1Marker) {
+            continue;
+        }
+
+        const unsigned int len = marker->data_length;
+        if (!exifAppears &&
+            len > kExifIdCode.size() &&
+            !strncmp(reinterpret_cast<const char*>(marker->data),
+                     kExifIdCode.c_str(),
+                     kExifIdCode.size())) {
+            mEXIFBuffer.resize(len, 0);
+            memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
+            exifAppears = true;
+            mExifPos = pos - marker->original_length;
+        }
+    }
+
+    jpeg_destroy_decompress(&cinfo);
+    return true;
+}
+
 bool JpegDecoder::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest,
         bool isSingleChannel) {
     if (isSingleChannel) {
@@ -212,8 +310,8 @@
     }
     jpeg_create_decompress(&cinfo);
 
-    jpeg_save_markers(&cinfo, kExifMarker, 0xFFFF);
-    jpeg_save_markers(&cinfo, kICCMarker, 0xFFFF);
+    jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
+    jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
 
     cinfo.src = &mgr;
     if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp
index fafc319..53fa8ce 100644
--- a/libs/jpegrecoverymap/recoverymap.cpp
+++ b/libs/jpegrecoverymap/recoverymap.cpp
@@ -82,12 +82,122 @@
   return NO_ERROR;
 }
 
+status_t Write(jr_exif_ptr destination, const void* source, size_t length, int &position) {
+  memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
+  position += length;
+  return NO_ERROR;
+}
+
+// If the EXIF package doesn't exist in the input JPEG, we'll create one with one entry
+// where the length is represented by this value.
+const size_t PSEUDO_EXIF_PACKAGE_LENGTH = 28;
+// If the EXIF package exists in the input JPEG, we'll add an "JR" entry where the length is
+// represented by this value.
+const size_t EXIF_J_R_ENTRY_LENGTH = 12;
+
+/*
+ * Helper function
+ * Add J R entry to existing exif, or create a new one with J R entry if it's null.
+ * EXIF syntax / change:
+ * ori:
+ * FF E1 - APP1
+ * 01 FC - size of APP1 (to be calculated)
+ * -----------------------------------------------------
+ * 45 78 69 66 00 00 - Exif\0\0 "Exif header"
+ * 49 49 2A 00 - TIFF Header
+ * 08 00 00 00 - offset to the IFD (image file directory)
+ * 06 00 - 6 entries
+ * 00 01 - Width Tag
+ * 03 00 - 'Short' type
+ * 01 00 00 00 - one entry
+ * 00 05 00 00 - image with 0x500
+ *--------------------------------------------------------------------------
+ * new:
+ * FF E1 - APP1
+ * 02 08 - new size, equals to old size + EXIF_J_R_ENTRY_LENGTH (12)
+ *-----------------------------------------------------
+ * 45 78 69 66 00 00 - Exif\0\0 "Exif header"
+ * 49 49 2A 00 - TIFF Header
+ * 08 00 00 00 - offset to the IFD (image file directory)
+ * 07 00 - +1 entry
+ * 4A 52   Custom ('J''R') Tag
+ * 07 00 - Unknown type
+ * 01 00 00 00 - one element
+ * 00 00 00 00 - empty data
+ * 00 01 - Width Tag
+ * 03 00 - 'Short' type
+ * 01 00 00 00 - one entry
+ * 00 05 00 00 - image with 0x500
+ */
+status_t updateExif(jr_exif_ptr exif, jr_exif_ptr dest) {
+  if (exif == nullptr || exif->data == nullptr) {
+    uint8_t data[PSEUDO_EXIF_PACKAGE_LENGTH] = {
+        0x45, 0x78, 0x69, 0x66, 0x00, 0x00,
+        0x49, 0x49, 0x2A, 0x00,
+        0x08, 0x00, 0x00, 0x00,
+        0x01, 0x00,
+        0x4A, 0x52,
+        0x07, 0x00,
+        0x01, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00};
+    int pos = 0;
+    Write(dest, data, PSEUDO_EXIF_PACKAGE_LENGTH, pos);
+    return NO_ERROR;
+  }
+
+  int num_entry = 0;
+  uint8_t num_entry_low = 0;
+  uint8_t num_entry_high = 0;
+  bool use_big_endian = false;
+  if (reinterpret_cast<uint16_t*>(exif->data)[3] == 0x4949) {
+      num_entry_low = reinterpret_cast<uint8_t*>(exif->data)[14];
+      num_entry_high = reinterpret_cast<uint8_t*>(exif->data)[15];
+  } else if (reinterpret_cast<uint16_t*>(exif->data)[3] == 0x4d4d) {
+      use_big_endian = true;
+      num_entry_high = reinterpret_cast<uint8_t*>(exif->data)[14];
+      num_entry_low = reinterpret_cast<uint8_t*>(exif->data)[15];
+  } else {
+      return ERROR_JPEGR_METADATA_ERROR;
+  }
+  num_entry = (num_entry_high << 8) | num_entry_low;
+  num_entry += 1;
+  num_entry_low = num_entry & 0xff;
+  num_entry_high = (num_entry << 8) & 0xff;
+
+  int pos = 0;
+  Write(dest, (uint8_t*)exif->data, 14, pos);
+
+  if (use_big_endian) {
+    Write(dest, &num_entry_high, 1, pos);
+    Write(dest, &num_entry_low, 1, pos);
+    uint8_t data[EXIF_J_R_ENTRY_LENGTH] = {
+          0x4A, 0x52,
+          0x07, 0x00,
+          0x01, 0x00, 0x00, 0x00,
+          0x00, 0x00, 0x00, 0x00};
+    Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos);
+  } else {
+    Write(dest, &num_entry_low, 1, pos);
+    Write(dest, &num_entry_high, 1, pos);
+    uint8_t data[EXIF_J_R_ENTRY_LENGTH] = {
+          0x4A, 0x52,
+          0x00, 0x07,
+          0x00, 0x00, 0x00, 0x01,
+          0x00, 0x00, 0x00, 0x00};
+    Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos);
+  }
+
+  Write(dest, (uint8_t*)exif->data + 16, exif->length - 16, pos);
+
+  return NO_ERROR;
+}
+
 /* Encode API-0 */
 status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
                                   jpegr_transfer_function hdr_tf,
                                   jr_compressed_ptr dest,
                                   int quality,
-                                  jr_exif_ptr /* exif */) {
+                                  jr_exif_ptr exif) {
   if (uncompressed_p010_image == nullptr || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
@@ -129,7 +239,18 @@
   jpeg.data = jpeg_encoder.getCompressedImagePtr();
   jpeg.length = jpeg_encoder.getCompressedImageSize();
 
-  JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &metadata, dest));
+  jpegr_exif_struct new_exif;
+  if (exif->data == nullptr) {
+      new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
+  } else {
+      new_exif.length = exif->length + EXIF_J_R_ENTRY_LENGTH;
+  }
+  new_exif.data = new uint8_t[new_exif.length];
+  std::unique_ptr<uint8_t[]> new_exif_data;
+  new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
+  JPEGR_CHECK(updateExif(exif, &new_exif));
+
+  JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &new_exif, &metadata, dest));
 
   return NO_ERROR;
 }
@@ -140,7 +261,7 @@
                                   jpegr_transfer_function hdr_tf,
                                   jr_compressed_ptr dest,
                                   int quality,
-                                  jr_exif_ptr /* exif */) {
+                                  jr_exif_ptr exif) {
   if (uncompressed_p010_image == nullptr
    || uncompressed_yuv_420_image == nullptr
    || dest == nullptr) {
@@ -186,7 +307,19 @@
   jpeg.data = jpeg_encoder.getCompressedImagePtr();
   jpeg.length = jpeg_encoder.getCompressedImageSize();
 
-  JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &metadata, dest));
+  jpegr_exif_struct new_exif;
+  if (exif == nullptr || exif->data == nullptr) {
+      new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
+  } else {
+      new_exif.length = exif->length + EXIF_J_R_ENTRY_LENGTH;
+  }
+
+  new_exif.data = new uint8_t[new_exif.length];
+  std::unique_ptr<uint8_t[]> new_exif_data;
+  new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
+  JPEGR_CHECK(updateExif(exif, &new_exif));
+
+  JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &new_exif, &metadata, dest));
 
   return NO_ERROR;
 }
@@ -228,7 +361,41 @@
   compressed_map.data = compressed_map_data.get();
   JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
 
-  JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
+  // Extract EXIF from JPEG without decoding.
+  JpegDecoder jpeg_decoder;
+  if (!jpeg_decoder.extractEXIF(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
+    return ERROR_JPEGR_DECODE_ERROR;
+  }
+
+  jpegr_exif_struct exif;
+  exif.data = nullptr;
+  exif.length = 0;
+  // Delete EXIF package if it appears, and update exif.
+  if (jpeg_decoder.getEXIFPos() != 0) {
+    int new_length = compressed_jpeg_image->length - jpeg_decoder.getEXIFSize() - 4;
+    memcpy((uint8_t*)compressed_jpeg_image->data + jpeg_decoder.getEXIFPos() - 4,
+           (uint8_t*)compressed_jpeg_image->data + jpeg_decoder.getEXIFPos()
+                  + jpeg_decoder.getEXIFSize(),
+           compressed_jpeg_image->length - jpeg_decoder.getEXIFPos() - jpeg_decoder.getEXIFSize());
+    compressed_jpeg_image->length = new_length;
+    exif.data = jpeg_decoder.getEXIFPtr();
+    exif.length = jpeg_decoder.getEXIFSize();
+  }
+
+  jpegr_exif_struct new_exif;
+  if (exif.data == nullptr) {
+      new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
+  } else {
+      new_exif.length = exif.length + EXIF_J_R_ENTRY_LENGTH;
+  }
+
+  new_exif.data = new uint8_t[new_exif.length];
+  std::unique_ptr<uint8_t[]> new_exif_data;
+  new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
+  JPEGR_CHECK(updateExif(&exif, &new_exif));
+
+  JPEGR_CHECK(appendRecoveryMap(
+          compressed_jpeg_image, &compressed_map, &new_exif, &metadata, dest));
 
   return NO_ERROR;
 }
@@ -254,6 +421,32 @@
   uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
   uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut;
 
+  jpegr_exif_struct exif;
+  exif.data = nullptr;
+  exif.length = 0;
+  // Delete EXIF package if it appears, and update exif.
+  if (jpeg_decoder.getEXIFPos() != 0) {
+    int new_length = compressed_jpeg_image->length - jpeg_decoder.getEXIFSize() - 4;
+    memcpy((uint8_t*)compressed_jpeg_image->data + jpeg_decoder.getEXIFPos() - 4,
+           (uint8_t*)compressed_jpeg_image->data + jpeg_decoder.getEXIFPos()
+                  + jpeg_decoder.getEXIFSize(),
+           compressed_jpeg_image->length - jpeg_decoder.getEXIFPos() - jpeg_decoder.getEXIFSize());
+    compressed_jpeg_image->length = new_length;
+    exif.data = jpeg_decoder.getEXIFPtr();
+    exif.length = jpeg_decoder.getEXIFSize();
+  }
+
+  jpegr_exif_struct new_exif;
+  if (exif.data == nullptr) {
+      new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
+  } else {
+      new_exif.length = exif.length + EXIF_J_R_ENTRY_LENGTH;
+  }
+  new_exif.data = new uint8_t[new_exif.length];
+  std::unique_ptr<uint8_t[]> new_exif_data;
+  new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
+  JPEGR_CHECK(updateExif(&exif, &new_exif));
+
   if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
    || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
     return ERROR_JPEGR_RESOLUTION_MISMATCH;
@@ -278,7 +471,8 @@
   compressed_map.data = compressed_map_data.get();
   JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
 
-  JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
+  JPEGR_CHECK(appendRecoveryMap(
+          compressed_jpeg_image, &compressed_map, &new_exif, &metadata, dest));
 
   return NO_ERROR;
 }
@@ -614,52 +808,84 @@
   return extractPrimaryImageAndRecoveryMap(compressed_jpegr_image, nullptr, dest);
 }
 
+// JPEG/R structure:
+// SOI (ff d8)
+// APP1 (ff e1)
+// 2 bytes of length (2 + length of exif package)
+// EXIF package (this includes the first two bytes representing the package length)
+// APP1 (ff e1)
+// 2 bytes of length (2 + 29 + length of xmp package)
+// name space ("http://ns.adobe.com/xap/1.0/\0")
+// xmp
+// primary image (without the first two bytes (SOI) and without EXIF, may have other packages)
+// secondary image (the recovery map)
+//
+// Metadata versions we are using:
+// ECMA TR-98 for JFIF marker
+// Exif 2.2 spec for EXIF marker
+// Adobe XMP spec part 3 for XMP marker
+// ICC v4.3 spec for ICC
 status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
                                         jr_compressed_ptr compressed_recovery_map,
+                                        jr_exif_ptr exif,
                                         jr_metadata_ptr metadata,
                                         jr_compressed_ptr dest) {
   if (compressed_jpeg_image == nullptr
    || compressed_recovery_map == nullptr
+   || exif == nullptr
    || metadata == nullptr
    || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
-  const string xmp = generateXmp(compressed_recovery_map->length, *metadata);
-  const string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
-  const int nameSpaceLength = nameSpace.size() + 1;  // need to count the null terminator
-
-  // 2 bytes: APP1 sign (ff e1)
-  // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0",
-  // x bytes: length of xmp packet
-
-  const int length = 3 + nameSpaceLength + xmp.size();
-  const uint8_t lengthH = ((length >> 8) & 0xff);
-  const uint8_t lengthL = (length & 0xff);
-
   int pos = 0;
 
-  // JPEG/R structure:
-  // SOI (ff d8)
-  // APP1 (ff e1)
-  // 2 bytes of length (2 + 29 + length of xmp packet)
-  // name space ("http://ns.adobe.com/xap/1.0/\0")
-  // xmp
-  // primary image (without the first two bytes, the SOI sign)
-  // secondary image (the recovery map)
+  // Write SOI
   JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
   JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
-  JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
-  JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
-  JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
-  JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
-  JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
-  JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos));
+
+  // Write EXIF
+  {
+    const int length = 2 + exif->length;
+    const uint8_t lengthH = ((length >> 8) & 0xff);
+    const uint8_t lengthL = (length & 0xff);
+    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
+    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
+    JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
+    JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
+    JPEGR_CHECK(Write(dest, exif->data, exif->length, pos));
+  }
+
+  // Prepare and write XMP
+  {
+    const string xmp = generateXmp(compressed_recovery_map->length, *metadata);
+    const string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
+    const int nameSpaceLength = nameSpace.size() + 1;  // need to count the null terminator
+    // 2 bytes: representing the length of the package
+    // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0",
+    // x bytes: length of xmp packet
+    const int length = 3 + nameSpaceLength + xmp.size();
+    const uint8_t lengthH = ((length >> 8) & 0xff);
+    const uint8_t lengthL = (length & 0xff);
+    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
+    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
+    JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
+    JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
+    JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
+    JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos));
+  }
+
+  // Write primary image
   JPEGR_CHECK(Write(dest,
       (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
+
+  // Write secondary image
   JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos));
+
+  // Set back length
   dest->length = pos;
 
+  // Done!
   return NO_ERROR;
 }
 
diff --git a/libs/jpegrecoverymap/tests/data/raw_yuv420_image.yuv420 b/libs/jpegrecoverymap/tests/data/raw_yuv420_image.yuv420
new file mode 100644
index 0000000..c043da6
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/data/raw_yuv420_image.yuv420
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
index 6dea27f..c3c6fd4 100644
--- a/libs/jpegrecoverymap/tests/recoverymap_test.cpp
+++ b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
@@ -22,9 +22,11 @@
 #include <utils/Log.h>
 
 #define RAW_P010_IMAGE "/sdcard/Documents/raw_p010_image.p010"
-#define RAW_P010_IMAGE_WIDTH 1280
-#define RAW_P010_IMAGE_HEIGHT 720
+#define RAW_YUV420_IMAGE "/sdcard/Documents/raw_yuv420_image.yuv420"
 #define JPEG_IMAGE "/sdcard/Documents/jpeg_image.jpg"
+#define TEST_IMAGE_WIDTH 1280
+#define TEST_IMAGE_HEIGHT 720
+#define DEFAULT_JPEG_QUALITY 90
 
 #define SAVE_ENCODING_RESULT true
 #define SAVE_DECODING_RESULT true
@@ -40,6 +42,7 @@
   virtual void TearDown();
 
   struct jpegr_uncompressed_struct mRawP010Image;
+  struct jpegr_uncompressed_struct mRawYuv420Image;
   struct jpegr_compressed_struct mJpegImage;
 };
 
@@ -49,6 +52,7 @@
 void RecoveryMapTest::SetUp() {}
 void RecoveryMapTest::TearDown() {
   free(mRawP010Image.data);
+  free(mRawYuv420Image.data);
   free(mJpegImage.data);
 }
 
@@ -108,24 +112,88 @@
   ASSERT_EQ(metadata_expected.rangeScalingFactor, metadata_read.rangeScalingFactor);
 
 }
-TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) {
+
+/* Test Encode API-0 and decode */
+// TODO: enable when tonemapper is ready.
+//TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) {
+//  int ret;
+//
+//  // Load input files.
+//  if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
+//    FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+//  }
+//  mRawP010Image.width = TEST_IMAGE_WIDTH;
+//  mRawP010Image.height = TEST_IMAGE_HEIGHT;
+//  mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
+//
+//  RecoveryMap recoveryMap;
+//
+//  jpegr_compressed_struct jpegR;
+//  jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
+//  jpegR.data = malloc(jpegR.maxLength);
+//  ret = recoveryMap.encodeJPEGR(
+//      &mRawP010Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR, 90, nullptr);
+//  if (ret != OK) {
+//    FAIL() << "Error code is " << ret;
+//  }
+//  if (SAVE_ENCODING_RESULT) {
+//    // Output image data to file
+//    std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr";
+//    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
+//    if (!imageFile.is_open()) {
+//      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
+//    }
+//    imageFile.write((const char*)jpegR.data, jpegR.length);
+//  }
+//
+//  jpegr_uncompressed_struct decodedJpegR;
+//  int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
+//  decodedJpegR.data = malloc(decodedJpegRSize);
+//  ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR);
+//  if (ret != OK) {
+//    FAIL() << "Error code is " << ret;
+//  }
+//  if (SAVE_DECODING_RESULT) {
+//    // Output image data to file
+//    std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10";
+//    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
+//    if (!imageFile.is_open()) {
+//      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
+//    }
+//    imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize);
+//  }
+//
+//  free(jpegR.data);
+//  free(decodedJpegR.data);
+//}
+
+/* Test Encode API-1 and decode */
+TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrThenDecode) {
   int ret;
 
   // Load input files.
   if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
     FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
   }
-  mRawP010Image.width = RAW_P010_IMAGE_WIDTH;
-  mRawP010Image.height = RAW_P010_IMAGE_HEIGHT;
+  mRawP010Image.width = TEST_IMAGE_WIDTH;
+  mRawP010Image.height = TEST_IMAGE_HEIGHT;
   mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
 
+  if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) {
+    FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+  }
+  mRawYuv420Image.width = TEST_IMAGE_WIDTH;
+  mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
+  mRawYuv420Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
+
   RecoveryMap recoveryMap;
 
   jpegr_compressed_struct jpegR;
-  jpegR.maxLength = RAW_P010_IMAGE_WIDTH * RAW_P010_IMAGE_HEIGHT * sizeof(uint8_t);
+  jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
   jpegR.data = malloc(jpegR.maxLength);
   ret = recoveryMap.encodeJPEGR(
-      &mRawP010Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR, 90, nullptr);
+      &mRawP010Image, &mRawYuv420Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR,
+      DEFAULT_JPEG_QUALITY, nullptr);
   if (ret != OK) {
     FAIL() << "Error code is " << ret;
   }
@@ -140,7 +208,7 @@
   }
 
   jpegr_uncompressed_struct decodedJpegR;
-  int decodedJpegRSize = RAW_P010_IMAGE_WIDTH * RAW_P010_IMAGE_HEIGHT * 4;
+  int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
   decodedJpegR.data = malloc(decodedJpegRSize);
   ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR);
   if (ret != OK) {
@@ -160,6 +228,72 @@
   free(decodedJpegR.data);
 }
 
+/* Test Encode API-2 and decode */
+TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrAndJpegThenDecode) {
+  int ret;
+
+  // Load input files.
+  if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
+    FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+  }
+  mRawP010Image.width = TEST_IMAGE_WIDTH;
+  mRawP010Image.height = TEST_IMAGE_HEIGHT;
+  mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
+
+  if (!loadFile(RAW_YUV420_IMAGE, mRawYuv420Image.data, nullptr)) {
+    FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+  }
+  mRawYuv420Image.width = TEST_IMAGE_WIDTH;
+  mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
+  mRawYuv420Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
+
+  if (!loadFile(JPEG_IMAGE, mJpegImage.data, &mJpegImage.length)) {
+    FAIL() << "Load file " << JPEG_IMAGE << " failed";
+  }
+  mJpegImage.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
+
+  RecoveryMap recoveryMap;
+
+  jpegr_compressed_struct jpegR;
+  jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
+  jpegR.data = malloc(jpegR.maxLength);
+  ret = recoveryMap.encodeJPEGR(
+      &mRawP010Image, &mRawYuv420Image, &mJpegImage, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR);
+  if (ret != OK) {
+    FAIL() << "Error code is " << ret;
+  }
+  if (SAVE_ENCODING_RESULT) {
+    // Output image data to file
+    std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr";
+    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
+    if (!imageFile.is_open()) {
+      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
+    }
+    imageFile.write((const char*)jpegR.data, jpegR.length);
+  }
+
+  jpegr_uncompressed_struct decodedJpegR;
+  int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
+  decodedJpegR.data = malloc(decodedJpegRSize);
+  ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR);
+  if (ret != OK) {
+    FAIL() << "Error code is " << ret;
+  }
+  if (SAVE_DECODING_RESULT) {
+    // Output image data to file
+    std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10";
+    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
+    if (!imageFile.is_open()) {
+      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
+    }
+    imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize);
+  }
+
+  free(jpegR.data);
+  free(decodedJpegR.data);
+}
+
+/* Test Encode API-3 and decode */
 TEST_F(RecoveryMapTest, encodeFromJpegThenDecode) {
   int ret;
 
@@ -167,8 +301,8 @@
   if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
     FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
   }
-  mRawP010Image.width = RAW_P010_IMAGE_WIDTH;
-  mRawP010Image.height = RAW_P010_IMAGE_HEIGHT;
+  mRawP010Image.width = TEST_IMAGE_WIDTH;
+  mRawP010Image.height = TEST_IMAGE_HEIGHT;
   mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
 
   if (!loadFile(JPEG_IMAGE, mJpegImage.data, &mJpegImage.length)) {
@@ -179,7 +313,7 @@
   RecoveryMap recoveryMap;
 
   jpegr_compressed_struct jpegR;
-  jpegR.maxLength = RAW_P010_IMAGE_WIDTH * RAW_P010_IMAGE_HEIGHT * sizeof(uint8_t);
+  jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
   jpegR.data = malloc(jpegR.maxLength);
   ret = recoveryMap.encodeJPEGR(
       &mRawP010Image, &mJpegImage, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR);
@@ -197,7 +331,7 @@
   }
 
   jpegr_uncompressed_struct decodedJpegR;
-  int decodedJpegRSize = RAW_P010_IMAGE_WIDTH * RAW_P010_IMAGE_HEIGHT * 4;
+  int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
   decodedJpegR.data = malloc(decodedJpegRSize);
   ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR);
   if (ret != OK) {
diff --git a/libs/nativedisplay/AChoreographer.cpp b/libs/nativedisplay/AChoreographer.cpp
index e64165f..66a40f1 100644
--- a/libs/nativedisplay/AChoreographer.cpp
+++ b/libs/nativedisplay/AChoreographer.cpp
@@ -16,8 +16,8 @@
 
 #include <android-base/thread_annotations.h>
 #include <android/gui/ISurfaceComposer.h>
+#include <gui/Choreographer.h>
 #include <jni.h>
-#include <nativedisplay/Choreographer.h>
 #include <private/android/choreographer.h>
 #include <utils/Looper.h>
 #include <utils/Timers.h>
diff --git a/libs/nativedisplay/Android.bp b/libs/nativedisplay/Android.bp
index 70de33d..8d8a2bc 100644
--- a/libs/nativedisplay/Android.bp
+++ b/libs/nativedisplay/Android.bp
@@ -56,7 +56,6 @@
         ":libgui_frame_event_aidl",
         "AChoreographer.cpp",
         "ADisplay.cpp",
-        "Choreographer.cpp",
         "surfacetexture/surface_texture.cpp",
         "surfacetexture/SurfaceTexture.cpp",
         "surfacetexture/ImageConsumer.cpp",
diff --git a/libs/nativewindow/AHardwareBuffer.cpp b/libs/nativewindow/AHardwareBuffer.cpp
index bbafbff..cf927db 100644
--- a/libs/nativewindow/AHardwareBuffer.cpp
+++ b/libs/nativewindow/AHardwareBuffer.cpp
@@ -617,15 +617,27 @@
     static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::R_8) ==
                           AHARDWAREBUFFER_FORMAT_R8_UNORM,
             "HAL and AHardwareBuffer pixel format don't match");
+    static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::R_16_UINT) ==
+                          AHARDWAREBUFFER_FORMAT_R16_UINT,
+            "HAL and AHardwareBuffer pixel format don't match");
+    static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::RG_1616_UINT) ==
+                          AHARDWAREBUFFER_FORMAT_R16G16_UINT,
+            "HAL and AHardwareBuffer pixel format don't match");
+    static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::RGBA_10101010) ==
+                          AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM,
+            "HAL and AHardwareBuffer pixel format don't match");
 
     switch (format) {
         case AHARDWAREBUFFER_FORMAT_R8_UNORM:
+        case AHARDWAREBUFFER_FORMAT_R16_UINT:
+        case AHARDWAREBUFFER_FORMAT_R16G16_UINT:
         case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
         case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
         case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
         case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
         case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
         case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
+        case AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM:
         case AHARDWAREBUFFER_FORMAT_BLOB:
         case AHARDWAREBUFFER_FORMAT_D16_UNORM:
         case AHARDWAREBUFFER_FORMAT_D24_UNORM:
@@ -677,6 +689,7 @@
           return 1;
       case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
       case AHARDWAREBUFFER_FORMAT_D16_UNORM:
+      case AHARDWAREBUFFER_FORMAT_R16_UINT:
           return 2;
       case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
       case AHARDWAREBUFFER_FORMAT_D24_UNORM:
@@ -686,8 +699,10 @@
       case AHARDWAREBUFFER_FORMAT_D32_FLOAT:
       case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
       case AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT:
+      case AHARDWAREBUFFER_FORMAT_R16G16_UINT:
           return 4;
       case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
+      case AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM:
           return 8;
       default:
           return 0;
diff --git a/libs/nativewindow/include/android/hardware_buffer.h b/libs/nativewindow/include/android/hardware_buffer.h
index c35507b..b2e8bea 100644
--- a/libs/nativewindow/include/android/hardware_buffer.h
+++ b/libs/nativewindow/include/android/hardware_buffer.h
@@ -173,6 +173,27 @@
      *   OpenGL ES: GR_GL_R8
      */
     AHARDWAREBUFFER_FORMAT_R8_UNORM                 = 0x38,
+
+    /**
+     * Corresponding formats:
+     *   Vulkan: VK_FORMAT_R16_UINT
+     *   OpenGL ES: GR_GL_R16UI
+     */
+    AHARDWAREBUFFER_FORMAT_R16_UINT                 = 0x39,
+
+    /**
+     * Corresponding formats:
+     *   Vulkan: VK_FORMAT_R16G16_UINT
+     *   OpenGL ES: GR_GL_RG16UI
+     */
+    AHARDWAREBUFFER_FORMAT_R16G16_UINT              = 0x3a,
+
+    /**
+     * Corresponding formats:
+     *   Vulkan: VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16
+     *   OpenGL ES: N/A
+     */
+    AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM       = 0x3b,
 };
 
 /**
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index 04e24ed..8d19c45 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -111,9 +111,23 @@
     ],
 }
 
+// Used to consolidate and simplify pulling Skia & Skia deps into targets that depend on
+// librenderengine. This allows shared deps to be deduplicated (e.g. Perfetto), which doesn't seem
+// possible if libskia_renderengine is just pulled into librenderengine via whole_static_libs.
+cc_defaults {
+    name: "librenderengine_deps",
+    defaults: ["skia_renderengine_deps"],
+    static_libs: ["libskia_renderengine"],
+}
+
+// Note: if compilation fails when adding librenderengine as a dependency, try adding
+// librenderengine_deps to the defaults field of your dependent target.
 cc_library_static {
     name: "librenderengine",
-    defaults: ["librenderengine_defaults"],
+    defaults: [
+        "librenderengine_defaults",
+        "librenderengine_deps",
+    ],
     double_loadable: true,
     cflags: [
         "-fvisibility=hidden",
@@ -132,7 +146,6 @@
     include_dirs: [
         "external/skia/src/gpu",
     ],
-    whole_static_libs: ["libskia_renderengine"],
     lto: {
         thin: true,
     },
diff --git a/libs/renderengine/benchmark/Android.bp b/libs/renderengine/benchmark/Android.bp
index afbe6cf..55c34cd 100644
--- a/libs/renderengine/benchmark/Android.bp
+++ b/libs/renderengine/benchmark/Android.bp
@@ -25,7 +25,7 @@
     name: "librenderengine_bench",
     defaults: [
         "android.hardware.graphics.composer3-ndk_shared",
-        "skia_deps",
+        "librenderengine_deps",
         "surfaceflinger_defaults",
     ],
     srcs: [
diff --git a/libs/renderengine/tests/Android.bp b/libs/renderengine/tests/Android.bp
index 6f328d7..50e166d 100644
--- a/libs/renderengine/tests/Android.bp
+++ b/libs/renderengine/tests/Android.bp
@@ -25,7 +25,7 @@
     name: "librenderengine_test",
     defaults: [
         "android.hardware.graphics.composer3-ndk_shared",
-        "skia_deps",
+        "librenderengine_deps",
         "surfaceflinger_defaults",
     ],
     test_suites: ["device-tests"],
diff --git a/libs/sensor/Sensor.cpp b/libs/sensor/Sensor.cpp
index ec0ced8..fb895f5 100644
--- a/libs/sensor/Sensor.cpp
+++ b/libs/sensor/Sensor.cpp
@@ -264,10 +264,6 @@
         mStringType = SENSOR_STRING_TYPE_HEART_BEAT;
         mFlags |= SENSOR_FLAG_SPECIAL_REPORTING_MODE;
         break;
-
-    // TODO:  Placeholder for LLOB sensor type
-
-
     case SENSOR_TYPE_ACCELEROMETER_UNCALIBRATED:
         mStringType = SENSOR_STRING_TYPE_ACCELEROMETER_UNCALIBRATED;
         mFlags |= SENSOR_FLAG_CONTINUOUS_MODE;
diff --git a/libs/ui/PublicFormat.cpp b/libs/ui/PublicFormat.cpp
index 78e82da..c9663ed 100644
--- a/libs/ui/PublicFormat.cpp
+++ b/libs/ui/PublicFormat.cpp
@@ -14,14 +14,15 @@
  * limitations under the License.
  */
 
-#include <ui/GraphicTypes.h> // ui::Dataspace
+#include "aidl/android/hardware/graphics/common/Dataspace.h"
 #include <ui/PublicFormat.h>
 
+
 // ----------------------------------------------------------------------------
 namespace android {
 // ----------------------------------------------------------------------------
 
-using ui::Dataspace;
+using ::aidl::android::hardware::graphics::common::Dataspace;
 
 int mapPublicFormatToHalFormat(PublicFormat f) {
     switch (f) {
@@ -29,6 +30,7 @@
         case PublicFormat::DEPTH_POINT_CLOUD:
         case PublicFormat::DEPTH_JPEG:
         case PublicFormat::HEIC:
+        case PublicFormat::JPEG_R:
             return HAL_PIXEL_FORMAT_BLOB;
         case PublicFormat::DEPTH16:
             return HAL_PIXEL_FORMAT_Y16;
@@ -47,7 +49,7 @@
     Dataspace dataspace;
     switch (f) {
         case PublicFormat::JPEG:
-            dataspace = Dataspace::V0_JFIF;
+            dataspace = Dataspace::JFIF;
             break;
         case PublicFormat::DEPTH_POINT_CLOUD:
         case PublicFormat::DEPTH16:
@@ -64,7 +66,7 @@
         case PublicFormat::YUV_420_888:
         case PublicFormat::NV21:
         case PublicFormat::YV12:
-            dataspace = Dataspace::V0_JFIF;
+            dataspace = Dataspace::JFIF;
             break;
         case PublicFormat::DEPTH_JPEG:
             dataspace = Dataspace::DYNAMIC_DEPTH;
@@ -72,6 +74,9 @@
         case PublicFormat::HEIC:
             dataspace = Dataspace::HEIF;
             break;
+        case PublicFormat::JPEG_R:
+            dataspace = Dataspace::JPEG_R;
+            break;
         default:
             // Most formats map to UNKNOWN
             dataspace = Dataspace::UNKNOWN;
@@ -139,14 +144,16 @@
             switch (ds) {
                 case Dataspace::DEPTH:
                     return PublicFormat::DEPTH_POINT_CLOUD;
-                case Dataspace::V0_JFIF:
+                case Dataspace::JFIF:
                     return PublicFormat::JPEG;
                 case Dataspace::HEIF:
                     return PublicFormat::HEIC;
                 default:
                     if (dataSpace == static_cast<android_dataspace>(HAL_DATASPACE_DYNAMIC_DEPTH)) {
                         return PublicFormat::DEPTH_JPEG;
-                    } else {
+                    } else if (dataSpace == static_cast<android_dataspace>(Dataspace::JPEG_R)) {
+                        return PublicFormat::JPEG_R;
+                    }else {
                         // Assume otherwise-marked blobs are also JPEG
                         return PublicFormat::JPEG;
                     }
diff --git a/libs/ui/include/ui/PixelFormat.h b/libs/ui/include/ui/PixelFormat.h
index f422ce4..cf5c2e8 100644
--- a/libs/ui/include/ui/PixelFormat.h
+++ b/libs/ui/include/ui/PixelFormat.h
@@ -53,16 +53,19 @@
 
     // real pixel formats supported for rendering -----------------------------
 
-    PIXEL_FORMAT_RGBA_8888    = HAL_PIXEL_FORMAT_RGBA_8888,    // 4x8-bit RGBA
-    PIXEL_FORMAT_RGBX_8888    = HAL_PIXEL_FORMAT_RGBX_8888,    // 4x8-bit RGB0
-    PIXEL_FORMAT_RGB_888      = HAL_PIXEL_FORMAT_RGB_888,      // 3x8-bit RGB
-    PIXEL_FORMAT_RGB_565      = HAL_PIXEL_FORMAT_RGB_565,      // 16-bit RGB
-    PIXEL_FORMAT_BGRA_8888    = HAL_PIXEL_FORMAT_BGRA_8888,    // 4x8-bit BGRA
-    PIXEL_FORMAT_RGBA_5551    = 6,                             // 16-bit ARGB
-    PIXEL_FORMAT_RGBA_4444    = 7,                             // 16-bit ARGB
-    PIXEL_FORMAT_RGBA_FP16    = HAL_PIXEL_FORMAT_RGBA_FP16,    // 64-bit RGBA
-    PIXEL_FORMAT_RGBA_1010102 = HAL_PIXEL_FORMAT_RGBA_1010102, // 32-bit RGBA
-    PIXEL_FORMAT_R_8          = 0x38,
+    PIXEL_FORMAT_RGBA_8888     = HAL_PIXEL_FORMAT_RGBA_8888,    // 4x8-bit RGBA
+    PIXEL_FORMAT_RGBX_8888     = HAL_PIXEL_FORMAT_RGBX_8888,    // 4x8-bit RGB0
+    PIXEL_FORMAT_RGB_888       = HAL_PIXEL_FORMAT_RGB_888,      // 3x8-bit RGB
+    PIXEL_FORMAT_RGB_565       = HAL_PIXEL_FORMAT_RGB_565,      // 16-bit RGB
+    PIXEL_FORMAT_BGRA_8888     = HAL_PIXEL_FORMAT_BGRA_8888,    // 4x8-bit BGRA
+    PIXEL_FORMAT_RGBA_5551     = 6,                             // 16-bit ARGB
+    PIXEL_FORMAT_RGBA_4444     = 7,                             // 16-bit ARGB
+    PIXEL_FORMAT_RGBA_FP16     = HAL_PIXEL_FORMAT_RGBA_FP16,    // 64-bit RGBA
+    PIXEL_FORMAT_RGBA_1010102  = HAL_PIXEL_FORMAT_RGBA_1010102, // 32-bit RGBA
+    PIXEL_FORMAT_R_8           = 0x38,
+    PIXEL_FORMAT_R_16_UINT     = 0x39,
+    PIXEL_FORMAT_RG_1616_UINT  = 0x3a,
+    PIXEL_FORMAT_RGBA_10101010 = 0x3b,
 };
 
 typedef int32_t PixelFormat;
diff --git a/libs/ui/include/ui/PublicFormat.h b/libs/ui/include/ui/PublicFormat.h
index aa58805..2248cca 100644
--- a/libs/ui/include/ui/PublicFormat.h
+++ b/libs/ui/include/ui/PublicFormat.h
@@ -57,6 +57,7 @@
     YCBCR_P010 = 0x36,
     DEPTH16 = 0x44363159,
     DEPTH_JPEG = 0x69656963,
+    JPEG_R = 0x1005,
     HEIC = 0x48454946,
 };
 
diff --git a/libs/ui/include_types/ui/DataspaceUtils.h b/libs/ui/include_types/ui/DataspaceUtils.h
index a461cb4..cd31167 100644
--- a/libs/ui/include_types/ui/DataspaceUtils.h
+++ b/libs/ui/include_types/ui/DataspaceUtils.h
@@ -22,8 +22,10 @@
 
 inline bool isHdrDataspace(ui::Dataspace dataspace) {
     const auto transfer = dataspace & HAL_DATASPACE_TRANSFER_MASK;
+    const auto range = dataspace & HAL_DATASPACE_RANGE_MASK;
 
-    return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG;
+    return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG ||
+            range == HAL_DATASPACE_RANGE_EXTENDED;
 }
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/libs/ui/tests/DataspaceUtils_test.cpp b/libs/ui/tests/DataspaceUtils_test.cpp
index 3e09671..ffe6438 100644
--- a/libs/ui/tests/DataspaceUtils_test.cpp
+++ b/libs/ui/tests/DataspaceUtils_test.cpp
@@ -29,12 +29,13 @@
     EXPECT_TRUE(isHdrDataspace(ui::Dataspace::BT2020_ITU_PQ));
     EXPECT_TRUE(isHdrDataspace(ui::Dataspace::BT2020_PQ));
     EXPECT_TRUE(isHdrDataspace(ui::Dataspace::BT2020_HLG));
+    // The original formulation of scRGB indicates the same white points as that
+    // of sRGB, however scRGB may be used to implement HDR.
+    EXPECT_TRUE(isHdrDataspace(ui::Dataspace::V0_SCRGB_LINEAR));
+    EXPECT_TRUE(isHdrDataspace(ui::Dataspace::V0_SCRGB));
 
     EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SRGB_LINEAR));
-    // scRGB defines a very wide gamut but not an expanded luminance range
-    EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SCRGB_LINEAR));
     EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SRGB));
-    EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SCRGB));
     EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_JFIF));
     EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_BT601_625));
     EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_BT601_525));
diff --git a/opengl/TEST_MAPPING b/opengl/TEST_MAPPING
index d391dce..7c50a94 100644
--- a/opengl/TEST_MAPPING
+++ b/opengl/TEST_MAPPING
@@ -2,6 +2,9 @@
   "presubmit": [
     {
       "name": "CtsGpuToolsHostTestCases"
+    },
+    {
+      "name": "EGL_test"
     }
   ]
 }
diff --git a/opengl/tests/EGLTest/Android.bp b/opengl/tests/EGLTest/Android.bp
index 51c9376..d96a895 100644
--- a/opengl/tests/EGLTest/Android.bp
+++ b/opengl/tests/EGLTest/Android.bp
@@ -1,4 +1,3 @@
-
 package {
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
@@ -11,6 +10,7 @@
 cc_test {
 
     name: "EGL_test",
+    test_suites: ["general-tests"],
 
     srcs: [
         "egl_cache_test.cpp",
diff --git a/opengl/tests/EGLTest/EGL_test.cpp b/opengl/tests/EGLTest/EGL_test.cpp
index bbd786d..cbe4ef9 100644
--- a/opengl/tests/EGLTest/EGL_test.cpp
+++ b/opengl/tests/EGLTest/EGL_test.cpp
@@ -343,6 +343,11 @@
 }
 
 TEST_F(EGLTest, EGLDisplayP31010102) {
+    // This test has been failing since:
+    // libEGL: When driver doesn't understand P3, map sRGB-encoded P3 to sRGB
+    // https://android-review.git.corp.google.com/c/platform/frameworks/native/+/793504
+    GTEST_SKIP() << "Skipping broken test. See b/120714942 and b/117104367";
+
     EGLint numConfigs;
     EGLConfig config;
     EGLBoolean success;
@@ -866,6 +871,12 @@
     EGLConfig config;
     EGLBoolean success;
 
+    if (!hasWideColorDisplay) {
+        // skip this test if device does not have wide-color display
+        RecordProperty("hasWideColorDisplay", false);
+        return;
+    }
+
     const EGLint attrs[] = {
             // clang-format off
             EGL_SURFACE_TYPE,             EGL_WINDOW_BIT,
@@ -951,6 +962,12 @@
 TEST_F(EGLTest, EGLCreateWindowTwoColorspaces) {
     EGLConfig config;
 
+    if (!hasWideColorDisplay) {
+        // skip this test if device does not have wide-color display
+        RecordProperty("hasWideColorDisplay", false);
+        return;
+    }
+
     ASSERT_NO_FATAL_FAILURE(get8BitConfig(config));
 
     struct MockConsumer : public BnConsumerListener {
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index b8a6dad..d2c940f 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -191,6 +191,12 @@
         // The set of disabled input devices (disabledDevices) has changed.
         CHANGE_ENABLED_STATE = 1 << 9,
 
+        // The device type has been updated.
+        CHANGE_DEVICE_TYPE = 1 << 10,
+
+        // The keyboard layout association has changed.
+        CHANGE_KEYBOARD_LAYOUT_ASSOCIATION = 1 << 11,
+
         // All devices must be reopened.
         CHANGE_MUST_REOPEN = 1 << 31,
     };
@@ -208,10 +214,18 @@
     // Used to determine which DisplayViewport should be tied to which InputDevice.
     std::unordered_map<std::string, uint8_t> portAssociations;
 
-    // The associations between input device names and display unique ids.
+    // The associations between input device physical port locations and display unique ids.
     // Used to determine which DisplayViewport should be tied to which InputDevice.
     std::unordered_map<std::string, std::string> uniqueIdAssociations;
 
+    // The associations between input device ports device types.
+    // This is used to determine which device type and source should be tied to which InputDevice.
+    std::unordered_map<std::string, std::string> deviceTypeAssociations;
+
+    // The map from the input device physical port location to the input device layout info.
+    // Can be used to determine the layout of the keyboard device.
+    std::unordered_map<std::string, KeyboardLayoutInfo> keyboardLayoutAssociations;
+
     // The suggested display ID to show the cursor.
     int32_t defaultPointerDisplayId;
 
@@ -326,7 +340,6 @@
     std::optional<DisplayViewport> getDisplayViewportById(int32_t displayId) const;
     void setDisplayViewports(const std::vector<DisplayViewport>& viewports);
 
-
     void dump(std::string& dump) const;
     void dumpViewport(std::string& dump, const DisplayViewport& viewport) const;
 
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index f37f0fa..f3b680b 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -29,6 +29,7 @@
         "include",
         "mapper",
         "mapper/accumulator",
+        "mapper/gestures",
     ],
 }
 
@@ -60,7 +61,9 @@
         "mapper/accumulator/MultiTouchMotionAccumulator.cpp",
         "mapper/accumulator/SingleTouchMotionAccumulator.cpp",
         "mapper/accumulator/TouchButtonAccumulator.cpp",
+        "mapper/gestures/GestureConverter.cpp",
         "mapper/gestures/GesturesLogging.cpp",
+        "mapper/gestures/HardwareStateConverter.cpp",
     ],
 }
 
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 11b5209..13f40ee 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -291,6 +291,9 @@
                 context.getConfiguration(&configuration);
                 mConfiguration.addAll(&configuration);
             });
+
+            mAssociatedDeviceType =
+                    getValueByKey(config->deviceTypeAssociations, mIdentifier.location);
         }
 
         if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUTS)) {
diff --git a/services/inputflinger/reader/Macros.h b/services/inputflinger/reader/Macros.h
index e107d88..d2a7ced 100644
--- a/services/inputflinger/reader/Macros.h
+++ b/services/inputflinger/reader/Macros.h
@@ -22,6 +22,8 @@
 #include <log/log.h>
 #include <log/log_event_list.h>
 
+#include <unordered_map>
+
 namespace android {
 /**
  * Log debug messages for each raw event received from the EventHub.
@@ -113,4 +115,14 @@
     return (sources & sourceMask & ~AINPUT_SOURCE_CLASS_MASK) != 0;
 }
 
+template <typename K, typename V>
+static inline std::optional<V> getValueByKey(const std::unordered_map<K, V>& map, K key) {
+    auto it = map.find(key);
+    std::optional<V> value = std::nullopt;
+    if (it != map.end()) {
+        value = it->second;
+    }
+    return value;
+}
+
 } // namespace android
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 6fa21e5..b173618 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -54,6 +54,7 @@
     inline std::optional<std::string> getBluetoothAddress() const {
         return mIdentifier.bluetoothAddress;
     }
+    inline const std::string getLocation() const { return mIdentifier.location; }
     inline ftl::Flags<InputDeviceClass> getClasses() const { return mClasses; }
     inline uint32_t getSources() const { return mSources; }
     inline bool hasEventHubDevices() const { return !mDevices.empty(); }
@@ -65,6 +66,9 @@
     inline std::optional<std::string> getAssociatedDisplayUniqueId() const {
         return mAssociatedDisplayUniqueId;
     }
+    inline std::optional<std::string> getDeviceTypeAssociation() const {
+        return mAssociatedDeviceType;
+    }
     inline std::optional<DisplayViewport> getAssociatedViewport() const {
         return mAssociatedViewport;
     }
@@ -180,6 +184,7 @@
     bool mIsExternal;
     std::optional<uint8_t> mAssociatedDisplayPort;
     std::optional<std::string> mAssociatedDisplayUniqueId;
+    std::optional<std::string> mAssociatedDeviceType;
     std::optional<DisplayViewport> mAssociatedViewport;
     bool mHasMic;
     bool mDropUntilNextSync;
@@ -399,8 +404,9 @@
     inline status_t enableDevice() { return mEventHub->enableDevice(mId); }
     inline status_t disableDevice() { return mEventHub->disableDevice(mId); }
 
-    inline const std::string getName() { return mDevice.getName(); }
+    inline const std::string getName() const { return mDevice.getName(); }
     inline const std::string getDescriptor() { return mDevice.getDescriptor(); }
+    inline const std::string getLocation() { return mDevice.getLocation(); }
     inline bool isExternal() { return mDevice.isExternal(); }
     inline std::optional<uint8_t> getAssociatedDisplayPort() const {
         return mDevice.getAssociatedDisplayPort();
@@ -408,6 +414,9 @@
     inline std::optional<std::string> getAssociatedDisplayUniqueId() const {
         return mDevice.getAssociatedDisplayUniqueId();
     }
+    inline std::optional<std::string> getDeviceTypeAssociation() const {
+        return mDevice.getDeviceTypeAssociation();
+    }
     inline std::optional<DisplayViewport> getAssociatedViewport() const {
         return mDevice.getAssociatedViewport();
     }
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index 44f0dfe..6f01449 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -120,6 +120,10 @@
 
     info->setKeyboardType(mKeyboardType);
     info->setKeyCharacterMap(getDeviceContext().getKeyCharacterMap());
+
+    if (mKeyboardLayoutInfo) {
+        info->setKeyboardLayoutInfo(*mKeyboardLayoutInfo);
+    }
 }
 
 void KeyboardInputMapper::dump(std::string& dump) {
@@ -129,6 +133,12 @@
     dump += StringPrintf(INDENT3 "Orientation: %d\n", getOrientation());
     dump += StringPrintf(INDENT3 "KeyDowns: %zu keys currently down\n", mKeyDowns.size());
     dump += StringPrintf(INDENT3 "MetaState: 0x%0x\n", mMetaState);
+    dump += INDENT3 "KeyboardLayoutInfo: ";
+    if (mKeyboardLayoutInfo) {
+        dump += mKeyboardLayoutInfo->languageTag + ", " + mKeyboardLayoutInfo->layoutType + "\n";
+    } else {
+        dump += "<not set>\n";
+    }
 }
 
 std::optional<DisplayViewport> KeyboardInputMapper::findViewport(
@@ -158,6 +168,12 @@
     if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
         mViewport = findViewport(config);
     }
+
+    if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUT_ASSOCIATION)) {
+        mKeyboardLayoutInfo =
+                getValueByKey(config->keyboardLayoutAssociations, getDeviceContext().getLocation());
+    }
+
     return out;
 }
 
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
index 0526fd8..da5b8ee 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
@@ -58,6 +58,7 @@
 
     uint32_t mSource{};
     int32_t mKeyboardType{};
+    std::optional<KeyboardLayoutInfo> mKeyboardLayoutInfo;
 
     std::vector<KeyDown> mKeyDowns{}; // keys that are down
     int32_t mMetaState{};
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index cefc44e..160f9eb 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -392,33 +392,10 @@
         }
     }
 
-    if (getDeviceContext().hasInputProperty(INPUT_PROP_DIRECT)) {
-        // The device is a touch screen.
-        mParameters.deviceType = Parameters::DeviceType::TOUCH_SCREEN;
-    } else if (getDeviceContext().hasInputProperty(INPUT_PROP_POINTER)) {
-        // The device is a pointing device like a track pad.
-        mParameters.deviceType = Parameters::DeviceType::POINTER;
-    } else {
-        // The device is a touch pad of unknown purpose.
-        mParameters.deviceType = Parameters::DeviceType::POINTER;
-    }
+    configureDeviceType();
 
     mParameters.hasButtonUnderPad = getDeviceContext().hasInputProperty(INPUT_PROP_BUTTONPAD);
 
-    std::string deviceTypeString;
-    if (getDeviceContext().getConfiguration().tryGetProperty("touch.deviceType",
-                                                             deviceTypeString)) {
-        if (deviceTypeString == "touchScreen") {
-            mParameters.deviceType = Parameters::DeviceType::TOUCH_SCREEN;
-        } else if (deviceTypeString == "touchNavigation") {
-            mParameters.deviceType = Parameters::DeviceType::TOUCH_NAVIGATION;
-        } else if (deviceTypeString == "pointer") {
-            mParameters.deviceType = Parameters::DeviceType::POINTER;
-        } else if (deviceTypeString != "default") {
-            ALOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.c_str());
-        }
-    }
-
     mParameters.orientationAware = mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN;
     getDeviceContext().getConfiguration().tryGetProperty("touch.orientationAware",
                                                          mParameters.orientationAware);
@@ -444,7 +421,9 @@
     mParameters.associatedDisplayIsExternal = false;
     if (mParameters.orientationAware ||
         mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN ||
-        mParameters.deviceType == Parameters::DeviceType::POINTER) {
+        mParameters.deviceType == Parameters::DeviceType::POINTER ||
+        (mParameters.deviceType == Parameters::DeviceType::TOUCH_NAVIGATION &&
+         getDeviceContext().getAssociatedViewport())) {
         mParameters.hasAssociatedDisplay = true;
         if (mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN) {
             mParameters.associatedDisplayIsExternal = getDeviceContext().isExternal();
@@ -473,6 +452,34 @@
                                                          mParameters.enableForInactiveViewport);
 }
 
+void TouchInputMapper::configureDeviceType() {
+    if (getDeviceContext().hasInputProperty(INPUT_PROP_DIRECT)) {
+        // The device is a touch screen.
+        mParameters.deviceType = Parameters::DeviceType::TOUCH_SCREEN;
+    } else if (getDeviceContext().hasInputProperty(INPUT_PROP_POINTER)) {
+        // The device is a pointing device like a track pad.
+        mParameters.deviceType = Parameters::DeviceType::POINTER;
+    } else {
+        // The device is a touch pad of unknown purpose.
+        mParameters.deviceType = Parameters::DeviceType::POINTER;
+    }
+
+    // Type association takes precedence over the device type found in the idc file.
+    std::string deviceTypeString = getDeviceContext().getDeviceTypeAssociation().value_or("");
+    if (deviceTypeString.empty()) {
+        getDeviceContext().getConfiguration().tryGetProperty("touch.deviceType", deviceTypeString);
+    }
+    if (deviceTypeString == "touchScreen") {
+        mParameters.deviceType = Parameters::DeviceType::TOUCH_SCREEN;
+    } else if (deviceTypeString == "touchNavigation") {
+        mParameters.deviceType = Parameters::DeviceType::TOUCH_NAVIGATION;
+    } else if (deviceTypeString == "pointer") {
+        mParameters.deviceType = Parameters::DeviceType::POINTER;
+    } else if (deviceTypeString != "default" && deviceTypeString != "") {
+        ALOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.c_str());
+    }
+}
+
 void TouchInputMapper::dumpParameters(std::string& dump) {
     dump += INDENT3 "Parameters:\n";
 
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 34ba625..50a7ea3 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -814,6 +814,8 @@
     static void assignPointerIds(const RawState& last, RawState& current);
 
     void rotateAndScale(float& x, float& y) const;
+
+    void configureDeviceType();
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index 956a7aa..c563dba 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -16,9 +16,8 @@
 
 #include "../Macros.h"
 
-#include <chrono>
-
 #include <android/input.h>
+#include <linux/input-event-codes.h>
 #include <log/log_main.h>
 #include "TouchCursorInputMapperCommon.h"
 #include "TouchpadInputMapper.h"
@@ -83,30 +82,14 @@
     mapper->consumeGesture(gesture);
 }
 
-uint32_t gesturesButtonToMotionEventButton(uint32_t gesturesButton) {
-    switch (gesturesButton) {
-        case GESTURES_BUTTON_LEFT:
-            return AMOTION_EVENT_BUTTON_PRIMARY;
-        case GESTURES_BUTTON_MIDDLE:
-            return AMOTION_EVENT_BUTTON_TERTIARY;
-        case GESTURES_BUTTON_RIGHT:
-            return AMOTION_EVENT_BUTTON_SECONDARY;
-        case GESTURES_BUTTON_BACK:
-            return AMOTION_EVENT_BUTTON_BACK;
-        case GESTURES_BUTTON_FORWARD:
-            return AMOTION_EVENT_BUTTON_FORWARD;
-        default:
-            return 0;
-    }
-}
-
 } // namespace
 
 TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext)
       : InputMapper(deviceContext),
         mGestureInterpreter(NewGestureInterpreter(), DeleteGestureInterpreter),
         mPointerController(getContext()->getPointerController(getDeviceId())),
-        mTouchButtonAccumulator(deviceContext) {
+        mStateConverter(deviceContext),
+        mGestureConverter(*getContext(), getDeviceId()) {
     mGestureInterpreter->Initialize(GESTURES_DEVCLASS_TOUCHPAD);
     mGestureInterpreter->SetHardwareProperties(createHardwareProperties(deviceContext));
     // Even though we don't explicitly delete copy/move semantics, it's safe to
@@ -116,16 +99,6 @@
     mGestureInterpreter->SetCallback(gestureInterpreterCallback, this);
     // TODO(b/251196347): set a property provider, so we can change gesture properties.
     // TODO(b/251196347): set a timer provider, so the library can use timers.
-
-    RawAbsoluteAxisInfo slotAxisInfo;
-    getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo);
-    if (!slotAxisInfo.valid || slotAxisInfo.maxValue <= 0) {
-        ALOGW("Touchpad \"%s\" doesn't have a valid ABS_MT_SLOT axis, and probably won't work "
-              "properly.",
-              getDeviceName().c_str());
-    }
-    mMotionAccumulator.configure(getDeviceContext(), slotAxisInfo.maxValue + 1, true);
-    mTouchButtonAccumulator.configure();
 }
 
 TouchpadInputMapper::~TouchpadInputMapper() {
@@ -139,82 +112,27 @@
 }
 
 std::list<NotifyArgs> TouchpadInputMapper::reset(nsecs_t when) {
-    mCursorButtonAccumulator.reset(getDeviceContext());
-    mTouchButtonAccumulator.reset();
-    mMscTimestamp = 0;
-
-    mButtonState = 0;
+    mStateConverter.reset();
+    mGestureConverter.reset();
     return InputMapper::reset(when);
 }
 
 std::list<NotifyArgs> TouchpadInputMapper::process(const RawEvent* rawEvent) {
-    std::list<NotifyArgs> out = {};
-    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
-        out = sync(rawEvent->when, rawEvent->readTime);
+    std::optional<SelfContainedHardwareState> state = mStateConverter.processRawEvent(rawEvent);
+    if (state) {
+        return sendHardwareState(rawEvent->when, rawEvent->readTime, *state);
+    } else {
+        return {};
     }
-    if (rawEvent->type == EV_MSC && rawEvent->code == MSC_TIMESTAMP) {
-        mMscTimestamp = rawEvent->value;
-    }
-    mCursorButtonAccumulator.process(rawEvent);
-    mMotionAccumulator.process(rawEvent);
-    mTouchButtonAccumulator.process(rawEvent);
-    return out;
 }
 
-std::list<NotifyArgs> TouchpadInputMapper::sync(nsecs_t when, nsecs_t readTime) {
-    HardwareState hwState;
-    // The gestures library uses doubles to represent timestamps in seconds.
-    hwState.timestamp = std::chrono::duration<stime_t>(std::chrono::nanoseconds(when)).count();
-    hwState.msc_timestamp =
-            std::chrono::duration<stime_t>(std::chrono::microseconds(mMscTimestamp)).count();
-
-    hwState.buttons_down = 0;
-    if (mCursorButtonAccumulator.isLeftPressed()) {
-        hwState.buttons_down |= GESTURES_BUTTON_LEFT;
-    }
-    if (mCursorButtonAccumulator.isMiddlePressed()) {
-        hwState.buttons_down |= GESTURES_BUTTON_MIDDLE;
-    }
-    if (mCursorButtonAccumulator.isRightPressed()) {
-        hwState.buttons_down |= GESTURES_BUTTON_RIGHT;
-    }
-    if (mCursorButtonAccumulator.isBackPressed() || mCursorButtonAccumulator.isSidePressed()) {
-        hwState.buttons_down |= GESTURES_BUTTON_BACK;
-    }
-    if (mCursorButtonAccumulator.isForwardPressed() || mCursorButtonAccumulator.isExtraPressed()) {
-        hwState.buttons_down |= GESTURES_BUTTON_FORWARD;
-    }
-
-    std::vector<FingerState> fingers;
-    for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) {
-        MultiTouchMotionAccumulator::Slot slot = mMotionAccumulator.getSlot(i);
-        if (slot.isInUse()) {
-            FingerState& fingerState = fingers.emplace_back();
-            fingerState = {};
-            fingerState.touch_major = slot.getTouchMajor();
-            fingerState.touch_minor = slot.getTouchMinor();
-            fingerState.width_major = slot.getToolMajor();
-            fingerState.width_minor = slot.getToolMinor();
-            fingerState.pressure = slot.getPressure();
-            fingerState.orientation = slot.getOrientation();
-            fingerState.position_x = slot.getX();
-            fingerState.position_y = slot.getY();
-            fingerState.tracking_id = slot.getTrackingId();
-        }
-    }
-    hwState.fingers = fingers.data();
-    hwState.finger_cnt = fingers.size();
-    hwState.touch_cnt = mTouchButtonAccumulator.getTouchCount();
-
+std::list<NotifyArgs> TouchpadInputMapper::sendHardwareState(nsecs_t when, nsecs_t readTime,
+                                                             SelfContainedHardwareState schs) {
     mProcessing = true;
-    mGestureInterpreter->PushHardwareState(&hwState);
+    mGestureInterpreter->PushHardwareState(&schs.state);
     mProcessing = false;
 
-    std::list<NotifyArgs> out = processGestures(when, readTime);
-
-    mMotionAccumulator.finishSync();
-    mMscTimestamp = 0;
-    return out;
+    return processGestures(when, readTime);
 }
 
 void TouchpadInputMapper::consumeGesture(const Gesture* gesture) {
@@ -229,137 +147,10 @@
 std::list<NotifyArgs> TouchpadInputMapper::processGestures(nsecs_t when, nsecs_t readTime) {
     std::list<NotifyArgs> out = {};
     for (Gesture& gesture : mGesturesToProcess) {
-        switch (gesture.type) {
-            case kGestureTypeMove:
-                out.push_back(handleMove(when, readTime, gesture));
-                break;
-            case kGestureTypeButtonsChange:
-                out += handleButtonsChange(when, readTime, gesture);
-                break;
-            default:
-                // TODO(b/251196347): handle more gesture types.
-                break;
-        }
+        out += mGestureConverter.handleGesture(when, readTime, gesture);
     }
     mGesturesToProcess.clear();
     return out;
 }
 
-NotifyArgs TouchpadInputMapper::handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture) {
-    PointerProperties props;
-    props.clear();
-    props.id = 0;
-    props.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
-
-    mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
-    mPointerController->move(gesture.details.move.dx, gesture.details.move.dy);
-    mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
-    float xCursorPosition, yCursorPosition;
-    mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
-
-    PointerCoords coords;
-    coords.clear();
-    coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
-    coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
-    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, gesture.details.move.dx);
-    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, gesture.details.move.dy);
-    const bool down = isPointerDown(mButtonState);
-    coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f);
-
-    const int32_t action = down ? AMOTION_EVENT_ACTION_MOVE : AMOTION_EVENT_ACTION_HOVER_MOVE;
-    return makeMotionArgs(when, readTime, action, /* actionButton= */ 0, mButtonState,
-                          /* pointerCount= */ 1, &props, &coords, xCursorPosition, yCursorPosition);
-}
-
-std::list<NotifyArgs> TouchpadInputMapper::handleButtonsChange(nsecs_t when, nsecs_t readTime,
-                                                               const Gesture& gesture) {
-    std::list<NotifyArgs> out = {};
-
-    mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
-    mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
-
-    PointerProperties props;
-    props.clear();
-    props.id = 0;
-    props.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
-
-    float xCursorPosition, yCursorPosition;
-    mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
-
-    PointerCoords coords;
-    coords.clear();
-    coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
-    coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
-    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0);
-    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0);
-    const uint32_t buttonsPressed = gesture.details.buttons.down;
-    bool pointerDown = isPointerDown(mButtonState) ||
-            buttonsPressed &
-                    (GESTURES_BUTTON_LEFT | GESTURES_BUTTON_MIDDLE | GESTURES_BUTTON_RIGHT);
-    coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pointerDown ? 1.0f : 0.0f);
-
-    uint32_t newButtonState = mButtonState;
-    std::list<NotifyArgs> pressEvents = {};
-    for (uint32_t button = 1; button <= GESTURES_BUTTON_FORWARD; button <<= 1) {
-        if (buttonsPressed & button) {
-            uint32_t actionButton = gesturesButtonToMotionEventButton(button);
-            newButtonState |= actionButton;
-            pressEvents.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                                                 actionButton, newButtonState,
-                                                 /* pointerCount= */ 1, &props, &coords,
-                                                 xCursorPosition, yCursorPosition));
-        }
-    }
-    if (!isPointerDown(mButtonState) && isPointerDown(newButtonState)) {
-        mDownTime = when;
-        out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
-                                     /* actionButton= */ 0, newButtonState, /* pointerCount= */ 1,
-                                     &props, &coords, xCursorPosition, yCursorPosition));
-    }
-    out.splice(out.end(), pressEvents);
-
-    // The same button may be in both down and up in the same gesture, in which case we should treat
-    // it as having gone down and then up. So, we treat a single button change gesture as two state
-    // changes: a set of buttons going down, followed by a set of buttons going up.
-    mButtonState = newButtonState;
-
-    const uint32_t buttonsReleased = gesture.details.buttons.up;
-    for (uint32_t button = 1; button <= GESTURES_BUTTON_FORWARD; button <<= 1) {
-        if (buttonsReleased & button) {
-            uint32_t actionButton = gesturesButtonToMotionEventButton(button);
-            newButtonState &= ~actionButton;
-            out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
-                                         actionButton, newButtonState, /* pointerCount= */ 1,
-                                         &props, &coords, xCursorPosition, yCursorPosition));
-        }
-    }
-    if (isPointerDown(mButtonState) && !isPointerDown(newButtonState)) {
-        coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f);
-        out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0,
-                                     newButtonState, /* pointerCount= */ 1, &props, &coords,
-                                     xCursorPosition, yCursorPosition));
-    }
-    mButtonState = newButtonState;
-    return out;
-}
-
-NotifyMotionArgs TouchpadInputMapper::makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
-                                                     int32_t actionButton, int32_t buttonState,
-                                                     uint32_t pointerCount,
-                                                     const PointerProperties* pointerProperties,
-                                                     const PointerCoords* pointerCoords,
-                                                     float xCursorPosition, float yCursorPosition) {
-    // TODO(b/260226362): consider what the appropriate source for these events is.
-    const uint32_t source = AINPUT_SOURCE_MOUSE;
-
-    return NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), source,
-                            mPointerController->getDisplayId(), /* policyFlags= */ 0, action,
-                            /* actionButton= */ actionButton, /* flags= */ 0,
-                            getContext()->getGlobalMetaState(), buttonState,
-                            MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount,
-                            pointerProperties, pointerCoords,
-                            /* xPrecision= */ 1.0f, /* yPrecision= */ 1.0f, xCursorPosition,
-                            yCursorPosition, /* downTime= */ mDownTime, /* videoFrames= */ {});
-}
-
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
index fe6b1fe..b3bc831 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -16,7 +16,9 @@
 
 #pragma once
 
+#include <list>
 #include <memory>
+#include <vector>
 
 #include <PointerControllerInterface.h>
 
@@ -24,9 +26,8 @@
 #include "InputDevice.h"
 #include "InputMapper.h"
 #include "NotifyArgs.h"
-#include "accumulator/CursorButtonAccumulator.h"
-#include "accumulator/MultiTouchMotionAccumulator.h"
-#include "accumulator/TouchButtonAccumulator.h"
+#include "gestures/GestureConverter.h"
+#include "gestures/HardwareStateConverter.h"
 
 #include "include/gestures.h"
 
@@ -44,35 +45,19 @@
     void consumeGesture(const Gesture* gesture);
 
 private:
-    [[nodiscard]] std::list<NotifyArgs> sync(nsecs_t when, nsecs_t readTime);
+    [[nodiscard]] std::list<NotifyArgs> sendHardwareState(nsecs_t when, nsecs_t readTime,
+                                                          SelfContainedHardwareState schs);
     [[nodiscard]] std::list<NotifyArgs> processGestures(nsecs_t when, nsecs_t readTime);
-    NotifyArgs handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture);
-    [[nodiscard]] std::list<NotifyArgs> handleButtonsChange(nsecs_t when, nsecs_t readTime,
-                                                            const Gesture& gesture);
-
-    NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
-                                    int32_t actionButton, int32_t buttonState,
-                                    uint32_t pointerCount,
-                                    const PointerProperties* pointerProperties,
-                                    const PointerCoords* pointerCoords, float xCursorPosition,
-                                    float yCursorPosition);
 
     std::unique_ptr<gestures::GestureInterpreter, void (*)(gestures::GestureInterpreter*)>
             mGestureInterpreter;
     std::shared_ptr<PointerControllerInterface> mPointerController;
 
-    CursorButtonAccumulator mCursorButtonAccumulator;
-    MultiTouchMotionAccumulator mMotionAccumulator;
-    TouchButtonAccumulator mTouchButtonAccumulator;
-    int32_t mMscTimestamp = 0;
+    HardwareStateConverter mStateConverter;
+    GestureConverter mGestureConverter;
 
     bool mProcessing = false;
     std::vector<Gesture> mGesturesToProcess;
-
-    // The current button state according to the gestures library, but converted into MotionEvent
-    // button values (AMOTION_EVENT_BUTTON_...).
-    uint32_t mButtonState = 0;
-    nsecs_t mDownTime = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp
index 2d7d73b..153236c 100644
--- a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp
@@ -25,7 +25,7 @@
     clearButtons();
 }
 
-void CursorButtonAccumulator::reset(InputDeviceContext& deviceContext) {
+void CursorButtonAccumulator::reset(const InputDeviceContext& deviceContext) {
     mBtnLeft = deviceContext.isKeyPressed(BTN_LEFT);
     mBtnRight = deviceContext.isKeyPressed(BTN_RIGHT);
     mBtnMiddle = deviceContext.isKeyPressed(BTN_MIDDLE);
diff --git a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h
index 1380604..6960644 100644
--- a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h
@@ -27,7 +27,7 @@
 class CursorButtonAccumulator {
 public:
     CursorButtonAccumulator();
-    void reset(InputDeviceContext& deviceContext);
+    void reset(const InputDeviceContext& deviceContext);
 
     void process(const RawEvent* rawEvent);
 
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
index 8746729..f6a42bd 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
@@ -26,8 +26,8 @@
 MultiTouchMotionAccumulator::MultiTouchMotionAccumulator()
       : mCurrentSlot(-1), mUsingSlotsProtocol(false) {}
 
-void MultiTouchMotionAccumulator::configure(InputDeviceContext& deviceContext, size_t slotCount,
-                                            bool usingSlotsProtocol) {
+void MultiTouchMotionAccumulator::configure(const InputDeviceContext& deviceContext,
+                                            size_t slotCount, bool usingSlotsProtocol) {
     mUsingSlotsProtocol = usingSlotsProtocol;
     mSlots = std::vector<Slot>(slotCount);
 
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
index 62bc780..3c1a2a9 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
@@ -72,7 +72,8 @@
 
     MultiTouchMotionAccumulator();
 
-    void configure(InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol);
+    void configure(const InputDeviceContext& deviceContext, size_t slotCount,
+                   bool usingSlotsProtocol);
     void process(const RawEvent* rawEvent);
     void finishSync();
 
diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h
index 2e70e2e..c2aa2ad 100644
--- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h
@@ -27,7 +27,7 @@
 /* Keeps track of the state of touch, stylus and tool buttons. */
 class TouchButtonAccumulator {
 public:
-    explicit TouchButtonAccumulator(InputDeviceContext& deviceContext)
+    explicit TouchButtonAccumulator(const InputDeviceContext& deviceContext)
           : mDeviceContext(deviceContext){};
 
     void configure();
@@ -65,7 +65,7 @@
 
     HidUsageAccumulator mHidUsageAccumulator{};
 
-    InputDeviceContext& mDeviceContext;
+    const InputDeviceContext& mDeviceContext;
 
     void processMappedKey(int32_t scanCode, bool down);
 };
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
new file mode 100644
index 0000000..23216d3
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -0,0 +1,186 @@
+/*
+ * 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 "gestures/GestureConverter.h"
+
+#include <android/input.h>
+
+#include "TouchCursorInputMapperCommon.h"
+#include "input/Input.h"
+
+namespace android {
+
+namespace {
+
+uint32_t gesturesButtonToMotionEventButton(uint32_t gesturesButton) {
+    switch (gesturesButton) {
+        case GESTURES_BUTTON_LEFT:
+            return AMOTION_EVENT_BUTTON_PRIMARY;
+        case GESTURES_BUTTON_MIDDLE:
+            return AMOTION_EVENT_BUTTON_TERTIARY;
+        case GESTURES_BUTTON_RIGHT:
+            return AMOTION_EVENT_BUTTON_SECONDARY;
+        case GESTURES_BUTTON_BACK:
+            return AMOTION_EVENT_BUTTON_BACK;
+        case GESTURES_BUTTON_FORWARD:
+            return AMOTION_EVENT_BUTTON_FORWARD;
+        default:
+            return 0;
+    }
+}
+
+} // namespace
+
+GestureConverter::GestureConverter(InputReaderContext& readerContext, int32_t deviceId)
+      : mDeviceId(deviceId),
+        mReaderContext(readerContext),
+        mPointerController(readerContext.getPointerController(deviceId)) {}
+
+void GestureConverter::reset() {
+    mButtonState = 0;
+}
+
+std::list<NotifyArgs> GestureConverter::handleGesture(nsecs_t when, nsecs_t readTime,
+                                                      const Gesture& gesture) {
+    switch (gesture.type) {
+        case kGestureTypeMove:
+            return {handleMove(when, readTime, gesture)};
+        case kGestureTypeButtonsChange:
+            return handleButtonsChange(when, readTime, gesture);
+        default:
+            // TODO(b/251196347): handle more gesture types.
+            return {};
+    }
+}
+
+NotifyArgs GestureConverter::handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture) {
+    PointerProperties props;
+    props.clear();
+    props.id = 0;
+    props.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+
+    mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
+    mPointerController->move(gesture.details.move.dx, gesture.details.move.dy);
+    mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
+    float xCursorPosition, yCursorPosition;
+    mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+
+    PointerCoords coords;
+    coords.clear();
+    coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, gesture.details.move.dx);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, gesture.details.move.dy);
+    const bool down = isPointerDown(mButtonState);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f);
+
+    const int32_t action = down ? AMOTION_EVENT_ACTION_MOVE : AMOTION_EVENT_ACTION_HOVER_MOVE;
+    return makeMotionArgs(when, readTime, action, /* actionButton= */ 0, mButtonState,
+                          /* pointerCount= */ 1, &props, &coords, xCursorPosition, yCursorPosition);
+}
+
+std::list<NotifyArgs> GestureConverter::handleButtonsChange(nsecs_t when, nsecs_t readTime,
+                                                            const Gesture& gesture) {
+    std::list<NotifyArgs> out = {};
+
+    mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
+    mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
+
+    PointerProperties props;
+    props.clear();
+    props.id = 0;
+    props.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+
+    float xCursorPosition, yCursorPosition;
+    mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+
+    PointerCoords coords;
+    coords.clear();
+    coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0);
+    const uint32_t buttonsPressed = gesture.details.buttons.down;
+    bool pointerDown = isPointerDown(mButtonState) ||
+            buttonsPressed &
+                    (GESTURES_BUTTON_LEFT | GESTURES_BUTTON_MIDDLE | GESTURES_BUTTON_RIGHT);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pointerDown ? 1.0f : 0.0f);
+
+    uint32_t newButtonState = mButtonState;
+    std::list<NotifyArgs> pressEvents = {};
+    for (uint32_t button = 1; button <= GESTURES_BUTTON_FORWARD; button <<= 1) {
+        if (buttonsPressed & button) {
+            uint32_t actionButton = gesturesButtonToMotionEventButton(button);
+            newButtonState |= actionButton;
+            pressEvents.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                                                 actionButton, newButtonState,
+                                                 /* pointerCount= */ 1, &props, &coords,
+                                                 xCursorPosition, yCursorPosition));
+        }
+    }
+    if (!isPointerDown(mButtonState) && isPointerDown(newButtonState)) {
+        mDownTime = when;
+        out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
+                                     /* actionButton= */ 0, newButtonState, /* pointerCount= */ 1,
+                                     &props, &coords, xCursorPosition, yCursorPosition));
+    }
+    out.splice(out.end(), pressEvents);
+
+    // The same button may be in both down and up in the same gesture, in which case we should treat
+    // it as having gone down and then up. So, we treat a single button change gesture as two state
+    // changes: a set of buttons going down, followed by a set of buttons going up.
+    mButtonState = newButtonState;
+
+    const uint32_t buttonsReleased = gesture.details.buttons.up;
+    for (uint32_t button = 1; button <= GESTURES_BUTTON_FORWARD; button <<= 1) {
+        if (buttonsReleased & button) {
+            uint32_t actionButton = gesturesButtonToMotionEventButton(button);
+            newButtonState &= ~actionButton;
+            out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+                                         actionButton, newButtonState, /* pointerCount= */ 1,
+                                         &props, &coords, xCursorPosition, yCursorPosition));
+        }
+    }
+    if (isPointerDown(mButtonState) && !isPointerDown(newButtonState)) {
+        coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f);
+        out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0,
+                                     newButtonState, /* pointerCount= */ 1, &props, &coords,
+                                     xCursorPosition, yCursorPosition));
+    }
+    mButtonState = newButtonState;
+    return out;
+}
+
+NotifyMotionArgs GestureConverter::makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
+                                                  int32_t actionButton, int32_t buttonState,
+                                                  uint32_t pointerCount,
+                                                  const PointerProperties* pointerProperties,
+                                                  const PointerCoords* pointerCoords,
+                                                  float xCursorPosition, float yCursorPosition) {
+    // TODO(b/260226362): consider what the appropriate source for these events is.
+    const uint32_t source = AINPUT_SOURCE_MOUSE;
+
+    return NotifyMotionArgs(mReaderContext.getNextId(), when, readTime, mDeviceId, source,
+                            mPointerController->getDisplayId(), /* policyFlags= */ 0, action,
+                            /* actionButton= */ actionButton, /* flags= */ 0,
+                            mReaderContext.getGlobalMetaState(), buttonState,
+                            MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount,
+                            pointerProperties, pointerCoords,
+                            /* xPrecision= */ 1.0f, /* yPrecision= */ 1.0f, xCursorPosition,
+                            yCursorPosition, /* downTime= */ mDownTime, /* videoFrames= */ {});
+}
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
new file mode 100644
index 0000000..dc11f24
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
@@ -0,0 +1,65 @@
+/*
+ * 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 <list>
+#include <memory>
+
+#include <PointerControllerInterface.h>
+#include <utils/Timers.h>
+
+#include "InputReaderContext.h"
+#include "NotifyArgs.h"
+
+#include "include/gestures.h"
+
+namespace android {
+
+// Converts Gesture structs from the gestures library into NotifyArgs and the appropriate
+// PointerController calls.
+class GestureConverter {
+public:
+    GestureConverter(InputReaderContext& readerContext, int32_t deviceId);
+
+    void reset();
+
+    [[nodiscard]] std::list<NotifyArgs> handleGesture(nsecs_t when, nsecs_t readTime,
+                                                      const Gesture& gesture);
+
+private:
+    NotifyArgs handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture);
+    [[nodiscard]] std::list<NotifyArgs> handleButtonsChange(nsecs_t when, nsecs_t readTime,
+                                                            const Gesture& gesture);
+
+    NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
+                                    int32_t actionButton, int32_t buttonState,
+                                    uint32_t pointerCount,
+                                    const PointerProperties* pointerProperties,
+                                    const PointerCoords* pointerCoords, float xCursorPosition,
+                                    float yCursorPosition);
+
+    const int32_t mDeviceId;
+    InputReaderContext& mReaderContext;
+    std::shared_ptr<PointerControllerInterface> mPointerController;
+
+    // The current button state according to the gestures library, but converted into MotionEvent
+    // button values (AMOTION_EVENT_BUTTON_...).
+    uint32_t mButtonState = 0;
+    nsecs_t mDownTime = 0;
+};
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp
new file mode 100644
index 0000000..2e175b8
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp
@@ -0,0 +1,109 @@
+/*
+ * 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 "gestures/HardwareStateConverter.h"
+
+#include <chrono>
+#include <vector>
+
+#include <linux/input-event-codes.h>
+
+namespace android {
+
+HardwareStateConverter::HardwareStateConverter(const InputDeviceContext& deviceContext)
+      : mDeviceContext(deviceContext), mTouchButtonAccumulator(deviceContext) {
+    RawAbsoluteAxisInfo slotAxisInfo;
+    deviceContext.getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo);
+    if (!slotAxisInfo.valid || slotAxisInfo.maxValue <= 0) {
+        ALOGW("Touchpad \"%s\" doesn't have a valid ABS_MT_SLOT axis, and probably won't work "
+              "properly.",
+              deviceContext.getName().c_str());
+    }
+    mMotionAccumulator.configure(deviceContext, slotAxisInfo.maxValue + 1, true);
+    mTouchButtonAccumulator.configure();
+}
+
+std::optional<SelfContainedHardwareState> HardwareStateConverter::processRawEvent(
+        const RawEvent* rawEvent) {
+    std::optional<SelfContainedHardwareState> out;
+    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
+        out = produceHardwareState(rawEvent->when);
+        mMotionAccumulator.finishSync();
+        mMscTimestamp = 0;
+    }
+    if (rawEvent->type == EV_MSC && rawEvent->code == MSC_TIMESTAMP) {
+        mMscTimestamp = rawEvent->value;
+    }
+    mCursorButtonAccumulator.process(rawEvent);
+    mMotionAccumulator.process(rawEvent);
+    mTouchButtonAccumulator.process(rawEvent);
+    return out;
+}
+
+SelfContainedHardwareState HardwareStateConverter::produceHardwareState(nsecs_t when) {
+    SelfContainedHardwareState schs;
+    // The gestures library uses doubles to represent timestamps in seconds.
+    schs.state.timestamp = std::chrono::duration<stime_t>(std::chrono::nanoseconds(when)).count();
+    schs.state.msc_timestamp =
+            std::chrono::duration<stime_t>(std::chrono::microseconds(mMscTimestamp)).count();
+
+    schs.state.buttons_down = 0;
+    if (mCursorButtonAccumulator.isLeftPressed()) {
+        schs.state.buttons_down |= GESTURES_BUTTON_LEFT;
+    }
+    if (mCursorButtonAccumulator.isMiddlePressed()) {
+        schs.state.buttons_down |= GESTURES_BUTTON_MIDDLE;
+    }
+    if (mCursorButtonAccumulator.isRightPressed()) {
+        schs.state.buttons_down |= GESTURES_BUTTON_RIGHT;
+    }
+    if (mCursorButtonAccumulator.isBackPressed() || mCursorButtonAccumulator.isSidePressed()) {
+        schs.state.buttons_down |= GESTURES_BUTTON_BACK;
+    }
+    if (mCursorButtonAccumulator.isForwardPressed() || mCursorButtonAccumulator.isExtraPressed()) {
+        schs.state.buttons_down |= GESTURES_BUTTON_FORWARD;
+    }
+
+    schs.fingers.clear();
+    for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) {
+        MultiTouchMotionAccumulator::Slot slot = mMotionAccumulator.getSlot(i);
+        if (slot.isInUse()) {
+            FingerState& fingerState = schs.fingers.emplace_back();
+            fingerState = {};
+            fingerState.touch_major = slot.getTouchMajor();
+            fingerState.touch_minor = slot.getTouchMinor();
+            fingerState.width_major = slot.getToolMajor();
+            fingerState.width_minor = slot.getToolMinor();
+            fingerState.pressure = slot.getPressure();
+            fingerState.orientation = slot.getOrientation();
+            fingerState.position_x = slot.getX();
+            fingerState.position_y = slot.getY();
+            fingerState.tracking_id = slot.getTrackingId();
+        }
+    }
+    schs.state.fingers = schs.fingers.data();
+    schs.state.finger_cnt = schs.fingers.size();
+    schs.state.touch_cnt = mTouchButtonAccumulator.getTouchCount();
+    return schs;
+}
+
+void HardwareStateConverter::reset() {
+    mCursorButtonAccumulator.reset(mDeviceContext);
+    mTouchButtonAccumulator.reset();
+    mMscTimestamp = 0;
+}
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
new file mode 100644
index 0000000..8831299
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
@@ -0,0 +1,58 @@
+/*
+ * 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 <optional>
+
+#include <utils/Timers.h>
+
+#include "EventHub.h"
+#include "InputDevice.h"
+#include "accumulator/CursorButtonAccumulator.h"
+#include "accumulator/MultiTouchMotionAccumulator.h"
+#include "accumulator/TouchButtonAccumulator.h"
+
+#include "include/gestures.h"
+
+namespace android {
+
+// A HardwareState struct, but bundled with a vector to contain its FingerStates, so you don't have
+// to worry about where that memory is allocated.
+struct SelfContainedHardwareState {
+    HardwareState state;
+    std::vector<FingerState> fingers;
+};
+
+// Converts RawEvents into the HardwareState structs used by the gestures library.
+class HardwareStateConverter {
+public:
+    HardwareStateConverter(const InputDeviceContext& deviceContext);
+
+    std::optional<SelfContainedHardwareState> processRawEvent(const RawEvent* event);
+    void reset();
+
+private:
+    SelfContainedHardwareState produceHardwareState(nsecs_t when);
+
+    const InputDeviceContext& mDeviceContext;
+    CursorButtonAccumulator mCursorButtonAccumulator;
+    MultiTouchMotionAccumulator mMotionAccumulator;
+    TouchButtonAccumulator mTouchButtonAccumulator;
+    int32_t mMscTimestamp = 0;
+};
+
+} // namespace android
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 53d821f..58a5c31 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -44,6 +44,8 @@
         "FakeInputReaderPolicy.cpp",
         "FakePointerController.cpp",
         "FocusResolver_test.cpp",
+        "GestureConverter_test.cpp",
+        "HardwareStateConverter_test.cpp",
         "InputMapperTest.cpp",
         "InputProcessor_test.cpp",
         "InputProcessorConverter_test.cpp",
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index 3af4298..f755356 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -126,11 +126,21 @@
     mConfig.portAssociations.insert({inputPort, displayPort});
 }
 
+void FakeInputReaderPolicy::addDeviceTypeAssociation(const std::string& inputPort,
+                                                     const std::string& type) {
+    mConfig.deviceTypeAssociations.insert({inputPort, type});
+}
+
 void FakeInputReaderPolicy::addInputUniqueIdAssociation(const std::string& inputUniqueId,
                                                         const std::string& displayUniqueId) {
     mConfig.uniqueIdAssociations.insert({inputUniqueId, displayUniqueId});
 }
 
+void FakeInputReaderPolicy::addKeyboardLayoutAssociation(const std::string& inputUniqueId,
+                                                         const KeyboardLayoutInfo& layoutInfo) {
+    mConfig.keyboardLayoutAssociations.insert({inputUniqueId, layoutInfo});
+}
+
 void FakeInputReaderPolicy::addDisabledDevice(int32_t deviceId) {
     mConfig.disabledDevices.insert(deviceId);
 }
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
index c16cda4..862ff0b 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.h
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -55,8 +55,11 @@
     bool updateViewport(const DisplayViewport& viewport);
     void addExcludedDeviceName(const std::string& deviceName);
     void addInputPortAssociation(const std::string& inputPort, uint8_t displayPort);
+    void addDeviceTypeAssociation(const std::string& inputPort, const std::string& type);
     void addInputUniqueIdAssociation(const std::string& inputUniqueId,
                                      const std::string& displayUniqueId);
+    void addKeyboardLayoutAssociation(const std::string& inputUniqueId,
+                                      const KeyboardLayoutInfo& layoutInfo);
     void addDisabledDevice(int32_t deviceId);
     void removeDisabledDevice(int32_t deviceId);
     void setPointerController(std::shared_ptr<FakePointerController> controller);
diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp
index 635366b..ab7879f 100644
--- a/services/inputflinger/tests/FakePointerController.cpp
+++ b/services/inputflinger/tests/FakePointerController.cpp
@@ -16,6 +16,8 @@
 
 #include "FakePointerController.h"
 
+#include <gtest/gtest.h>
+
 namespace android {
 
 void FakePointerController::setBounds(float minX, float minY, float maxX, float maxY) {
@@ -56,6 +58,13 @@
     mDisplayId = viewport.displayId;
 }
 
+void FakePointerController::assertPosition(float x, float y) {
+    float actualX, actualY;
+    getPosition(&actualX, &actualY);
+    ASSERT_NEAR(x, actualX, 1);
+    ASSERT_NEAR(y, actualY, 1);
+}
+
 bool FakePointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
                                       float* outMaxY) const {
     *outMinX = mMinX;
diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h
index f00870f..d10cbcd 100644
--- a/services/inputflinger/tests/FakePointerController.h
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -38,6 +38,8 @@
     int32_t getDisplayId() const override;
     void setDisplayViewport(const DisplayViewport& viewport) override;
 
+    void assertPosition(float x, float y);
+
 private:
     bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override;
     void move(float deltaX, float deltaY) override;
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
new file mode 100644
index 0000000..91efd1a
--- /dev/null
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -0,0 +1,200 @@
+/*
+ * 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 <memory>
+
+#include <EventHub.h>
+#include <gestures/GestureConverter.h>
+#include <gtest/gtest.h>
+
+#include "FakeEventHub.h"
+#include "FakeInputReaderPolicy.h"
+#include "FakePointerController.h"
+#include "InstrumentedInputReader.h"
+#include "NotifyArgs.h"
+#include "TestConstants.h"
+#include "TestInputListener.h"
+#include "TestInputListenerMatchers.h"
+#include "include/gestures.h"
+
+namespace android {
+
+using testing::AllOf;
+
+class GestureConverterTest : public testing::Test {
+protected:
+    static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
+    static constexpr stime_t ARBITRARY_GESTURE_TIME = 1.2;
+    static constexpr float POINTER_X = 100;
+    static constexpr float POINTER_Y = 200;
+
+    void SetUp() {
+        mFakeEventHub = std::make_unique<FakeEventHub>();
+        mFakePolicy = sp<FakeInputReaderPolicy>::make();
+        mFakeListener = std::make_unique<TestInputListener>();
+        mReader = std::make_unique<InstrumentedInputReader>(mFakeEventHub, mFakePolicy,
+                                                            *mFakeListener);
+
+        mFakePointerController = std::make_shared<FakePointerController>();
+        mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+        mFakePointerController->setPosition(POINTER_X, POINTER_Y);
+        mFakePolicy->setPointerController(mFakePointerController);
+    }
+
+    std::shared_ptr<FakeEventHub> mFakeEventHub;
+    sp<FakeInputReaderPolicy> mFakePolicy;
+    std::unique_ptr<TestInputListener> mFakeListener;
+    std::unique_ptr<InstrumentedInputReader> mReader;
+    std::shared_ptr<FakePointerController> mFakePointerController;
+};
+
+TEST_F(GestureConverterTest, Move) {
+    GestureConverter converter(*mReader->getContext(), DEVICE_ID);
+
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+    ASSERT_EQ(1u, args.size());
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithButtonState(0),
+                      WithPressure(0.0f)));
+
+    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(95, 210));
+}
+
+TEST_F(GestureConverterTest, ButtonsChange) {
+    GestureConverter converter(*mReader->getContext(), DEVICE_ID);
+
+    // Press left and right buttons at once
+    Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                        /* down= */ GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
+                        /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
+    ASSERT_EQ(3u, args.size());
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
+                                      AMOTION_EVENT_BUTTON_SECONDARY),
+                      WithCoords(POINTER_X, POINTER_Y),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithCoords(POINTER_X, POINTER_Y),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
+                                      AMOTION_EVENT_BUTTON_SECONDARY),
+                      WithCoords(POINTER_X, POINTER_Y),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+    // Then release the left button
+    Gesture leftUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                          /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT,
+                          /* is_tap= */ false);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, leftUpGesture);
+    ASSERT_EQ(1u, args.size());
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY),
+                      WithCoords(POINTER_X, POINTER_Y),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+    // Finally release the right button
+    Gesture rightUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                           /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_RIGHT,
+                           /* is_tap= */ false);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, rightUpGesture);
+    ASSERT_EQ(2u, args.size());
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0),
+                      WithCoords(POINTER_X, POINTER_Y),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
+                      WithCoords(POINTER_X, POINTER_Y),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+}
+
+TEST_F(GestureConverterTest, DragWithButton) {
+    GestureConverter converter(*mReader->getContext(), DEVICE_ID);
+
+    // Press the button
+    Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                        /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_NONE,
+                        /* is_tap= */ false);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
+    ASSERT_EQ(2u, args.size());
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithCoords(POINTER_X, POINTER_Y),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+                      WithCoords(POINTER_X, POINTER_Y),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+    // Move
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+    ASSERT_EQ(1u, args.size());
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER),
+                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f)));
+
+    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(95, 210));
+
+    // Release the button
+    Gesture upGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                      /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT,
+                      /* is_tap= */ false);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, upGesture);
+    ASSERT_EQ(2u, args.size());
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
+                      WithCoords(POINTER_X - 5, POINTER_Y + 10),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
+                      WithCoords(POINTER_X - 5, POINTER_Y + 10),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/HardwareStateConverter_test.cpp b/services/inputflinger/tests/HardwareStateConverter_test.cpp
new file mode 100644
index 0000000..7921881
--- /dev/null
+++ b/services/inputflinger/tests/HardwareStateConverter_test.cpp
@@ -0,0 +1,219 @@
+/*
+ * 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 <EventHub.h>
+#include <gestures/HardwareStateConverter.h>
+#include <gtest/gtest.h>
+#include <linux/input-event-codes.h>
+
+#include "FakeEventHub.h"
+#include "FakeInputReaderPolicy.h"
+#include "InstrumentedInputReader.h"
+#include "TestConstants.h"
+#include "TestInputListener.h"
+
+namespace android {
+
+class HardwareStateConverterTest : public testing::Test {
+protected:
+    static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
+    static constexpr int32_t EVENTHUB_ID = 1;
+
+    void SetUp() {
+        mFakeEventHub = std::make_unique<FakeEventHub>();
+        mFakePolicy = sp<FakeInputReaderPolicy>::make();
+        mFakeListener = std::make_unique<TestInputListener>();
+        mReader = std::make_unique<InstrumentedInputReader>(mFakeEventHub, mFakePolicy,
+                                                            *mFakeListener);
+        mDevice = newDevice();
+
+        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_SLOT, 0, 7, 0, 0, 0);
+    }
+
+    std::shared_ptr<InputDevice> newDevice() {
+        InputDeviceIdentifier identifier;
+        identifier.name = "device";
+        identifier.location = "USB1";
+        identifier.bus = 0;
+        std::shared_ptr<InputDevice> device =
+                std::make_shared<InputDevice>(mReader->getContext(), DEVICE_ID, /* generation= */ 2,
+                                              identifier);
+        mReader->pushNextDevice(device);
+        mFakeEventHub->addDevice(EVENTHUB_ID, identifier.name, InputDeviceClass::TOUCHPAD,
+                                 identifier.bus);
+        mReader->loopOnce();
+        return device;
+    }
+
+    void processAxis(HardwareStateConverter& conv, nsecs_t when, int32_t type, int32_t code,
+                     int32_t value) {
+        RawEvent event;
+        event.when = when;
+        event.readTime = READ_TIME;
+        event.deviceId = EVENTHUB_ID;
+        event.type = type;
+        event.code = code;
+        event.value = value;
+        std::optional<SelfContainedHardwareState> schs = conv.processRawEvent(&event);
+        EXPECT_FALSE(schs.has_value());
+    }
+
+    std::optional<SelfContainedHardwareState> processSync(HardwareStateConverter& conv,
+                                                          nsecs_t when) {
+        RawEvent event;
+        event.when = when;
+        event.readTime = READ_TIME;
+        event.deviceId = EVENTHUB_ID;
+        event.type = EV_SYN;
+        event.code = SYN_REPORT;
+        event.value = 0;
+        return conv.processRawEvent(&event);
+    }
+
+    std::shared_ptr<FakeEventHub> mFakeEventHub;
+    sp<FakeInputReaderPolicy> mFakePolicy;
+    std::unique_ptr<TestInputListener> mFakeListener;
+    std::unique_ptr<InstrumentedInputReader> mReader;
+    std::shared_ptr<InputDevice> mDevice;
+};
+
+TEST_F(HardwareStateConverterTest, OneFinger) {
+    const nsecs_t time = 1500000000;
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    HardwareStateConverter conv(deviceContext);
+
+    processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 123);
+    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 100);
+    processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MAJOR, 5);
+    processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MINOR, 4);
+    processAxis(conv, time, EV_ABS, ABS_MT_PRESSURE, 42);
+    processAxis(conv, time, EV_ABS, ABS_MT_ORIENTATION, 2);
+
+    processAxis(conv, time, EV_ABS, ABS_X, 50);
+    processAxis(conv, time, EV_ABS, ABS_Y, 100);
+    processAxis(conv, time, EV_ABS, ABS_PRESSURE, 42);
+
+    processAxis(conv, time, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, time, EV_KEY, BTN_TOOL_FINGER, 1);
+    std::optional<SelfContainedHardwareState> schs = processSync(conv, time);
+
+    ASSERT_TRUE(schs.has_value());
+    const HardwareState& state = schs->state;
+    EXPECT_NEAR(1.5, state.timestamp, EPSILON);
+    EXPECT_EQ(0, state.buttons_down);
+    EXPECT_EQ(1, state.touch_cnt);
+
+    ASSERT_EQ(1, state.finger_cnt);
+    const FingerState& finger = state.fingers[0];
+    EXPECT_EQ(123, finger.tracking_id);
+    EXPECT_NEAR(50, finger.position_x, EPSILON);
+    EXPECT_NEAR(100, finger.position_y, EPSILON);
+    EXPECT_NEAR(5, finger.touch_major, EPSILON);
+    EXPECT_NEAR(4, finger.touch_minor, EPSILON);
+    EXPECT_NEAR(42, finger.pressure, EPSILON);
+    EXPECT_NEAR(2, finger.orientation, EPSILON);
+    EXPECT_EQ(0u, finger.flags);
+
+    EXPECT_EQ(0, state.rel_x);
+    EXPECT_EQ(0, state.rel_y);
+    EXPECT_EQ(0, state.rel_wheel);
+    EXPECT_EQ(0, state.rel_wheel_hi_res);
+    EXPECT_EQ(0, state.rel_hwheel);
+    EXPECT_NEAR(0.0, state.msc_timestamp, EPSILON);
+}
+
+TEST_F(HardwareStateConverterTest, TwoFingers) {
+    const nsecs_t time = ARBITRARY_TIME;
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    HardwareStateConverter conv(deviceContext);
+
+    processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 123);
+    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 100);
+    processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MAJOR, 5);
+    processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MINOR, 4);
+    processAxis(conv, time, EV_ABS, ABS_MT_PRESSURE, 42);
+    processAxis(conv, time, EV_ABS, ABS_MT_ORIENTATION, 2);
+
+    processAxis(conv, time, EV_ABS, ABS_MT_SLOT, 1);
+    processAxis(conv, time, EV_ABS, ABS_MT_TRACKING_ID, 456);
+    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_X, -20);
+    processAxis(conv, time, EV_ABS, ABS_MT_POSITION_Y, 40);
+    processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MAJOR, 8);
+    processAxis(conv, time, EV_ABS, ABS_MT_TOUCH_MINOR, 7);
+    processAxis(conv, time, EV_ABS, ABS_MT_PRESSURE, 21);
+    processAxis(conv, time, EV_ABS, ABS_MT_ORIENTATION, 1);
+
+    processAxis(conv, time, EV_ABS, ABS_X, 50);
+    processAxis(conv, time, EV_ABS, ABS_Y, 100);
+    processAxis(conv, time, EV_ABS, ABS_PRESSURE, 42);
+
+    processAxis(conv, time, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, time, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
+    std::optional<SelfContainedHardwareState> schs = processSync(conv, time);
+
+    ASSERT_TRUE(schs.has_value());
+    ASSERT_EQ(2, schs->state.finger_cnt);
+    const FingerState& finger1 = schs->state.fingers[0];
+    EXPECT_EQ(123, finger1.tracking_id);
+    EXPECT_NEAR(50, finger1.position_x, EPSILON);
+    EXPECT_NEAR(100, finger1.position_y, EPSILON);
+    EXPECT_NEAR(5, finger1.touch_major, EPSILON);
+    EXPECT_NEAR(4, finger1.touch_minor, EPSILON);
+    EXPECT_NEAR(42, finger1.pressure, EPSILON);
+    EXPECT_NEAR(2, finger1.orientation, EPSILON);
+    EXPECT_EQ(0u, finger1.flags);
+
+    const FingerState& finger2 = schs->state.fingers[1];
+    EXPECT_EQ(456, finger2.tracking_id);
+    EXPECT_NEAR(-20, finger2.position_x, EPSILON);
+    EXPECT_NEAR(40, finger2.position_y, EPSILON);
+    EXPECT_NEAR(8, finger2.touch_major, EPSILON);
+    EXPECT_NEAR(7, finger2.touch_minor, EPSILON);
+    EXPECT_NEAR(21, finger2.pressure, EPSILON);
+    EXPECT_NEAR(1, finger2.orientation, EPSILON);
+    EXPECT_EQ(0u, finger2.flags);
+}
+
+TEST_F(HardwareStateConverterTest, ButtonPressed) {
+    const nsecs_t time = ARBITRARY_TIME;
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    HardwareStateConverter conv(deviceContext);
+
+    processAxis(conv, time, EV_KEY, BTN_LEFT, 1);
+    std::optional<SelfContainedHardwareState> schs = processSync(conv, time);
+
+    ASSERT_TRUE(schs.has_value());
+    EXPECT_EQ(GESTURES_BUTTON_LEFT, schs->state.buttons_down);
+}
+
+TEST_F(HardwareStateConverterTest, MscTimestamp) {
+    const nsecs_t time = ARBITRARY_TIME;
+    mFakeEventHub->setMscEvent(EVENTHUB_ID, MSC_TIMESTAMP);
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    HardwareStateConverter conv(deviceContext);
+
+    processAxis(conv, time, EV_MSC, MSC_TIMESTAMP, 1200000);
+    std::optional<SelfContainedHardwareState> schs = processSync(conv, time);
+
+    ASSERT_TRUE(schs.has_value());
+    EXPECT_NEAR(1.2, schs->state.msc_timestamp, EPSILON);
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index e3bc516..864aaea 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -29,6 +29,7 @@
 #include <sys/epoll.h>
 
 #include <cinttypes>
+#include <compare>
 #include <thread>
 #include <unordered_set>
 #include <vector>
@@ -56,6 +57,7 @@
 static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
 static constexpr int32_t SECOND_DISPLAY_ID = 1;
 
+static constexpr int32_t ACTION_OUTSIDE = AMOTION_EVENT_ACTION_OUTSIDE;
 static constexpr int32_t POINTER_1_DOWN =
         AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 static constexpr int32_t POINTER_2_DOWN =
@@ -89,6 +91,7 @@
 struct PointF {
     float x;
     float y;
+    auto operator<=>(const PointF&) const = default;
 };
 
 /**
@@ -142,6 +145,24 @@
     return arg.getSource() == source;
 }
 
+MATCHER_P2(WithCoords, x, y, "MotionEvent with specified coordinates") {
+    if (arg.getPointerCount() != 1) {
+        *result_listener << "Expected 1 pointer, got " << arg.getPointerCount();
+        return false;
+    }
+    return arg.getX(0 /*pointerIndex*/) == x && arg.getY(0 /*pointerIndex*/) == y;
+}
+
+MATCHER_P(WithPointers, pointers, "MotionEvent with specified pointers") {
+    // Build a map for the received pointers, by pointer id
+    std::map<int32_t /*pointerId*/, PointF> actualPointers;
+    for (size_t pointerIndex = 0; pointerIndex < arg.getPointerCount(); pointerIndex++) {
+        const int32_t pointerId = arg.getPointerId(pointerIndex);
+        actualPointers[pointerId] = {arg.getX(pointerIndex), arg.getY(pointerIndex)};
+    }
+    return pointers == actualPointers;
+}
+
 // --- FakeInputDispatcherPolicy ---
 
 class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
@@ -2548,6 +2569,39 @@
 }
 
 /**
+ * Two windows. First is a regular window. Second does not overlap with the first, and has
+ * WATCH_OUTSIDE_TOUCH.
+ * Both windows are owned by the same UID.
+ * Tap first window. Make sure that the second window receives ACTION_OUTSIDE with correct, non-zero
+ * coordinates. The coordinates are not zeroed out because both windows are owned by the same UID.
+ */
+TEST_F(InputDispatcherTest, ActionOutsideForOwnedWindowHasValidCoordinates) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+                                                             "First Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect{0, 0, 100, 100});
+
+    sp<FakeWindowHandle> outsideWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
+                                       ADISPLAY_ID_DEFAULT);
+    outsideWindow->setFrame(Rect{100, 100, 200, 200});
+    outsideWindow->setWatchOutsideTouch(true);
+    // outsideWindow must be above 'window' to receive ACTION_OUTSIDE events when 'window' is tapped
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {outsideWindow, window}}});
+
+    // Tap on first window.
+    NotifyMotionArgs motionArgs =
+            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ADISPLAY_ID_DEFAULT, {PointF{50, 50}});
+    mDispatcher->notifyMotion(&motionArgs);
+    window->consumeMotionDown();
+    // The coordinates of the tap in 'outsideWindow' are relative to its top left corner.
+    // Therefore, we should offset them by (100, 100) relative to the screen's top left corner.
+    outsideWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_OUTSIDE), WithCoords(-50, -50)));
+}
+
+/**
  * This test documents the behavior of WATCH_OUTSIDE_TOUCH. The window will get ACTION_OUTSIDE when
  * a another pointer causes ACTION_DOWN to be sent to another window for the first time. Only one
  * ACTION_OUTSIDE event is sent per gesture.
@@ -2582,7 +2636,9 @@
     motionArgs = generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                     {PointF{-10, -10}, PointF{105, 105}});
     mDispatcher->notifyMotion(&motionArgs);
-    window->consumeMotionOutside();
+    const std::map<int32_t, PointF> expectedPointers{{0, PointF{-10, -10}}, {1, PointF{105, 105}}};
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_OUTSIDE), WithPointers(expectedPointers)));
     secondWindow->consumeMotionDown();
     thirdWindow->assertNoEvents();
 
diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp
index 3cd7c1b..a02ef05 100644
--- a/services/inputflinger/tests/InputMapperTest.cpp
+++ b/services/inputflinger/tests/InputMapperTest.cpp
@@ -166,11 +166,4 @@
     ASSERT_NEAR(distance, coords.getAxisValue(AMOTION_EVENT_AXIS_DISTANCE), EPSILON);
 }
 
-void InputMapperTest::assertPosition(const FakePointerController& controller, float x, float y) {
-    float actualX, actualY;
-    controller.getPosition(&actualX, &actualY);
-    ASSERT_NEAR(x, actualX, 1);
-    ASSERT_NEAR(y, actualY, 1);
-}
-
 } // namespace android
diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h
index b3401c3..63ca44c 100644
--- a/services/inputflinger/tests/InputMapperTest.h
+++ b/services/inputflinger/tests/InputMapperTest.h
@@ -89,7 +89,6 @@
                                     float size, float touchMajor, float touchMinor, float toolMajor,
                                     float toolMinor, float orientation, float distance,
                                     float scaledAxisEpsilon = 1.f);
-    static void assertPosition(const FakePointerController& controller, float x, float y);
 };
 
 } // namespace android
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 4cc48f6..ef2080f 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -2763,7 +2763,7 @@
 class KeyboardInputMapperTest : public InputMapperTest {
 protected:
     const std::string UNIQUE_ID = "local:0";
-
+    const KeyboardLayoutInfo DEVICE_KEYBOARD_LAYOUT_INFO = KeyboardLayoutInfo("en-US", "qwerty");
     void prepareDisplay(ui::Rotation orientation);
 
     void testDPadKeyRotation(KeyboardInputMapper& mapper, int32_t originalScanCode,
@@ -3582,6 +3582,24 @@
     ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_CANCELED, args.flags);
 }
 
+TEST_F(KeyboardInputMapperTest, Configure_AssignKeyboardLayoutInfo) {
+    mDevice->addMapper<KeyboardInputMapper>(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD,
+                                            AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    std::list<NotifyArgs> unused =
+            mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0);
+
+    mFakePolicy->addKeyboardLayoutAssociation(DEVICE_LOCATION, DEVICE_KEYBOARD_LAYOUT_INFO);
+
+    unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+                                 InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUT_ASSOCIATION);
+
+    InputDeviceInfo deviceInfo = mDevice->getDeviceInfo();
+    ASSERT_EQ(DEVICE_KEYBOARD_LAYOUT_INFO.languageTag,
+              deviceInfo.getKeyboardLayoutInfo()->languageTag);
+    ASSERT_EQ(DEVICE_KEYBOARD_LAYOUT_INFO.layoutType,
+              deviceInfo.getKeyboardLayoutInfo()->layoutType);
+}
+
 // --- KeyboardInputMapperTest_ExternalDevice ---
 
 class KeyboardInputMapperTest_ExternalDevice : public InputMapperTest {
@@ -4344,7 +4362,7 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
             110.0f, 220.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 110.0f, 220.0f));
+    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
 }
 
 TEST_F(CursorInputMapperTest, Process_PointerCapture) {
@@ -4372,7 +4390,7 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
             10.0f, 20.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 100.0f, 200.0f));
+    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(100.0f, 200.0f));
 
     // Button press.
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1);
@@ -4411,7 +4429,7 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
             30.0f, 40.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 100.0f, 200.0f));
+    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(100.0f, 200.0f));
 
     // Disable pointer capture and check that the device generation got bumped
     // and events are generated the usual way.
@@ -4431,7 +4449,7 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
             110.0f, 220.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 110.0f, 220.0f));
+    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
 }
 
 /**
@@ -4548,7 +4566,7 @@
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
                   WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID),
                   WithCoords(110.0f, 220.0f))));
-    ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 110.0f, 220.0f));
+    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
 }
 
 TEST_F(CursorInputMapperTest, ConfigureDisplayId_WithAssociatedViewport) {
@@ -4575,7 +4593,7 @@
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
                   WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID),
                   WithCoords(110.0f, 220.0f))));
-    ASSERT_NO_FATAL_FAILURE(assertPosition(*mFakePointerController, 110.0f, 220.0f));
+    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
 }
 
 TEST_F(CursorInputMapperTest, ConfigureDisplayId_IgnoresEventsForMismatchedPointerDisplay) {
@@ -6505,6 +6523,17 @@
                   WithCoords(toDisplayX(100), toDisplayY(200)), WithButtonState(0))));
 }
 
+TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsSetToTouchNavigation_setsCorrectType) {
+    mFakePolicy->addDeviceTypeAssociation(DEVICE_LOCATION, "touchNavigation");
+    prepareDisplay(ui::ROTATION_0);
+    prepareButtons();
+    prepareAxes(POSITION);
+    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
+
+    ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mapper.getSources());
+}
+
 // --- TouchDisplayProjectionTest ---
 
 class TouchDisplayProjectionTest : public SingleTouchInputMapperTest {
diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h
index 9db3422..e5a4b14 100644
--- a/services/inputflinger/tests/TestInputListenerMatchers.h
+++ b/services/inputflinger/tests/TestInputListenerMatchers.h
@@ -74,6 +74,14 @@
     return argX == x && argY == y;
 }
 
+MATCHER_P2(WithRelativeMotion, x, y, "InputEvent with specified relative motion") {
+    const auto argX = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+    const auto argY = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+    *result_listener << "expected relative motion (" << x << ", " << y << "), but got (" << argX
+                     << ", " << argY << ")";
+    return argX == x && argY == y;
+}
+
 MATCHER_P(WithPressure, pressure, "InputEvent with specified pressure") {
     const auto argPressure = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
     *result_listener << "expected pressure " << pressure << ", but got " << argPressure;
@@ -97,6 +105,12 @@
     return arg.buttonState == buttons;
 }
 
+MATCHER_P(WithActionButton, actionButton, "InputEvent with specified action button") {
+    *result_listener << "expected action button " << actionButton << ", but got "
+                     << arg.actionButton;
+    return arg.actionButton == actionButton;
+}
+
 MATCHER_P(WithEventTime, eventTime, "InputEvent with specified eventTime") {
     *result_listener << "expected event time " << eventTime << ", but got " << arg.eventTime;
     return arg.eventTime == eventTime;
diff --git a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp
index cc523e1..be85026 100644
--- a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp
@@ -16,11 +16,10 @@
 
 #include <CursorInputMapper.h>
 #include <FuzzContainer.h>
-#include <fuzzer/FuzzedDataProvider.h>
 
 namespace android {
 
-static void addProperty(FuzzContainer& fuzzer, std::shared_ptr<FuzzedDataProvider> fdp) {
+static void addProperty(FuzzContainer& fuzzer, std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp) {
     // Pick a random property to set for the mapper to have set.
     fdp->PickValueInArray<std::function<void()>>(
             {[&]() -> void { fuzzer.addProperty("cursor.mode", "pointer"); },
@@ -35,7 +34,8 @@
 }
 
 extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
-    std::shared_ptr<FuzzedDataProvider> fdp = std::make_shared<FuzzedDataProvider>(data, size);
+    std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp =
+            std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
     FuzzContainer fuzzer(fdp);
 
     CursorInputMapper& mapper = fuzzer.getMapper<CursorInputMapper>();
diff --git a/services/inputflinger/tests/fuzzers/FuzzContainer.h b/services/inputflinger/tests/fuzzers/FuzzContainer.h
index 1e0764f..76d2bcd 100644
--- a/services/inputflinger/tests/fuzzers/FuzzContainer.h
+++ b/services/inputflinger/tests/fuzzers/FuzzContainer.h
@@ -20,7 +20,6 @@
 #include <InputMapper.h>
 #include <InputReader.h>
 #include <MapperHelpers.h>
-#include <fuzzer/FuzzedDataProvider.h>
 
 namespace android {
 
@@ -31,10 +30,10 @@
     std::unique_ptr<FuzzInputReaderContext> mFuzzContext;
     std::unique_ptr<InputDevice> mFuzzDevice;
     InputReaderConfiguration mPolicyConfig;
-    std::shared_ptr<FuzzedDataProvider> mFdp;
+    std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp;
 
 public:
-    FuzzContainer(std::shared_ptr<FuzzedDataProvider> fdp) : mFdp(fdp) {
+    FuzzContainer(std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp) : mFdp(fdp) {
         // Setup parameters.
         std::string deviceName = mFdp->ConsumeRandomLengthString(16);
         std::string deviceLocation = mFdp->ConsumeRandomLengthString(12);
diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
index 057c15d..a80839c 100644
--- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
@@ -170,7 +170,8 @@
 };
 
 extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
-    std::shared_ptr<FuzzedDataProvider> fdp = std::make_shared<FuzzedDataProvider>(data, size);
+    std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp =
+            std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
 
     FuzzInputListener fuzzListener;
     sp<FuzzInputReaderPolicy> fuzzPolicy = sp<FuzzInputReaderPolicy>::make(fdp);
diff --git a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp
index e880f55..8e2d677 100644
--- a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp
@@ -16,13 +16,12 @@
 
 #include <FuzzContainer.h>
 #include <KeyboardInputMapper.h>
-#include <fuzzer/FuzzedDataProvider.h>
 
 namespace android {
 
 const int32_t kMaxKeycodes = 100;
 
-static void addProperty(FuzzContainer& fuzzer, std::shared_ptr<FuzzedDataProvider> fdp) {
+static void addProperty(FuzzContainer& fuzzer, std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp) {
     // Pick a random property to set for the mapper to have set.
     fdp->PickValueInArray<std::function<void()>>(
             {[&]() -> void { fuzzer.addProperty("keyboard.orientationAware", "1"); },
@@ -41,7 +40,8 @@
 }
 
 extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
-    std::shared_ptr<FuzzedDataProvider> fdp = std::make_shared<FuzzedDataProvider>(data, size);
+    std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp =
+            std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
     FuzzContainer fuzzer(fdp);
 
     KeyboardInputMapper& mapper =
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index cd852d6..a0910ea 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -13,13 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 #pragma once
 
 #include <InputDevice.h>
 #include <InputMapper.h>
 #include <InputReader.h>
-#include <fuzzer/FuzzedDataProvider.h>
+#include <ThreadSafeFuzzedDataProvider.h>
 #include "android/hardware/input/InputDeviceCountryCode.h"
 
 using android::hardware::input::InputDeviceCountryCode;
@@ -114,10 +113,10 @@
     InputDeviceIdentifier mIdentifier;
     std::vector<TouchVideoFrame> mVideoFrames;
     PropertyMap mFuzzConfig;
-    std::shared_ptr<FuzzedDataProvider> mFdp;
+    std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp;
 
 public:
-    FuzzEventHub(std::shared_ptr<FuzzedDataProvider> fdp) : mFdp(std::move(fdp)) {}
+    FuzzEventHub(std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp) : mFdp(std::move(fdp)) {}
     ~FuzzEventHub() {}
     void addProperty(std::string key, std::string value) { mFuzzConfig.addProperty(key, value); }
 
@@ -264,10 +263,10 @@
 };
 
 class FuzzPointerController : public PointerControllerInterface {
-    std::shared_ptr<FuzzedDataProvider> mFdp;
+    std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp;
 
 public:
-    FuzzPointerController(std::shared_ptr<FuzzedDataProvider> mFdp) : mFdp(mFdp) {}
+    FuzzPointerController(std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp) : mFdp(mFdp) {}
     ~FuzzPointerController() {}
     bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override {
         return mFdp->ConsumeBool();
@@ -290,13 +289,13 @@
 class FuzzInputReaderPolicy : public InputReaderPolicyInterface {
     TouchAffineTransformation mTransform;
     std::shared_ptr<FuzzPointerController> mPointerController;
-    std::shared_ptr<FuzzedDataProvider> mFdp;
+    std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp;
 
 protected:
     ~FuzzInputReaderPolicy() {}
 
 public:
-    FuzzInputReaderPolicy(std::shared_ptr<FuzzedDataProvider> mFdp) : mFdp(mFdp) {
+    FuzzInputReaderPolicy(std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp) : mFdp(mFdp) {
         mPointerController = std::make_shared<FuzzPointerController>(mFdp);
     }
     void getReaderConfiguration(InputReaderConfiguration* outConfig) override {}
@@ -334,13 +333,13 @@
 class FuzzInputReaderContext : public InputReaderContext {
     std::shared_ptr<EventHubInterface> mEventHub;
     sp<InputReaderPolicyInterface> mPolicy;
-    std::shared_ptr<FuzzedDataProvider> mFdp;
+    std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp;
 
 public:
     FuzzInputReaderContext(std::shared_ptr<EventHubInterface> eventHub,
                            const sp<InputReaderPolicyInterface>& policy,
                            InputListenerInterface& listener,
-                           std::shared_ptr<FuzzedDataProvider> mFdp)
+                           std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp)
           : mEventHub(eventHub), mPolicy(policy), mFdp(mFdp) {}
     ~FuzzInputReaderContext() {}
     void updateGlobalMetaState() override {}
diff --git a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp
index 99fd083..011455b 100644
--- a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp
@@ -16,13 +16,12 @@
 
 #include <FuzzContainer.h>
 #include <MultiTouchInputMapper.h>
-#include <fuzzer/FuzzedDataProvider.h>
 
 namespace android {
 
 const int32_t kMaxKeycodes = 100;
 
-static void addProperty(FuzzContainer& fuzzer, std::shared_ptr<FuzzedDataProvider> fdp) {
+static void addProperty(FuzzContainer& fuzzer, std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp) {
     // Pick a random property to set for the mapper to have set.
     fdp->PickValueInArray<std::function<void()>>(
             {[&]() -> void { fuzzer.addProperty("touch.deviceType", "touchScreen"); },
@@ -58,7 +57,8 @@
 }
 
 extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
-    std::shared_ptr<FuzzedDataProvider> fdp = std::make_shared<FuzzedDataProvider>(data, size);
+    std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp =
+            std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
     FuzzContainer fuzzer(fdp);
 
     MultiTouchInputMapper& mapper = fuzzer.getMapper<MultiTouchInputMapper>();
diff --git a/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp
index 7416ce9..c4938f2 100644
--- a/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp
@@ -16,12 +16,12 @@
 
 #include <FuzzContainer.h>
 #include <SwitchInputMapper.h>
-#include <fuzzer/FuzzedDataProvider.h>
 
 namespace android {
 
 extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
-    std::shared_ptr<FuzzedDataProvider> fdp = std::make_shared<FuzzedDataProvider>(data, size);
+    std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp =
+            std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
     FuzzContainer fuzzer(fdp);
 
     SwitchInputMapper& mapper = fuzzer.getMapper<SwitchInputMapper>();
diff --git a/services/inputflinger/tests/fuzzers/ThreadSafeFuzzedDataProvider.h b/services/inputflinger/tests/fuzzers/ThreadSafeFuzzedDataProvider.h
new file mode 100644
index 0000000..2f76f18
--- /dev/null
+++ b/services/inputflinger/tests/fuzzers/ThreadSafeFuzzedDataProvider.h
@@ -0,0 +1,136 @@
+/*
+ * 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 <fuzzer/FuzzedDataProvider.h>
+
+/**
+ * A thread-safe interface to the FuzzedDataProvider
+ */
+class ThreadSafeFuzzedDataProvider : FuzzedDataProvider {
+private:
+    std::mutex mLock;
+
+public:
+    ThreadSafeFuzzedDataProvider(const uint8_t* data, size_t size)
+          : FuzzedDataProvider(data, size) {}
+
+    template <typename T>
+    std::vector<T> ConsumeBytes(size_t num_bytes) {
+        std::scoped_lock _l(mLock);
+        return FuzzedDataProvider::ConsumeBytes<T>(num_bytes);
+    }
+
+    template <typename T>
+    std::vector<T> ConsumeBytesWithTerminator(size_t num_bytes, T terminator) {
+        std::scoped_lock _l(mLock);
+        return FuzzedDataProvider::ConsumeBytesWithTerminator<T>(num_bytes, terminator);
+    }
+
+    template <typename T>
+    std::vector<T> ConsumeRemainingBytes() {
+        std::scoped_lock _l(mLock);
+        return FuzzedDataProvider::ConsumeRemainingBytes<T>();
+    }
+
+    std::string ConsumeBytesAsString(size_t num_bytes) {
+        std::scoped_lock _l(mLock);
+        return FuzzedDataProvider::ConsumeBytesAsString(num_bytes);
+    }
+
+    std::string ConsumeRandomLengthString(size_t max_length) {
+        std::scoped_lock _l(mLock);
+        return FuzzedDataProvider::ConsumeRandomLengthString(max_length);
+    }
+
+    std::string ConsumeRandomLengthString() {
+        std::scoped_lock _l(mLock);
+        return FuzzedDataProvider::ConsumeRandomLengthString();
+    }
+
+    std::string ConsumeRemainingBytesAsString() {
+        std::scoped_lock _l(mLock);
+        return FuzzedDataProvider::ConsumeRemainingBytesAsString();
+    }
+
+    template <typename T>
+    T ConsumeIntegral() {
+        std::scoped_lock _l(mLock);
+        return FuzzedDataProvider::ConsumeIntegral<T>();
+    }
+
+    template <typename T>
+    T ConsumeIntegralInRange(T min, T max) {
+        std::scoped_lock _l(mLock);
+        return FuzzedDataProvider::ConsumeIntegralInRange<T>(min, max);
+    }
+
+    template <typename T>
+    T ConsumeFloatingPoint() {
+        std::scoped_lock _l(mLock);
+        return FuzzedDataProvider::ConsumeFloatingPoint<T>();
+    }
+
+    template <typename T>
+    T ConsumeFloatingPointInRange(T min, T max) {
+        std::scoped_lock _l(mLock);
+        return FuzzedDataProvider::ConsumeFloatingPointInRange<T>(min, max);
+    }
+
+    template <typename T>
+    T ConsumeProbability() {
+        std::scoped_lock _l(mLock);
+        return FuzzedDataProvider::ConsumeProbability<T>();
+    }
+
+    bool ConsumeBool() {
+        std::scoped_lock _l(mLock);
+        return FuzzedDataProvider::ConsumeBool();
+    }
+
+    template <typename T>
+    T ConsumeEnum() {
+        std::scoped_lock _l(mLock);
+        return FuzzedDataProvider::ConsumeEnum<T>();
+    }
+
+    template <typename T, size_t size>
+    T PickValueInArray(const T (&array)[size]) {
+        std::scoped_lock _l(mLock);
+        return FuzzedDataProvider::PickValueInArray(array);
+    }
+
+    template <typename T, size_t size>
+    T PickValueInArray(const std::array<T, size>& array) {
+        std::scoped_lock _l(mLock);
+        return FuzzedDataProvider::PickValueInArray(array);
+    }
+
+    template <typename T>
+    T PickValueInArray(std::initializer_list<const T> list) {
+        std::scoped_lock _l(mLock);
+        return FuzzedDataProvider::PickValueInArray(list);
+    }
+
+    size_t ConsumeData(void* destination, size_t num_bytes) {
+        std::scoped_lock _l(mLock);
+        return FuzzedDataProvider::ConsumeData(destination, num_bytes);
+    }
+
+    size_t remaining_bytes() {
+        std::scoped_lock _l(mLock);
+        return FuzzedDataProvider::remaining_bytes();
+    }
+};
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 0a192c5..e0dcab5 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -26,8 +26,8 @@
     name: "libsurfaceflinger_defaults",
     defaults: [
         "android.hardware.graphics.composer3-ndk_shared",
+        "librenderengine_deps",
         "surfaceflinger_defaults",
-        "skia_renderengine_deps",
     ],
     cflags: [
         "-DLOG_TAG=\"SurfaceFlinger\"",
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index 30d34a5..f3a0186 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -11,6 +11,7 @@
     name: "libcompositionengine_defaults",
     defaults: [
         "android.hardware.graphics.composer3-ndk_shared",
+        "librenderengine_deps",
         "surfaceflinger_defaults",
     ],
     cflags: [
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h
index 6e9ea6f..b6a4240 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h
@@ -83,7 +83,7 @@
     // If the buffer is already in the cache, the buffer is null to optimize away sending HWC the
     // buffer handle.
     //
-    HwcSlotAndBuffer getHwcSlotAndBufferForOverride(const sp<GraphicBuffer>& buffer);
+    HwcSlotAndBuffer getOverrideHwcSlotAndBuffer(const sp<GraphicBuffer>& buffer);
 
     //
     // When a client process discards a buffer, it needs to be purged from the HWC cache.
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
index 2b383c1..2a5bfae 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
@@ -136,6 +136,9 @@
         // cost of sending reused buffers to the HWC.
         HwcBufferCache hwcBufferCache;
 
+        // The previously-active buffer for this layer.
+        uint32_t activeBufferSlot;
+
         // Set to true when overridden info has been sent to HW composer
         bool stateOverridden = false;
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
index 14922a4..12e063b 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
@@ -36,6 +36,7 @@
     // sp<StrictMock<LayerFE>>::make()
     friend class sp<LayerFE>;
     friend class testing::StrictMock<LayerFE>;
+    friend class testing::NiceMock<LayerFE>;
 
 public:
     virtual ~LayerFE();
diff --git a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp
index d64fd57..34ed214 100644
--- a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp
@@ -22,7 +22,7 @@
 namespace android::compositionengine::impl {
 
 HwcBufferCache::HwcBufferCache() {
-    for (uint32_t i = 0; i < kMaxLayerBufferCount; i++) {
+    for (uint32_t i = kMaxLayerBufferCount; i-- > 0;) {
         mFreeSlots.push(i);
     }
 }
@@ -42,7 +42,7 @@
     return {cache(buffer), buffer};
 }
 
-HwcSlotAndBuffer HwcBufferCache::getHwcSlotAndBufferForOverride(const sp<GraphicBuffer>& buffer) {
+HwcSlotAndBuffer HwcBufferCache::getOverrideHwcSlotAndBuffer(const sp<GraphicBuffer>& buffer) {
     if (buffer == mLastOverrideBuffer) {
         return {kOverrideBufferSlot, nullptr};
     }
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index a7c24b6..766d7ea 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -610,16 +610,26 @@
     }
 }
 
-void OutputLayer::uncacheBuffers(std::vector<uint64_t> const& bufferIdsToUncache) {
+void OutputLayer::uncacheBuffers(const std::vector<uint64_t>& bufferIdsToUncache) {
     auto& state = editState();
     // Skip doing this if there is no HWC interface
     if (!state.hwc) {
         return;
     }
 
-    for (auto bufferId : bufferIdsToUncache) {
-        state.hwc->hwcBufferCache.uncache(bufferId);
-        // TODO(b/258196272): send uncache requests to Composer HAL
+    std::vector<uint32_t> slotsToClear;
+    for (uint64_t bufferId : bufferIdsToUncache) {
+        uint32_t slot = state.hwc->hwcBufferCache.uncache(bufferId);
+        if (slot != UINT32_MAX) {
+            slotsToClear.push_back(slot);
+        }
+    }
+
+    hal::Error error =
+            state.hwc->hwcLayer->setBufferSlotsToClear(slotsToClear, state.hwc->activeBufferSlot);
+    if (error != hal::Error::NONE) {
+        ALOGE("[%s] Failed to clear buffer slots: %s (%d)", getLayerFE().getDebugName(),
+              to_string(error).c_str(), static_cast<int32_t>(error));
     }
 }
 
@@ -637,15 +647,25 @@
 
     HwcSlotAndBuffer hwcSlotAndBuffer;
     sp<Fence> hwcFence;
-    // Override buffers use a special cache slot so that they don't evict client buffers.
-    if (getState().overrideInfo.buffer != nullptr && !skipLayer) {
-        hwcSlotAndBuffer = editState().hwc->hwcBufferCache.getHwcSlotAndBufferForOverride(
-                getState().overrideInfo.buffer->getBuffer());
-        hwcFence = getState().overrideInfo.acquireFence;
-    } else {
-        hwcSlotAndBuffer =
-                editState().hwc->hwcBufferCache.getHwcSlotAndBuffer(outputIndependentState.buffer);
-        hwcFence = outputIndependentState.acquireFence;
+    {
+        // Editing the state only because we update the HWC buffer cache and active buffer.
+        auto& state = editState();
+        // Override buffers use a special cache slot so that they don't evict client buffers.
+        if (state.overrideInfo.buffer != nullptr && !skipLayer) {
+            hwcSlotAndBuffer = state.hwc->hwcBufferCache.getOverrideHwcSlotAndBuffer(
+                    state.overrideInfo.buffer->getBuffer());
+            hwcFence = state.overrideInfo.acquireFence;
+        } else {
+            hwcSlotAndBuffer =
+                    state.hwc->hwcBufferCache.getHwcSlotAndBuffer(outputIndependentState.buffer);
+            hwcFence = outputIndependentState.acquireFence;
+        }
+
+        // Keep track of the active buffer slot, so we can restore it after clearing other buffer
+        // slots.
+        if (hwcSlotAndBuffer.buffer) {
+            state.hwc->activeBufferSlot = hwcSlotAndBuffer.slot;
+        }
     }
 
     if (auto error = hwcLayer->setBuffer(hwcSlotAndBuffer.slot, hwcSlotAndBuffer.buffer, hwcFence);
diff --git a/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp b/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp
index cf72e8f..c5fb594 100644
--- a/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp
@@ -115,20 +115,20 @@
     EXPECT_EQ(cache.uncache(mBuffer2->getId()), UINT32_MAX);
 }
 
-TEST_F(HwcBufferCacheTest, getHwcSlotAndBufferForOverride_whenCached_returnsSameSlotAndNullBuffer) {
+TEST_F(HwcBufferCacheTest, getOverrideHwcSlotAndBuffer_whenCached_returnsSameSlotAndNullBuffer) {
     HwcBufferCache cache;
     sp<GraphicBuffer> outBuffer;
 
-    HwcSlotAndBuffer originalSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1);
+    HwcSlotAndBuffer originalSlotAndBuffer = cache.getOverrideHwcSlotAndBuffer(mBuffer1);
     EXPECT_NE(originalSlotAndBuffer.slot, UINT32_MAX);
     EXPECT_EQ(originalSlotAndBuffer.buffer, mBuffer1);
 
-    HwcSlotAndBuffer finalSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1);
+    HwcSlotAndBuffer finalSlotAndBuffer = cache.getOverrideHwcSlotAndBuffer(mBuffer1);
     EXPECT_EQ(finalSlotAndBuffer.slot, originalSlotAndBuffer.slot);
     EXPECT_EQ(finalSlotAndBuffer.buffer, nullptr);
 }
 
-TEST_F(HwcBufferCacheTest, getHwcSlotAndBufferForOverride_whenSlotsFull_returnsIndependentSlot) {
+TEST_F(HwcBufferCacheTest, getOverrideHwcSlotAndBuffer_whenSlotsFull_returnsIndependentSlot) {
     HwcBufferCache cache;
     sp<GraphicBuffer> outBuffer;
 
@@ -149,7 +149,7 @@
 
     sp<GraphicBuffer> overrideBuffer =
             sp<GraphicBuffer>::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u);
-    HwcSlotAndBuffer overrideSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(overrideBuffer);
+    HwcSlotAndBuffer overrideSlotAndBuffer = cache.getOverrideHwcSlotAndBuffer(overrideBuffer);
     // expect us to have a slot number
     EXPECT_NE(overrideSlotAndBuffer.slot, UINT32_MAX);
     // expect this to be the first time we cached the buffer
@@ -173,7 +173,7 @@
     HwcBufferCache cache;
     sp<GraphicBuffer> outBuffer;
 
-    HwcSlotAndBuffer hwcSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1);
+    HwcSlotAndBuffer hwcSlotAndBuffer = cache.getOverrideHwcSlotAndBuffer(mBuffer1);
     ASSERT_NE(hwcSlotAndBuffer.slot, UINT32_MAX);
 
     EXPECT_EQ(cache.uncache(mBuffer1->getId()), hwcSlotAndBuffer.slot);
@@ -184,7 +184,7 @@
     HwcBufferCache cache;
     sp<GraphicBuffer> outBuffer;
 
-    HwcSlotAndBuffer hwcSlotAndBuffer = cache.getHwcSlotAndBufferForOverride(mBuffer1);
+    HwcSlotAndBuffer hwcSlotAndBuffer = cache.getOverrideHwcSlotAndBuffer(mBuffer1);
     ASSERT_NE(hwcSlotAndBuffer.slot, UINT32_MAX);
 
     EXPECT_EQ(cache.uncache(mBuffer2->getId()), UINT32_MAX);
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h b/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h
index d933b94..b0b1a02 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h
@@ -56,6 +56,7 @@
     MOCK_METHOD3(setBuffer,
                  Error(uint32_t, const android::sp<android::GraphicBuffer>&,
                        const android::sp<android::Fence>&));
+    MOCK_METHOD2(setBufferSlotsToClear, Error(const std::vector<uint32_t>&, uint32_t));
     MOCK_METHOD1(setSurfaceDamage, Error(const android::Region&));
     MOCK_METHOD1(setBlendMode, Error(hal::BlendMode));
     MOCK_METHOD1(setColor, Error(aidl::android::hardware::graphics::composer3::Color));
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index 03cf292..0edc226 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -42,6 +42,8 @@
 
 using testing::_;
 using testing::InSequence;
+using testing::Mock;
+using testing::NiceMock;
 using testing::Return;
 using testing::ReturnRef;
 using testing::StrictMock;
@@ -82,13 +84,13 @@
 
 struct OutputLayerTest : public testing::Test {
     struct OutputLayer final : public impl::OutputLayer {
-        OutputLayer(const compositionengine::Output& output, sp<compositionengine::LayerFE> layerFE)
+        OutputLayer(const compositionengine::Output& output, compositionengine::LayerFE& layerFE)
               : mOutput(output), mLayerFE(layerFE) {}
         ~OutputLayer() override = default;
 
         // compositionengine::OutputLayer overrides
         const compositionengine::Output& getOutput() const override { return mOutput; }
-        compositionengine::LayerFE& getLayerFE() const override { return *mLayerFE; }
+        compositionengine::LayerFE& getLayerFE() const override { return mLayerFE; }
         const impl::OutputLayerCompositionState& getState() const override { return mState; }
         impl::OutputLayerCompositionState& editState() override { return mState; }
 
@@ -96,21 +98,22 @@
         void dumpState(std::string& out) const override { mState.dump(out); }
 
         const compositionengine::Output& mOutput;
-        sp<compositionengine::LayerFE> mLayerFE;
+        compositionengine::LayerFE& mLayerFE;
         impl::OutputLayerCompositionState mState;
     };
 
     OutputLayerTest() {
-        EXPECT_CALL(*mLayerFE, getDebugName()).WillRepeatedly(Return("Test LayerFE"));
-        EXPECT_CALL(mOutput, getName()).WillRepeatedly(ReturnRef(kOutputName));
+        ON_CALL(mLayerFE, getDebugName()).WillByDefault(Return("Test LayerFE"));
+        ON_CALL(mOutput, getName()).WillByDefault(ReturnRef(kOutputName));
 
-        EXPECT_CALL(*mLayerFE, getCompositionState()).WillRepeatedly(Return(&mLayerFEState));
-        EXPECT_CALL(mOutput, getState()).WillRepeatedly(ReturnRef(mOutputState));
+        ON_CALL(mLayerFE, getCompositionState()).WillByDefault(Return(&mLayerFEState));
+        ON_CALL(mOutput, getState()).WillByDefault(ReturnRef(mOutputState));
     }
 
-    compositionengine::mock::Output mOutput;
-    sp<StrictMock<compositionengine::mock::LayerFE>> mLayerFE =
-            sp<StrictMock<compositionengine::mock::LayerFE>>::make();
+    NiceMock<compositionengine::mock::Output> mOutput;
+    sp<NiceMock<compositionengine::mock::LayerFE>> mLayerFE_ =
+            sp<NiceMock<compositionengine::mock::LayerFE>>::make();
+    NiceMock<compositionengine::mock::LayerFE>& mLayerFE = *mLayerFE_;
     OutputLayer mOutputLayer{mOutput, mLayerFE};
 
     LayerFECompositionState mLayerFEState;
@@ -530,7 +533,7 @@
 
 struct OutputLayerPartialMockForUpdateCompositionState : public impl::OutputLayer {
     OutputLayerPartialMockForUpdateCompositionState(const compositionengine::Output& output,
-                                                    sp<compositionengine::LayerFE> layerFE)
+                                                    compositionengine::LayerFE& layerFE)
           : mOutput(output), mLayerFE(layerFE) {}
     // Mock everything called by updateCompositionState to simplify testing it.
     MOCK_CONST_METHOD1(calculateOutputSourceCrop, FloatRect(uint32_t));
@@ -539,7 +542,7 @@
 
     // compositionengine::OutputLayer overrides
     const compositionengine::Output& getOutput() const override { return mOutput; }
-    compositionengine::LayerFE& getLayerFE() const override { return *mLayerFE; }
+    compositionengine::LayerFE& getLayerFE() const override { return mLayerFE; }
     const impl::OutputLayerCompositionState& getState() const override { return mState; }
     impl::OutputLayerCompositionState& editState() override { return mState; }
 
@@ -547,7 +550,7 @@
     MOCK_CONST_METHOD1(dumpState, void(std::string&));
 
     const compositionengine::Output& mOutput;
-    sp<compositionengine::LayerFE> mLayerFE;
+    compositionengine::LayerFE& mLayerFE;
     impl::OutputLayerCompositionState mState;
 };
 
@@ -588,7 +591,7 @@
 };
 
 TEST_F(OutputLayerUpdateCompositionStateTest, doesNothingIfNoFECompositionState) {
-    EXPECT_CALL(*mLayerFE, getCompositionState()).WillOnce(Return(nullptr));
+    EXPECT_CALL(mLayerFE, getCompositionState()).WillOnce(Return(nullptr));
 
     mOutputLayer.updateCompositionState(true, false, ui::Transform::RotationFlags::ROT_90);
 }
@@ -833,7 +836,6 @@
         EXPECT_CALL(mDisplayColorProfile, getSupportedPerFrameMetadata())
                 .WillRepeatedly(Return(kSupportedPerFrameMetadata));
     }
-
     // Some tests may need to simulate unsupported HWC calls
     enum class SimulateUnsupported { None, ColorTransform };
 
@@ -952,7 +954,10 @@
 const HdrMetadata OutputLayerWriteStateToHWCTest::kHdrMetadata{{/* LightFlattenable */}, 1029};
 native_handle_t* OutputLayerWriteStateToHWCTest::kSidebandStreamHandle =
         reinterpret_cast<native_handle_t*>(1031);
-const sp<GraphicBuffer> OutputLayerWriteStateToHWCTest::kBuffer;
+const sp<GraphicBuffer> OutputLayerWriteStateToHWCTest::kBuffer =
+        sp<GraphicBuffer>::make(1, 2, PIXEL_FORMAT_RGBA_8888,
+                                AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
+                                        AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN);
 const sp<GraphicBuffer> OutputLayerWriteStateToHWCTest::kOverrideBuffer =
         sp<GraphicBuffer>::make(4, 5, PIXEL_FORMAT_RGBA_8888,
                                 AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
@@ -968,7 +973,7 @@
         {4, 5, 6, 7}};
 
 TEST_F(OutputLayerWriteStateToHWCTest, doesNothingIfNoFECompositionState) {
-    EXPECT_CALL(*mLayerFE, getCompositionState()).WillOnce(Return(nullptr));
+    EXPECT_CALL(mLayerFE, getCompositionState()).WillOnce(Return(nullptr));
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
@@ -993,7 +998,7 @@
     expectPerFrameCommonCalls();
 
     expectNoSetCompositionTypeCall();
-    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
+    EXPECT_CALL(mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
@@ -1018,7 +1023,6 @@
     mLayerFEState.compositionType = Composition::SOLID_COLOR;
 
     expectPerFrameCommonCalls();
-    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
 
     // Setting the composition type should happen before setting the color. We
     // check this in this test only by setting up an testing::InSeqeuence
@@ -1038,8 +1042,6 @@
     expectSetSidebandHandleCall();
     expectSetCompositionTypeCall(Composition::SIDEBAND);
 
-    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
-
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
 }
@@ -1051,8 +1053,6 @@
     expectSetHdrMetadataAndBufferCalls();
     expectSetCompositionTypeCall(Composition::CURSOR);
 
-    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
-
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
 }
@@ -1064,8 +1064,6 @@
     expectSetHdrMetadataAndBufferCalls();
     expectSetCompositionTypeCall(Composition::DEVICE);
 
-    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
-
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
 }
@@ -1079,8 +1077,6 @@
     expectSetColorCall();
     expectNoSetCompositionTypeCall();
 
-    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
-
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
 }
@@ -1119,8 +1115,6 @@
     expectGenericLayerMetadataCalls();
     expectSetCompositionTypeCall(Composition::DEVICE);
 
-    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
-
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
 }
@@ -1133,8 +1127,6 @@
     expectSetHdrMetadataAndBufferCalls();
     expectSetCompositionTypeCall(Composition::DEVICE);
 
-    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
-
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
 }
@@ -1149,7 +1141,6 @@
                               kOverrideSurfaceDamage, kOverrideLayerBrightness);
     expectSetHdrMetadataAndBufferCalls();
     expectSetCompositionTypeCall(Composition::DEVICE);
-    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false));
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ true, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
@@ -1165,7 +1156,6 @@
                               kOverrideSurfaceDamage, kOverrideLayerBrightness);
     expectSetHdrMetadataAndBufferCalls();
     expectSetCompositionTypeCall(Composition::DEVICE);
-    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false));
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ true, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
@@ -1181,7 +1171,6 @@
                               kOverrideSurfaceDamage, kOverrideLayerBrightness);
     expectSetHdrMetadataAndBufferCalls(kOverrideHwcSlot, kOverrideBuffer, kOverrideFence);
     expectSetCompositionTypeCall(Composition::DEVICE);
-    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false));
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
@@ -1197,7 +1186,6 @@
                               kOverrideSurfaceDamage, kOverrideLayerBrightness);
     expectSetHdrMetadataAndBufferCalls(kOverrideHwcSlot, kOverrideBuffer, kOverrideFence);
     expectSetCompositionTypeCall(Composition::DEVICE);
-    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false));
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
@@ -1212,7 +1200,6 @@
                               Region::INVALID_REGION);
     expectSetHdrMetadataAndBufferCalls();
     expectSetCompositionTypeCall(Composition::DEVICE);
-    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false));
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
@@ -1229,7 +1216,6 @@
                               Region::INVALID_REGION);
     expectSetHdrMetadataAndBufferCalls();
     expectSetCompositionTypeCall(Composition::DEVICE);
-    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
@@ -1247,22 +1233,20 @@
                               Region::INVALID_REGION);
     expectSetHdrMetadataAndBufferCalls();
     expectSetCompositionTypeCall(Composition::CLIENT);
-    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false));
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, peekThroughChangesBlendMode) {
-    auto peekThroughLayerFE = sp<compositionengine::mock::LayerFE>::make();
-    OutputLayer peekThroughLayer{mOutput, peekThroughLayerFE};
+    auto peekThroughLayerFE = sp<NiceMock<compositionengine::mock::LayerFE>>::make();
+    OutputLayer peekThroughLayer{mOutput, *peekThroughLayerFE};
 
     mOutputLayer.mState.overrideInfo.peekThroughLayer = &peekThroughLayer;
 
     expectGeometryCommonCalls(kDisplayFrame, kSourceCrop, kBufferTransform,
                               Hwc2::IComposerClient::BlendMode::PREMULTIPLIED);
     expectPerFrameCommonCalls();
-    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
@@ -1280,7 +1264,6 @@
 TEST_F(OutputLayerWriteStateToHWCTest, zIsOverriddenSetsOverride) {
     expectGeometryCommonCalls();
     expectPerFrameCommonCalls();
-    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ true, /*isPeekingThrough*/
@@ -1291,7 +1274,7 @@
 TEST_F(OutputLayerWriteStateToHWCTest, roundedCornersForceClientComposition) {
     expectGeometryCommonCalls();
     expectPerFrameCommonCalls();
-    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(true));
+    EXPECT_CALL(mLayerFE, hasRoundedCorners()).WillOnce(Return(true));
     expectSetCompositionTypeCall(Composition::CLIENT);
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
@@ -1303,7 +1286,7 @@
     expectGeometryCommonCalls();
     expectPerFrameCommonCalls();
     expectSetHdrMetadataAndBufferCalls();
-    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(true));
+    EXPECT_CALL(mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(true));
     expectSetCompositionTypeCall(Composition::DEVICE);
 
     mLayerFEState.compositionType = Composition::DEVICE;
@@ -1322,7 +1305,6 @@
     expectPerFrameCommonCalls(SimulateUnsupported::None, kDataspace, kOutputSpaceVisibleRegion,
                               kSurfaceDamage, kLayerBrightness, blockingRegion);
     expectSetHdrMetadataAndBufferCalls();
-    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false));
     expectSetCompositionTypeCall(Composition::DISPLAY_DECORATION);
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
@@ -1331,6 +1313,76 @@
 }
 
 /*
+ * OutputLayer::uncacheBuffers
+ */
+struct OutputLayerUncacheBufferTest : public OutputLayerTest {
+    static const sp<GraphicBuffer> kBuffer1;
+    static const sp<GraphicBuffer> kBuffer2;
+    static const sp<Fence> kFence;
+
+    OutputLayerUncacheBufferTest() {
+        auto& outputLayerState = mOutputLayer.editState();
+        outputLayerState.hwc = impl::OutputLayerCompositionState::Hwc(mHwcLayer_);
+
+        mLayerFEState.compositionType = Composition::DEVICE;
+        mLayerFEState.acquireFence = kFence;
+
+        ON_CALL(mOutput, getDisplayColorProfile()).WillByDefault(Return(&mDisplayColorProfile));
+    }
+
+    std::shared_ptr<HWC2::mock::Layer> mHwcLayer_{std::make_shared<NiceMock<HWC2::mock::Layer>>()};
+    HWC2::mock::Layer& mHwcLayer = *mHwcLayer_;
+    NiceMock<mock::DisplayColorProfile> mDisplayColorProfile;
+};
+
+const sp<GraphicBuffer> OutputLayerUncacheBufferTest::kBuffer1 =
+        sp<GraphicBuffer>::make(1, 2, PIXEL_FORMAT_RGBA_8888,
+                                AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
+                                        AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN);
+const sp<GraphicBuffer> OutputLayerUncacheBufferTest::kBuffer2 =
+        sp<GraphicBuffer>::make(2, 3, PIXEL_FORMAT_RGBA_8888,
+                                AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
+                                        AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN);
+const sp<Fence> OutputLayerUncacheBufferTest::kFence = sp<Fence>::make();
+
+TEST_F(OutputLayerUncacheBufferTest, canUncacheAndReuseSlot) {
+    // Buffer1 is stored in slot 0
+    mLayerFEState.buffer = kBuffer1;
+    EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 0, kBuffer1, kFence));
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+    Mock::VerifyAndClearExpectations(&mHwcLayer);
+
+    // Buffer2 is stored in slot 1
+    mLayerFEState.buffer = kBuffer2;
+    EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 1, kBuffer2, kFence));
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+    Mock::VerifyAndClearExpectations(&mHwcLayer);
+
+    // buffer slots are cleared in HWC
+    std::vector<uint32_t> slotsToClear = {0, 1};
+    EXPECT_CALL(mHwcLayer,
+                setBufferSlotsToClear(/*slotsToClear*/ slotsToClear, /*activeBufferSlot*/ 1));
+    mOutputLayer.uncacheBuffers({kBuffer1->getId(), kBuffer2->getId()});
+    Mock::VerifyAndClearExpectations(&mHwcLayer);
+
+    // rather than allocating a new slot, the active buffer slot (slot 1) is reused first to free
+    // the memory as soon as possible
+    mLayerFEState.buffer = kBuffer1;
+    EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 1, kBuffer1, kFence));
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+    Mock::VerifyAndClearExpectations(&mHwcLayer);
+
+    // rather than allocating a new slot, slot 0 is reused
+    mLayerFEState.buffer = kBuffer2;
+    EXPECT_CALL(mHwcLayer, setBuffer(/*slot*/ 0, kBuffer2, kFence));
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+}
+
+/*
  * OutputLayer::writeCursorPositionToHWC()
  */
 
@@ -1358,7 +1410,7 @@
 const Rect OutputLayerWriteCursorPositionToHWCTest::kDefaultCursorFrame{1, 2, 3, 4};
 
 TEST_F(OutputLayerWriteCursorPositionToHWCTest, doesNothingIfNoFECompositionState) {
-    EXPECT_CALL(*mLayerFE, getCompositionState()).WillOnce(Return(nullptr));
+    EXPECT_CALL(mLayerFE, getCompositionState()).WillOnce(Return(nullptr));
 
     mOutputLayer.writeCursorPositionToHWC();
 }
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 46b857b..96ae77f 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -452,7 +452,8 @@
     }
 }
 
-auto DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info) -> DesiredActiveModeAction {
+auto DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info, bool force)
+        -> DesiredActiveModeAction {
     ATRACE_CALL();
 
     LOG_ALWAYS_FATAL_IF(!info.modeOpt, "desired mode not provided");
@@ -473,7 +474,7 @@
     const auto& desiredMode = *info.modeOpt->modePtr;
 
     // Check if we are already at the desired mode
-    if (refreshRateSelector().getActiveMode().modePtr->getId() == desiredMode.getId()) {
+    if (!force && refreshRateSelector().getActiveMode().modePtr->getId() == desiredMode.getId()) {
         if (refreshRateSelector().getActiveMode() == info.modeOpt) {
             return DesiredActiveModeAction::None;
         }
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 8f9b2a1..d757673 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -210,7 +210,8 @@
         InitiateDisplayModeSwitch,
         InitiateRenderRateSwitch
     };
-    DesiredActiveModeAction setDesiredActiveMode(const ActiveModeInfo&) EXCLUDES(mActiveModeLock);
+    DesiredActiveModeAction setDesiredActiveMode(const ActiveModeInfo&, bool force = false)
+            EXCLUDES(mActiveModeLock);
     std::optional<ActiveModeInfo> getDesiredActiveMode() const EXCLUDES(mActiveModeLock);
     void clearDesiredActiveModeState() EXCLUDES(mActiveModeLock);
     ActiveModeInfo getUpcomingActiveMode() const REQUIRES(kMainThreadContext) {
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 806df87..e372b72 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -233,6 +233,25 @@
 
     addReader(translate<Display>(kSingleReaderKey));
 
+    // If unable to read interface version, then become backwards compatible.
+    int32_t version = 1;
+    const auto status = mAidlComposerClient->getInterfaceVersion(&version);
+    if (!status.isOk()) {
+        ALOGE("getInterfaceVersion for AidlComposer constructor failed %s",
+              status.getDescription().c_str());
+    }
+    if (version == 1) {
+        mClearSlotBuffer = sp<GraphicBuffer>::make(1, 1, PIXEL_FORMAT_RGBX_8888,
+                                                   GraphicBuffer::USAGE_HW_COMPOSER |
+                                                           GraphicBuffer::USAGE_SW_READ_OFTEN |
+                                                           GraphicBuffer::USAGE_SW_WRITE_OFTEN,
+                                                   "AidlComposer");
+        if (!mClearSlotBuffer || mClearSlotBuffer->initCheck() != ::android::OK) {
+            LOG_ALWAYS_FATAL("Failed to allocate a buffer for clearing layer buffer slots");
+            return;
+        }
+    }
+
     ALOGI("Loaded AIDL composer3 HAL service");
 }
 
@@ -814,6 +833,49 @@
     return error;
 }
 
+Error AidlComposer::setLayerBufferSlotsToClear(Display display, Layer layer,
+                                               const std::vector<uint32_t>& slotsToClear,
+                                               uint32_t activeBufferSlot) {
+    if (slotsToClear.empty()) {
+        return Error::NONE;
+    }
+
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        // Backwards compatible way of clearing buffer is to set the layer buffer with a placeholder
+        // buffer, using the slot that needs to cleared... tricky.
+        if (mClearSlotBuffer == nullptr) {
+            writer->get().setLayerBufferSlotsToClear(translate<int64_t>(display),
+                                                     translate<int64_t>(layer), slotsToClear);
+        } else {
+            for (uint32_t slot : slotsToClear) {
+                // Don't clear the active buffer slot because we need to restore the active buffer
+                // after clearing the requested buffer slots with a placeholder buffer.
+                if (slot != activeBufferSlot) {
+                    writer->get().setLayerBufferWithNewCommand(translate<int64_t>(display),
+                                                               translate<int64_t>(layer), slot,
+                                                               mClearSlotBuffer->handle,
+                                                               /*fence*/ -1);
+                }
+            }
+            // Since we clear buffers by setting them to a placeholder buffer, we want to make
+            // sure that the last setLayerBuffer command is sent with the currently active
+            // buffer, not the placeholder buffer, so that there is no perceptual change when
+            // buffers are discarded.
+            writer->get().setLayerBufferWithNewCommand(translate<int64_t>(display),
+                                                       translate<int64_t>(layer), activeBufferSlot,
+                                                       // The active buffer is still cached in
+                                                       // its slot and doesn't need a fence.
+                                                       /*buffer*/ nullptr, /*fence*/ -1);
+        }
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
+}
+
 Error AidlComposer::setLayerSurfaceDamage(Display display, Layer layer,
                                           const std::vector<IComposerClient::Rect>& damage) {
     Error error = Error::NONE;
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
index 2a043fd..9a7ade7 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -143,6 +143,9 @@
     /* see setClientTarget for the purpose of slot */
     Error setLayerBuffer(Display display, Layer layer, uint32_t slot,
                          const sp<GraphicBuffer>& buffer, int acquireFence) override;
+    Error setLayerBufferSlotsToClear(Display display, Layer layer,
+                                     const std::vector<uint32_t>& slotsToClear,
+                                     uint32_t activeBufferSlot) override;
     Error setLayerSurfaceDamage(Display display, Layer layer,
                                 const std::vector<IComposerClient::Rect>& damage) override;
     Error setLayerBlendMode(Display display, Layer layer, IComposerClient::BlendMode mode) override;
@@ -280,6 +283,9 @@
     // threading annotations.
     ftl::SharedMutex mMutex;
 
+    // Buffer slots for layers are cleared by setting the slot buffer to this buffer.
+    sp<GraphicBuffer> mClearSlotBuffer;
+
     // Aidl interface
     using AidlIComposer = aidl::android::hardware::graphics::composer3::IComposer;
     using AidlIComposerClient = aidl::android::hardware::graphics::composer3::IComposerClient;
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index ec23935..1c2b8b5 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -179,6 +179,9 @@
     /* see setClientTarget for the purpose of slot */
     virtual Error setLayerBuffer(Display display, Layer layer, uint32_t slot,
                                  const sp<GraphicBuffer>& buffer, int acquireFence) = 0;
+    virtual Error setLayerBufferSlotsToClear(Display display, Layer layer,
+                                             const std::vector<uint32_t>& slotsToClear,
+                                             uint32_t activeBufferSlot) = 0;
     virtual Error setLayerSurfaceDamage(Display display, Layer layer,
                                         const std::vector<IComposerClient::Rect>& damage) = 0;
     virtual Error setLayerBlendMode(Display display, Layer layer,
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index d0126d0..6738f00 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -717,6 +717,16 @@
     return static_cast<Error>(intError);
 }
 
+Error Layer::setBufferSlotsToClear(const std::vector<uint32_t>& slotsToClear,
+                                   uint32_t activeBufferSlot) {
+    if (CC_UNLIKELY(!mDisplay)) {
+        return Error::BAD_DISPLAY;
+    }
+    auto intError = mComposer.setLayerBufferSlotsToClear(mDisplay->getId(), mId, slotsToClear,
+                                                         activeBufferSlot);
+    return static_cast<Error>(intError);
+}
+
 Error Layer::setSurfaceDamage(const Region& damage)
 {
     if (CC_UNLIKELY(!mDisplay)) {
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index 4971d19..c1c7070 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -310,6 +310,8 @@
     [[nodiscard]] virtual hal::Error setBuffer(uint32_t slot,
                                                const android::sp<android::GraphicBuffer>& buffer,
                                                const android::sp<android::Fence>& acquireFence) = 0;
+    [[nodiscard]] virtual hal::Error setBufferSlotsToClear(
+            const std::vector<uint32_t>& slotsToClear, uint32_t activeBufferSlot) = 0;
     [[nodiscard]] virtual hal::Error setSurfaceDamage(const android::Region& damage) = 0;
 
     [[nodiscard]] virtual hal::Error setBlendMode(hal::BlendMode mode) = 0;
@@ -360,6 +362,8 @@
     hal::Error setCursorPosition(int32_t x, int32_t y) override;
     hal::Error setBuffer(uint32_t slot, const android::sp<android::GraphicBuffer>& buffer,
                          const android::sp<android::Fence>& acquireFence) override;
+    hal::Error setBufferSlotsToClear(const std::vector<uint32_t>& slotsToClear,
+                                     uint32_t activeBufferSlot) override;
     hal::Error setSurfaceDamage(const android::Region& damage) override;
 
     hal::Error setBlendMode(hal::BlendMode mode) override;
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index b607df0..c9e1e79 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -186,9 +186,22 @@
     return out;
 }
 
+sp<GraphicBuffer> allocateClearSlotBuffer() {
+    sp<GraphicBuffer> buffer = sp<GraphicBuffer>::make(1, 1, PIXEL_FORMAT_RGBX_8888,
+                                                       GraphicBuffer::USAGE_HW_COMPOSER |
+                                                               GraphicBuffer::USAGE_SW_READ_OFTEN |
+                                                               GraphicBuffer::USAGE_SW_WRITE_OFTEN,
+                                                       "HidlComposer");
+    if (!buffer || buffer->initCheck() != ::android::OK) {
+        return nullptr;
+    }
+    return std::move(buffer);
+}
+
 } // anonymous namespace
 
-HidlComposer::HidlComposer(const std::string& serviceName) : mWriter(kWriterInitialSize) {
+HidlComposer::HidlComposer(const std::string& serviceName)
+      : mClearSlotBuffer(allocateClearSlotBuffer()), mWriter(kWriterInitialSize) {
     mComposer = V2_1::IComposer::getService(serviceName);
 
     if (mComposer == nullptr) {
@@ -230,6 +243,11 @@
     if (mClient == nullptr) {
         LOG_ALWAYS_FATAL("failed to create composer client");
     }
+
+    if (!mClearSlotBuffer) {
+        LOG_ALWAYS_FATAL("Failed to allocate a buffer for clearing layer buffer slots");
+        return;
+    }
 }
 
 bool HidlComposer::isSupported(OptionalFeature feature) const {
@@ -694,6 +712,32 @@
     return Error::NONE;
 }
 
+Error HidlComposer::setLayerBufferSlotsToClear(Display display, Layer layer,
+                                               const std::vector<uint32_t>& slotsToClear,
+                                               uint32_t activeBufferSlot) {
+    if (slotsToClear.empty()) {
+        return Error::NONE;
+    }
+    // Backwards compatible way of clearing buffer is to set the layer buffer with a placeholder
+    // buffer, using the slot that needs to cleared... tricky.
+    for (uint32_t slot : slotsToClear) {
+        // Don't clear the active buffer slot because we need to restore the active buffer after
+        // setting the requested buffer slots with a placeholder buffer.
+        if (slot != activeBufferSlot) {
+            mWriter.selectDisplay(display);
+            mWriter.selectLayer(layer);
+            mWriter.setLayerBuffer(slot, mClearSlotBuffer->handle, /*fence*/ -1);
+        }
+    }
+    // Since we clear buffers by setting them to a placeholder buffer, we want to make sure that the
+    // last setLayerBuffer command is sent with the currently active buffer, not the placeholder
+    // buffer, so that there is no perceptual change.
+    mWriter.selectDisplay(display);
+    mWriter.selectLayer(layer);
+    mWriter.setLayerBuffer(activeBufferSlot, /*buffer*/ nullptr, /*fence*/ -1);
+    return Error::NONE;
+}
+
 Error HidlComposer::setLayerSurfaceDamage(Display display, Layer layer,
                                           const std::vector<IComposerClient::Rect>& damage) {
     mWriter.selectDisplay(display);
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
index 3602bbb..921add5 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -248,6 +248,9 @@
     /* see setClientTarget for the purpose of slot */
     Error setLayerBuffer(Display display, Layer layer, uint32_t slot,
                          const sp<GraphicBuffer>& buffer, int acquireFence) override;
+    Error setLayerBufferSlotsToClear(Display display, Layer layer,
+                                     const std::vector<uint32_t>& slotsToClear,
+                                     uint32_t activeBufferSlot) override;
     Error setLayerSurfaceDamage(Display display, Layer layer,
                                 const std::vector<IComposerClient::Rect>& damage) override;
     Error setLayerBlendMode(Display display, Layer layer, IComposerClient::BlendMode mode) override;
@@ -362,6 +365,9 @@
     sp<V2_3::IComposerClient> mClient_2_3;
     sp<IComposerClient> mClient_2_4;
 
+    // Buffer slots for layers are cleared by setting the slot buffer to this buffer.
+    sp<GraphicBuffer> mClearSlotBuffer;
+
     // 64KiB minus a small space for metadata such as read/write pointers
     static constexpr size_t kWriterInitialSize = 64 * 1024 / sizeof(uint32_t) - 16;
     // Max number of buffers that may be cached for a given layer
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 34f8df2..856fda0 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -815,18 +815,18 @@
             .powerOnImminent = powerOnImminent};
 }
 
-ftl::Optional<FrameRateMode> Scheduler::getPreferredDisplayMode() {
+FrameRateMode Scheduler::getPreferredDisplayMode() {
     std::lock_guard<std::mutex> lock(mPolicyLock);
-    // Make sure the stored mode is up to date.
-    if (mPolicy.modeOpt) {
-        const auto ranking =
-                leaderSelectorPtr()
-                        ->getRankedFrameRates(mPolicy.contentRequirements, makeGlobalSignals())
-                        .ranking;
+    const auto frameRateMode =
+            leaderSelectorPtr()
+                    ->getRankedFrameRates(mPolicy.contentRequirements, makeGlobalSignals())
+                    .ranking.front()
+                    .frameRateMode;
 
-        mPolicy.modeOpt = ranking.front().frameRateMode;
-    }
-    return mPolicy.modeOpt;
+    // Make sure the stored mode is up to date.
+    mPolicy.modeOpt = frameRateMode;
+
+    return frameRateMode;
 }
 
 void Scheduler::onNewVsyncPeriodChangeTimeline(const hal::VsyncPeriodChangeTimeline& timeline) {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index cf2ffb8..f189426 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -209,8 +209,8 @@
     void dump(ConnectionHandle, std::string&) const;
     void dumpVsync(std::string&) const;
 
-    // Get the appropriate refresh for current conditions.
-    ftl::Optional<FrameRateMode> getPreferredDisplayMode();
+    // Returns the preferred refresh rate and frame rate for the leader display.
+    FrameRateMode getPreferredDisplayMode();
 
     // Notifies the scheduler about a refresh rate timeline change.
     void onNewVsyncPeriodChangeTimeline(const hal::VsyncPeriodChangeTimeline& timeline);
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 60000ff..3dd86aa 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -299,6 +299,7 @@
 const String16 sDump("android.permission.DUMP");
 const String16 sCaptureBlackoutContent("android.permission.CAPTURE_BLACKOUT_CONTENT");
 const String16 sInternalSystemWindow("android.permission.INTERNAL_SYSTEM_WINDOW");
+const String16 sWakeupSurfaceFlinger("android.permission.WAKEUP_SURFACE_FLINGER");
 
 const char* KERNEL_IDLE_TIMER_PROP = "graphics.display.kernel_idle_timer.enabled";
 
@@ -332,20 +333,12 @@
     }
 }
 
-bool callingThreadHasRotateSurfaceFlingerAccess() {
+bool callingThreadHasPermission(const String16& permission) {
     IPCThreadState* ipc = IPCThreadState::self();
     const int pid = ipc->getCallingPid();
     const int uid = ipc->getCallingUid();
     return uid == AID_GRAPHICS || uid == AID_SYSTEM ||
-            PermissionCache::checkPermission(sRotateSurfaceFlinger, pid, uid);
-}
-
-bool callingThreadHasInternalSystemWindowAccess() {
-    IPCThreadState* ipc = IPCThreadState::self();
-    const int pid = ipc->getCallingPid();
-    const int uid = ipc->getCallingUid();
-    return uid == AID_GRAPHICS || uid == AID_SYSTEM ||
-        PermissionCache::checkPermission(sInternalSystemWindow, pid, uid);
+            PermissionCache::checkPermission(permission, pid, uid);
 }
 
 SurfaceFlinger::SurfaceFlinger(Factory& factory, SkipInitializationTag)
@@ -447,6 +440,9 @@
     property_get("debug.sf.treat_170m_as_sRGB", value, "0");
     mTreat170mAsSrgb = atoi(value);
 
+    mIgnoreHwcPhysicalDisplayOrientation =
+            base::GetBoolProperty("debug.sf.ignore_hwc_physical_display_orientation"s, false);
+
     // We should be reading 'persist.sys.sf.color_saturation' here
     // but since /data may be encrypted, we need to wait until after vold
     // comes online to attempt to read the property. The property is
@@ -1139,7 +1135,7 @@
     return NO_ERROR;
 }
 
-void SurfaceFlinger::setDesiredActiveMode(display::DisplayModeRequest&& request) {
+void SurfaceFlinger::setDesiredActiveMode(display::DisplayModeRequest&& request, bool force) {
     ATRACE_CALL();
 
     auto display = getDisplayDeviceLocked(request.mode.modePtr->getPhysicalDisplayId());
@@ -1151,7 +1147,8 @@
     const auto mode = request.mode;
     const bool emitEvent = request.emitEvent;
 
-    switch (display->setDesiredActiveMode(DisplayDevice::ActiveModeInfo(std::move(request)))) {
+    switch (display->setDesiredActiveMode(DisplayDevice::ActiveModeInfo(std::move(request)),
+                                          force)) {
         case DisplayDevice::DesiredActiveModeAction::InitiateDisplayModeSwitch:
             scheduleComposite(FrameHint::kNone);
 
@@ -2448,7 +2445,8 @@
     if (!id) {
         return ui::ROTATION_0;
     }
-    if (getHwComposer().getComposer()->isSupported(
+    if (!mIgnoreHwcPhysicalDisplayOrientation &&
+        getHwComposer().getComposer()->isSupported(
                 Hwc2::Composer::OptionalFeature::PhysicalDisplayOrientation)) {
         switch (getHwComposer().getPhysicalDisplayOrientation(*id)) {
             case Hwc2::AidlTransform::ROT_90:
@@ -3464,10 +3462,21 @@
 
     for (auto& request : modeRequests) {
         const auto& modePtr = request.mode.modePtr;
-        const auto display = getDisplayDeviceLocked(modePtr->getPhysicalDisplayId());
+
+        const auto displayId = modePtr->getPhysicalDisplayId();
+        const auto display = getDisplayDeviceLocked(displayId);
 
         if (!display) continue;
 
+        const bool isInternalDisplay = mPhysicalDisplays.get(displayId)
+                                               .transform(&PhysicalDisplay::isInternal)
+                                               .value_or(false);
+
+        if (isInternalDisplay && displayId != mActiveDisplayId) {
+            ALOGV("%s(%s): Inactive display", __func__, to_string(displayId).c_str());
+            continue;
+        }
+
         if (display->refreshRateSelector().isModeAllowed(request.mode)) {
             setDesiredActiveMode(std::move(request));
         } else {
@@ -3989,18 +3998,23 @@
     // Avoid checking for rotation permissions if the caller already has ACCESS_SURFACE_FLINGER
     // permissions.
     if ((permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) ||
-        callingThreadHasRotateSurfaceFlingerAccess()) {
+        callingThreadHasPermission(sRotateSurfaceFlinger)) {
         permissions |= layer_state_t::Permission::ROTATE_SURFACE_FLINGER;
     }
 
-    if (callingThreadHasInternalSystemWindowAccess()) {
+    if (callingThreadHasPermission(sInternalSystemWindow)) {
         permissions |= layer_state_t::Permission::INTERNAL_SYSTEM_WINDOW;
     }
 
-    if (!(permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) &&
-        (flags & (eEarlyWakeupStart | eEarlyWakeupEnd))) {
-        ALOGE("Only WindowManager is allowed to use eEarlyWakeup[Start|End] flags");
-        flags &= ~(eEarlyWakeupStart | eEarlyWakeupEnd);
+    if (flags & (eEarlyWakeupStart | eEarlyWakeupEnd)) {
+        const bool hasPermission =
+                (permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) ||
+                callingThreadHasPermission(sWakeupSurfaceFlinger);
+        if (!hasPermission) {
+            ALOGE("Caller needs permission android.permission.WAKEUP_SURFACE_FLINGER to use "
+                  "eEarlyWakeup[Start|End] flags");
+            flags &= ~(eEarlyWakeupStart | eEarlyWakeupEnd);
+        }
     }
 
     const int64_t postTime = systemTime();
@@ -6727,7 +6741,7 @@
 ftl::Optional<scheduler::FrameRateMode> SurfaceFlinger::getPreferredDisplayMode(
         PhysicalDisplayId displayId, DisplayModeId defaultModeId) const {
     if (const auto schedulerMode = mScheduler->getPreferredDisplayMode();
-        schedulerMode && schedulerMode->modePtr->getPhysicalDisplayId() == displayId) {
+        schedulerMode.modePtr->getPhysicalDisplayId() == displayId) {
         return schedulerMode;
     }
 
@@ -6762,12 +6776,24 @@
         case SetPolicyResult::Unchanged:
             return NO_ERROR;
         case SetPolicyResult::Changed:
-            return applyRefreshRateSelectorPolicy(displayId, selector);
+            break;
     }
+
+    const bool isInternalDisplay = mPhysicalDisplays.get(displayId)
+                                           .transform(&PhysicalDisplay::isInternal)
+                                           .value_or(false);
+
+    if (isInternalDisplay && displayId != mActiveDisplayId) {
+        // The policy will be be applied when the display becomes active.
+        ALOGV("%s(%s): Inactive display", __func__, to_string(displayId).c_str());
+        return NO_ERROR;
+    }
+
+    return applyRefreshRateSelectorPolicy(displayId, selector);
 }
 
 status_t SurfaceFlinger::applyRefreshRateSelectorPolicy(
-        PhysicalDisplayId displayId, const scheduler::RefreshRateSelector& selector) {
+        PhysicalDisplayId displayId, const scheduler::RefreshRateSelector& selector, bool force) {
     const scheduler::RefreshRateSelector::Policy currentPolicy = selector.getCurrentPolicy();
     ALOGV("Setting desired display mode specs: %s", currentPolicy.toString().c_str());
 
@@ -6797,7 +6823,7 @@
         return INVALID_OPERATION;
     }
 
-    setDesiredActiveMode({std::move(preferredMode), .emitEvent = true});
+    setDesiredActiveMode({std::move(preferredMode), .emitEvent = true}, force);
     return NO_ERROR;
 }
 
@@ -7101,7 +7127,8 @@
     // case, its preferred mode has not been propagated to HWC (via setDesiredActiveMode). In either
     // case, the Scheduler's cachedModeChangedParams must be initialized to the newly active mode,
     // and the kernel idle timer of the newly active display must be toggled.
-    applyRefreshRateSelectorPolicy(mActiveDisplayId, activeDisplay->refreshRateSelector());
+    constexpr bool kForce = true;
+    applyRefreshRateSelectorPolicy(mActiveDisplayId, activeDisplay->refreshRateSelector(), kForce);
 }
 
 status_t SurfaceFlinger::addWindowInfosListener(
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index fa3a49f..23ac19e 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -311,6 +311,11 @@
     // on this behavior to increase contrast for some media sources.
     bool mTreat170mAsSrgb = false;
 
+    // Allows to ignore physical orientation provided through hwc API in favour of
+    // 'ro.surface_flinger.primary_display_orientation'.
+    // TODO(b/246793311): Clean up a temporary property
+    bool mIgnoreHwcPhysicalDisplayOrientation = false;
+
 protected:
     // We're reference counted, never destroy SurfaceFlinger directly
     virtual ~SurfaceFlinger();
@@ -649,7 +654,8 @@
     // Show render rate with refresh rate overlay
     bool mRefreshRateOverlayRenderRate = false;
 
-    void setDesiredActiveMode(display::DisplayModeRequest&&) REQUIRES(mStateLock);
+    void setDesiredActiveMode(display::DisplayModeRequest&&, bool force = false)
+            REQUIRES(mStateLock);
 
     status_t setActiveModeFromBackdoor(const sp<display::DisplayToken>&, DisplayModeId);
     // Sets the active mode and a new refresh rate in SF.
@@ -678,7 +684,8 @@
 
     // TODO(b/241285191): Look up RefreshRateSelector on Scheduler to remove redundant parameter.
     status_t applyRefreshRateSelectorPolicy(PhysicalDisplayId,
-                                            const scheduler::RefreshRateSelector&)
+                                            const scheduler::RefreshRateSelector&,
+                                            bool force = false)
             REQUIRES(mStateLock, kMainThreadContext);
 
     void commitTransactions() EXCLUDES(mStateLock) REQUIRES(kMainThreadContext);
@@ -1187,7 +1194,9 @@
     display::PhysicalDisplays mPhysicalDisplays GUARDED_BY(mStateLock);
 
     // The inner or outer display for foldables, assuming they have mutually exclusive power states.
-    PhysicalDisplayId mActiveDisplayId GUARDED_BY(mStateLock);
+    // Atomic because writes from onActiveDisplayChangedLocked are not always under mStateLock, but
+    // reads from ISchedulerCallback::requestDisplayModes may happen concurrently.
+    std::atomic<PhysicalDisplayId> mActiveDisplayId GUARDED_BY(mStateLock);
 
     struct {
         DisplayIdGenerator<GpuVirtualDisplayId> gpu;
diff --git a/services/surfaceflinger/Tracing/tools/Android.bp b/services/surfaceflinger/Tracing/tools/Android.bp
index e8fe734..b6435a8 100644
--- a/services/surfaceflinger/Tracing/tools/Android.bp
+++ b/services/surfaceflinger/Tracing/tools/Android.bp
@@ -25,8 +25,8 @@
     name: "layertracegenerator",
     defaults: [
         "libsurfaceflinger_mocks_defaults",
+        "librenderengine_deps",
         "surfaceflinger_defaults",
-        "skia_renderengine_deps",
     ],
     srcs: [
         ":libsurfaceflinger_sources",
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index bf22521..6d1b3fe 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -140,6 +140,7 @@
     defaults: [
         "android.hardware.graphics.common-ndk_static",
         "android.hardware.graphics.composer3-ndk_static",
+        "librenderengine_deps",
     ],
     static_libs: [
         "android.hardware.common-V2-ndk",
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index 074bf8c..ad3bd35 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-#include "mock/MockDisplayModeSpecs.h"
-#include "mock/MockEventThread.h"
 #undef LOG_TAG
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
 #include "DisplayTransactionTestHelpers.h"
+#include "mock/DisplayHardware/MockDisplayMode.h"
+#include "mock/MockDisplayModeSpecs.h"
 
 #include <ftl/fake_guard.h>
 #include <scheduler/Fps.h>
@@ -42,8 +42,7 @@
         PrimaryDisplayVariant::setupNativeWindowSurfaceCreationCallExpectations(this);
         PrimaryDisplayVariant::setupHwcGetActiveConfigCallExpectations(this);
 
-        DisplayModes modes = makeModes(kMode60, kMode90, kMode120, kMode90_4K);
-        auto selectorPtr = std::make_shared<scheduler::RefreshRateSelector>(modes, kModeId60);
+        auto selectorPtr = std::make_shared<scheduler::RefreshRateSelector>(kModes, kModeId60);
 
         setupScheduler(selectorPtr);
 
@@ -51,7 +50,7 @@
         mFlinger.configureAndCommit();
 
         mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this)
-                           .setDisplayModes(std::move(modes), kModeId60, std::move(selectorPtr))
+                           .setDisplayModes(kModes, kModeId60, std::move(selectorPtr))
                            .inject();
 
         // isVsyncPeriodSwitchSupported should return true, otherwise the SF's HWC proxy
@@ -78,6 +77,8 @@
     static constexpr ui::Size kResolution4K{3840, 2160};
     static inline const DisplayModePtr kMode90_4K =
             createDisplayMode(kModeId90_4K, 90_Hz, 3, kResolution4K);
+
+    static inline const DisplayModes kModes = makeModes(kMode60, kMode90, kMode120, kMode90_4K);
 };
 
 void DisplayModeSwitchingTest::setupScheduler(
@@ -283,5 +284,114 @@
     ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90_4K);
 }
 
+TEST_F(DisplayModeSwitchingTest, multiDisplay) {
+    ftl::FakeGuard guard(kMainThreadContext);
+
+    constexpr HWDisplayId kInnerDisplayHwcId = PrimaryDisplayVariant::HWC_DISPLAY_ID;
+    constexpr HWDisplayId kOuterDisplayHwcId = kInnerDisplayHwcId + 1;
+
+    constexpr PhysicalDisplayId kOuterDisplayId = PhysicalDisplayId::fromPort(254u);
+
+    constexpr bool kIsPrimary = false;
+    TestableSurfaceFlinger::FakeHwcDisplayInjector(kOuterDisplayId, hal::DisplayType::PHYSICAL,
+                                                   kIsPrimary)
+            .setHwcDisplayId(kOuterDisplayHwcId)
+            .inject(&mFlinger, mComposer);
+
+    const auto outerDisplay = mFakeDisplayInjector.injectInternalDisplay(
+            [&](FakeDisplayDeviceInjector& injector) {
+                injector.setDisplayModes(mock::cloneForDisplay(kOuterDisplayId, kModes),
+                                         kModeId120);
+            },
+            {.displayId = kOuterDisplayId,
+             .hwcDisplayId = kOuterDisplayHwcId,
+             .isPrimary = kIsPrimary});
+
+    const auto& innerDisplay = mDisplay;
+
+    EXPECT_FALSE(innerDisplay->getDesiredActiveMode());
+    EXPECT_FALSE(outerDisplay->getDesiredActiveMode());
+
+    EXPECT_EQ(innerDisplay->getActiveMode().modePtr->getId(), kModeId60);
+    EXPECT_EQ(outerDisplay->getActiveMode().modePtr->getId(), kModeId120);
+
+    mFlinger.onActiveDisplayChanged(innerDisplay);
+
+    EXPECT_EQ(NO_ERROR,
+              mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
+                                                  mock::createDisplayModeSpecs(kModeId90.value(),
+                                                                               false, 0.f, 120.f)));
+
+    EXPECT_EQ(NO_ERROR,
+              mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(),
+                                                  mock::createDisplayModeSpecs(kModeId60.value(),
+                                                                               false, 0.f, 120.f)));
+
+    // Transition on the inner display.
+    ASSERT_TRUE(innerDisplay->getDesiredActiveMode());
+    EXPECT_EQ(innerDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId90);
+
+    // No transition on the outer display.
+    EXPECT_FALSE(outerDisplay->getDesiredActiveMode());
+
+    const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
+    EXPECT_CALL(*mComposer,
+                setActiveConfigWithConstraints(kInnerDisplayHwcId,
+                                               hal::HWConfigId(kModeId90.value()), _, _))
+            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+
+    mFlinger.commit();
+
+    // Transition on the inner display.
+    ASSERT_TRUE(innerDisplay->getDesiredActiveMode());
+    EXPECT_EQ(innerDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId90);
+
+    // No transition on the outer display.
+    EXPECT_FALSE(outerDisplay->getDesiredActiveMode());
+
+    mFlinger.commit();
+
+    // Transition on the inner display.
+    EXPECT_FALSE(innerDisplay->getDesiredActiveMode());
+    EXPECT_EQ(innerDisplay->getActiveMode().modePtr->getId(), kModeId90);
+
+    // No transition on the outer display.
+    EXPECT_FALSE(outerDisplay->getDesiredActiveMode());
+    EXPECT_EQ(outerDisplay->getActiveMode().modePtr->getId(), kModeId120);
+
+    mFlinger.onActiveDisplayChanged(outerDisplay);
+
+    // No transition on the inner display.
+    EXPECT_FALSE(innerDisplay->getDesiredActiveMode());
+
+    // Transition on the outer display.
+    ASSERT_TRUE(outerDisplay->getDesiredActiveMode());
+    EXPECT_EQ(outerDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId60);
+
+    EXPECT_CALL(*mComposer,
+                setActiveConfigWithConstraints(kOuterDisplayHwcId,
+                                               hal::HWConfigId(kModeId60.value()), _, _))
+            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+
+    mFlinger.commit();
+
+    // No transition on the inner display.
+    EXPECT_FALSE(innerDisplay->getDesiredActiveMode());
+
+    // Transition on the outer display.
+    ASSERT_TRUE(outerDisplay->getDesiredActiveMode());
+    EXPECT_EQ(outerDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId60);
+
+    mFlinger.commit();
+
+    // No transition on the inner display.
+    EXPECT_FALSE(innerDisplay->getDesiredActiveMode());
+    EXPECT_EQ(innerDisplay->getActiveMode().modePtr->getId(), kModeId90);
+
+    // Transition on the outer display.
+    EXPECT_FALSE(outerDisplay->getDesiredActiveMode());
+    EXPECT_EQ(outerDisplay->getActiveMode().modePtr->getId(), kModeId60);
+}
+
 } // namespace
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index 836e3a4..2f16b7b 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -98,6 +98,8 @@
                  Error(Display, nsecs_t, uint32_t*, uint32_t*, int*, uint32_t*));
     MOCK_METHOD4(setCursorPosition, Error(Display, Layer, int32_t, int32_t));
     MOCK_METHOD5(setLayerBuffer, Error(Display, Layer, uint32_t, const sp<GraphicBuffer>&, int));
+    MOCK_METHOD4(setLayerBufferSlotsToClear,
+                 Error(Display, Layer, const std::vector<uint32_t>&, uint32_t));
     MOCK_METHOD3(setLayerSurfaceDamage,
                  Error(Display, Layer, const std::vector<IComposerClient::Rect>&));
     MOCK_METHOD3(setLayerBlendMode, Error(Display, Layer, IComposerClient::BlendMode));
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
index c78b6bd..3b36361 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
@@ -38,4 +38,24 @@
     return createDisplayMode(modeId, refreshRate, {}, {}, displayId);
 }
 
+inline DisplayModePtr cloneForDisplay(PhysicalDisplayId displayId, const DisplayModePtr& modePtr) {
+    return DisplayMode::Builder(modePtr->getHwcId())
+            .setId(modePtr->getId())
+            .setPhysicalDisplayId(displayId)
+            .setVsyncPeriod(modePtr->getVsyncPeriod())
+            .setGroup(modePtr->getGroup())
+            .setResolution(modePtr->getResolution())
+            .build();
+}
+
+inline DisplayModes cloneForDisplay(PhysicalDisplayId displayId, const DisplayModes& modes) {
+    DisplayModes clones;
+
+    for (const auto& [id, modePtr] : modes) {
+        clones.try_emplace(id, cloneForDisplay(displayId, modePtr));
+    }
+
+    return clones;
+}
+
 } // namespace android::mock
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 1bf7abb..b03200e 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -511,6 +511,10 @@
         case VK_FORMAT_R8_UNORM:
             native_format = android::PIXEL_FORMAT_R_8;
             break;
+        // TODO: Do we need to query for VK_EXT_rgba10x6_formats here?
+        case VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16:
+            native_format = android::PIXEL_FORMAT_RGBA_10101010;
+            break;
         default:
             ALOGV("unsupported swapchain format %d", format);
             break;
@@ -858,6 +862,22 @@
         }
     }
 
+    // TODO query VK_EXT_rgba10x6_formats support
+    desc.format = AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM;
+    if (AHardwareBuffer_isSupported(&desc)) {
+        all_formats.emplace_back(
+            VkSurfaceFormatKHR{VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16,
+                               VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
+        if (colorspace_ext) {
+            all_formats.emplace_back(
+                VkSurfaceFormatKHR{VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16,
+                                   VK_COLOR_SPACE_PASS_THROUGH_EXT});
+            all_formats.emplace_back(
+                VkSurfaceFormatKHR{VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16,
+                                   VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT});
+        }
+    }
+
     // NOTE: Any new formats that are added must be coordinated across different
     // Android users.  This includes the ANGLE team (a layered implementation of
     // OpenGL-ES).
diff --git a/vulkan/vkjson/vkjson.cc b/vulkan/vkjson/vkjson.cc
index da6b00a..0284192 100644
--- a/vulkan/vkjson/vkjson.cc
+++ b/vulkan/vkjson/vkjson.cc
@@ -731,7 +731,7 @@
     visitor->Visit("vulkanMemoryModelAvailabilityVisibilityChains", &features->vulkanMemoryModelAvailabilityVisibilityChains) &&
     visitor->Visit("shaderOutputViewportIndex", &features->shaderOutputViewportIndex) &&
     visitor->Visit("shaderOutputLayer", &features->shaderOutputLayer) &&
-    visitor->Visit("shaderOutputLayer", &features->shaderOutputLayer);
+    visitor->Visit("subgroupBroadcastDynamicId", &features->subgroupBroadcastDynamicId);
 }
 
 template <typename Visitor>