Merge "Add mic mute keyboard led. (2/2)" into main
diff --git a/cmds/dumpstate/Android.bp b/cmds/dumpstate/Android.bp
index 341fabb..a1c10f5 100644
--- a/cmds/dumpstate/Android.bp
+++ b/cmds/dumpstate/Android.bp
@@ -37,9 +37,6 @@
     name: "libdumpstateutil",
     defaults: ["dumpstate_cflag_defaults"],
     vendor_available: true,
-    vndk: {
-        enabled: true,
-    },
     srcs: [
         "DumpstateInternal.cpp",
         "DumpstateUtil.cpp",
@@ -82,7 +79,10 @@
 
 cc_defaults {
     name: "dumpstate_defaults",
-    defaults: ["dumpstate_cflag_defaults"],
+    defaults: [
+        "aconfig_lib_cc_static_link.defaults",
+        "dumpstate_cflag_defaults",
+    ],
     shared_libs: [
         "android.hardware.dumpstate@1.0",
         "android.hardware.dumpstate@1.1",
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index a5c0c60..95a05cd 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -790,7 +790,8 @@
 
     if (OK !=
         IInterface::asBinder(cb)->linkToDeath(sp<ServiceManager>::fromExisting(this))) {
-        ALOGE("%s Could not linkToDeath when adding client callback for %s", name.c_str());
+        ALOGE("%s Could not linkToDeath when adding client callback for %s",
+              ctx.toDebugString().c_str(), name.c_str());
         return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "Couldn't linkToDeath.");
     }
 
diff --git a/cmds/sfdo/Android.bp b/cmds/sfdo/Android.bp
index c19c9da..a91a7dc 100644
--- a/cmds/sfdo/Android.bp
+++ b/cmds/sfdo/Android.bp
@@ -1,17 +1,10 @@
-cc_binary {
+rust_binary {
     name: "sfdo",
+    srcs: ["sfdo.rs"],
 
-    srcs: ["sfdo.cpp"],
-
-    shared_libs: [
-        "libutils",
-        "libgui",
+    rustlibs: [
+        "android.gui-rust",
+        "libclap",
     ],
-
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wunused",
-        "-Wunreachable-code",
-    ],
+    edition: "2021",
 }
diff --git a/cmds/sfdo/sfdo.cpp b/cmds/sfdo/sfdo.cpp
deleted file mode 100644
index de0e171..0000000
--- a/cmds/sfdo/sfdo.cpp
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include <inttypes.h>
-#include <stdint.h>
-#include <any>
-#include <map>
-
-#include <cutils/properties.h>
-#include <sys/resource.h>
-#include <utils/Log.h>
-
-#include <gui/ISurfaceComposer.h>
-#include <gui/SurfaceComposerClient.h>
-#include <gui/SurfaceControl.h>
-#include <private/gui/ComposerServiceAIDL.h>
-
-using namespace android;
-
-std::map<std::string, std::any> g_functions;
-
-enum class ParseToggleResult {
-    kError,
-    kFalse,
-    kTrue,
-};
-
-const std::map<std::string, std::string> g_function_details = {
-        {"debugFlash", "[optional(delay)] Perform a debug flash."},
-        {"frameRateIndicator", "[hide | show] displays the framerate in the top left corner."},
-        {"scheduleComposite", "Force composite ahead of next VSYNC."},
-        {"scheduleCommit", "Force commit ahead of next VSYNC."},
-        {"scheduleComposite", "PENDING - if you have a good understanding let me know!"},
-        {"forceClientComposition",
-         "[enabled | disabled] When enabled, it disables "
-         "Hardware Overlays, and routes all window composition to the GPU. This can "
-         "help check if there is a bug in HW Composer."},
-};
-
-static void ShowUsage() {
-    std::cout << "usage: sfdo [help, frameRateIndicator show, debugFlash enabled, ...]\n\n";
-    for (const auto& sf : g_functions) {
-        const std::string fn = sf.first;
-        std::string fdetails = "TODO";
-        if (g_function_details.find(fn) != g_function_details.end())
-            fdetails = g_function_details.find(fn)->second;
-        std::cout << "    " << fn << ": " << fdetails << "\n";
-    }
-}
-
-// Returns 1 for positive keywords and 0 for negative keywords.
-// If the string does not match any it will return -1.
-ParseToggleResult parseToggle(const char* str) {
-    const std::unordered_set<std::string> positive{"1",  "true",    "y",   "yes",
-                                                   "on", "enabled", "show"};
-    const std::unordered_set<std::string> negative{"0",   "false",    "n",   "no",
-                                                   "off", "disabled", "hide"};
-
-    const std::string word(str);
-    if (positive.count(word)) {
-        return ParseToggleResult::kTrue;
-    }
-    if (negative.count(word)) {
-        return ParseToggleResult::kFalse;
-    }
-
-    return ParseToggleResult::kError;
-}
-
-int frameRateIndicator(int argc, char** argv) {
-    bool hide = false, show = false;
-    if (argc == 3) {
-        show = strcmp(argv[2], "show") == 0;
-        hide = strcmp(argv[2], "hide") == 0;
-    }
-
-    if (show || hide) {
-        ComposerServiceAIDL::getComposerService()->enableRefreshRateOverlay(show);
-    } else {
-        std::cerr << "Incorrect usage of frameRateIndicator. Missing [hide | show].\n";
-        return -1;
-    }
-    return 0;
-}
-
-int debugFlash(int argc, char** argv) {
-    int delay = 0;
-    if (argc == 3) {
-        delay = atoi(argv[2]) == 0;
-    }
-
-    ComposerServiceAIDL::getComposerService()->setDebugFlash(delay);
-    return 0;
-}
-
-int scheduleComposite([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
-    ComposerServiceAIDL::getComposerService()->scheduleComposite();
-    return 0;
-}
-
-int scheduleCommit([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
-    ComposerServiceAIDL::getComposerService()->scheduleCommit();
-    return 0;
-}
-
-int forceClientComposition(int argc, char** argv) {
-    bool enabled = true;
-    // A valid command looks like this:
-    // adb shell sfdo forceClientComposition enabled
-    if (argc >= 3) {
-        const ParseToggleResult toggle = parseToggle(argv[2]);
-        if (toggle == ParseToggleResult::kError) {
-            std::cerr << "Incorrect usage of forceClientComposition. "
-                         "Missing [enabled | disabled].\n";
-            return -1;
-        }
-        if (argc > 3) {
-            std::cerr << "Too many arguments after [enabled | disabled]. "
-                         "Ignoring extra arguments.\n";
-        }
-        enabled = (toggle == ParseToggleResult::kTrue);
-    } else {
-        std::cerr << "Incorrect usage of forceClientComposition. Missing [enabled | disabled].\n";
-        return -1;
-    }
-
-    ComposerServiceAIDL::getComposerService()->forceClientComposition(enabled);
-    return 0;
-}
-
-int main(int argc, char** argv) {
-    std::cout << "Execute SurfaceFlinger internal commands.\n";
-    std::cout << "sfdo requires to be run with root permissions..\n";
-
-    g_functions["frameRateIndicator"] = frameRateIndicator;
-    g_functions["debugFlash"] = debugFlash;
-    g_functions["scheduleComposite"] = scheduleComposite;
-    g_functions["scheduleCommit"] = scheduleCommit;
-    g_functions["forceClientComposition"] = forceClientComposition;
-
-    if (argc > 1 && g_functions.find(argv[1]) != g_functions.end()) {
-        std::cout << "Running: " << argv[1] << "\n";
-        const std::string key(argv[1]);
-        const auto fn = g_functions[key];
-        int result = std::any_cast<int (*)(int, char**)>(fn)(argc, argv);
-        if (result == 0) {
-            std::cout << "Success.\n";
-        }
-        return result;
-    } else {
-        ShowUsage();
-    }
-    return 0;
-}
\ No newline at end of file
diff --git a/cmds/sfdo/sfdo.rs b/cmds/sfdo/sfdo.rs
new file mode 100644
index 0000000..863df6b
--- /dev/null
+++ b/cmds/sfdo/sfdo.rs
@@ -0,0 +1,155 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! sfdo: Make surface flinger do things
+use android_gui::{aidl::android::gui::ISurfaceComposer::ISurfaceComposer, binder};
+use clap::{Parser, Subcommand};
+use std::fmt::Debug;
+
+const SERVICE_IDENTIFIER: &str = "SurfaceFlingerAIDL";
+
+fn print_result<T, E>(function_name: &str, res: Result<T, E>)
+where
+    E: Debug,
+{
+    match res {
+        Ok(_) => println!("{}: Operation successful!", function_name),
+        Err(err) => println!("{}: Operation failed: {:?}", function_name, err),
+    }
+}
+
+fn parse_toggle(toggle_value: &str) -> Option<bool> {
+    let positive = ["1", "true", "y", "yes", "on", "enabled", "show"];
+    let negative = ["0", "false", "n", "no", "off", "disabled", "hide"];
+
+    let word = toggle_value.to_lowercase(); // Case-insensitive comparison
+
+    if positive.contains(&word.as_str()) {
+        Some(true)
+    } else if negative.contains(&word.as_str()) {
+        Some(false)
+    } else {
+        None
+    }
+}
+
+#[derive(Parser)]
+#[command(version = "0.1", about = "Execute SurfaceFlinger internal commands.")]
+#[command(propagate_version = true)]
+struct Cli {
+    #[command(subcommand)]
+    command: Option<Commands>,
+}
+
+#[derive(Subcommand, Debug)]
+enum Commands {
+    #[command(about = "[optional(--delay)] Perform a debug flash.")]
+    DebugFlash {
+        #[arg(short, long, default_value_t = 0)]
+        delay: i32,
+    },
+
+    #[command(
+        about = "state = [enabled | disabled] When enabled, it disables Hardware Overlays, \
+                      and routes all window composition to the GPU. This can help check if \
+                      there is a bug in HW Composer."
+    )]
+    ForceClientComposition { state: Option<String> },
+
+    #[command(about = "state = [hide | show], displays the framerate in the top left corner.")]
+    FrameRateIndicator { state: Option<String> },
+
+    #[command(about = "Force composite ahead of next VSYNC.")]
+    ScheduleComposite,
+
+    #[command(about = "Force commit ahead of next VSYNC.")]
+    ScheduleCommit,
+}
+
+/// sfdo command line tool
+///
+/// sfdo allows you to call different functions from the SurfaceComposer using
+/// the adb shell.
+fn main() {
+    binder::ProcessState::start_thread_pool();
+    let composer_service = match binder::get_interface::<dyn ISurfaceComposer>(SERVICE_IDENTIFIER) {
+        Ok(service) => service,
+        Err(err) => {
+            eprintln!("Unable to connect to ISurfaceComposer: {}", err);
+            return;
+        }
+    };
+
+    let cli = Cli::parse();
+
+    match &cli.command {
+        Some(Commands::FrameRateIndicator { state }) => {
+            if let Some(op_state) = state {
+                let toggle = parse_toggle(op_state);
+                match toggle {
+                    Some(true) => {
+                        let res = composer_service.enableRefreshRateOverlay(true);
+                        print_result("enableRefreshRateOverlay", res);
+                    }
+                    Some(false) => {
+                        let res = composer_service.enableRefreshRateOverlay(false);
+                        print_result("enableRefreshRateOverlay", res);
+                    }
+                    None => {
+                        eprintln!("Invalid state: {}, choices are [hide | show]", op_state);
+                    }
+                }
+            } else {
+                eprintln!("No state, choices are [hide | show]");
+            }
+        }
+        Some(Commands::DebugFlash { delay }) => {
+            let res = composer_service.setDebugFlash(*delay);
+            print_result("setDebugFlash", res);
+        }
+        Some(Commands::ScheduleComposite) => {
+            let res = composer_service.scheduleComposite();
+            print_result("scheduleComposite", res);
+        }
+        Some(Commands::ScheduleCommit) => {
+            let res = composer_service.scheduleCommit();
+            print_result("scheduleCommit", res);
+        }
+        Some(Commands::ForceClientComposition { state }) => {
+            if let Some(op_state) = state {
+                let toggle = parse_toggle(op_state);
+                match toggle {
+                    Some(true) => {
+                        let res = composer_service.forceClientComposition(true);
+                        print_result("forceClientComposition", res);
+                    }
+                    Some(false) => {
+                        let res = composer_service.forceClientComposition(false);
+                        print_result("forceClientComposition", res);
+                    }
+                    None => {
+                        eprintln!("Invalid state: {}, choices are [enabled | disabled]", op_state);
+                    }
+                }
+            } else {
+                eprintln!("No state, choices are [enabled | disabled]");
+            }
+        }
+        None => {
+            println!("Execute SurfaceFlinger internal commands.");
+            println!("run `adb shell sfdo help` for more to view the commands.");
+            println!("run `adb shell sfdo [COMMAND] --help` for more info on the command.");
+        }
+    }
+}
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 99f4fbd..0300f8c 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -377,12 +377,6 @@
 }
 
 prebuilt_etc {
-    name: "android.software.contextualsearch.prebuilt.xml",
-    src: "android.software.contextualsearch.xml",
-    defaults: ["frameworks_native_data_etc_defaults"],
-}
-
-prebuilt_etc {
     name: "android.software.device_id_attestation.prebuilt.xml",
     src: "android.software.device_id_attestation.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
diff --git a/data/etc/android.software.contextualsearch.xml b/data/etc/android.software.contextualsearch.xml
deleted file mode 100644
index 4564ac8..0000000
--- a/data/etc/android.software.contextualsearch.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 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.
--->
-
-<!-- Feature for devices supporting contextual search helper. -->
-<permissions>
-    <feature name="android.software.contextualsearch" />
-</permissions>
diff --git a/data/etc/input/motion_predictor_config.xml b/data/etc/input/motion_predictor_config.xml
index a20993f..c3f2fed 100644
--- a/data/etc/input/motion_predictor_config.xml
+++ b/data/etc/input/motion_predictor_config.xml
@@ -33,11 +33,9 @@
   <distance-noise-floor>0.2</distance-noise-floor>
   <!-- The low and high jerk thresholds for prediction pruning.
 
-    The jerk thresholds are based on normalized dt = 1 calculations, and
-    are taken from Jetpacks MotionEventPredictor's KalmanPredictor
-    implementation (using its ACCURATE_LOW_JANK and ACCURATE_HIGH_JANK).
+    The jerk thresholds are based on normalized dt = 1 calculations.
   -->
-  <low-jerk>0.1</low-jerk>
-  <high-jerk>0.2</high-jerk>
+  <low-jerk>1.0</low-jerk>
+  <high-jerk>1.1</high-jerk>
 </motion-predictor>
 
diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h
index 3c82d88..97e4dc0 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -114,6 +114,9 @@
  * API, the client is expected to call {@link APerformanceHint_reportActualWorkDuration} each
  * cycle to report the actual time taken to complete to the system.
  *
+ * Note, methods of {@link APerformanceHintSession_*} are not thread safe so callers must
+ * ensure thread safety.
+ *
  * All timings should be from `std::chrono::steady_clock` or `clock_gettime(CLOCK_MONOTONIC, ...)`
  */
 typedef struct APerformanceHintSession APerformanceHintSession;
diff --git a/include/android/surface_control.h b/include/android/surface_control.h
index fb1419f..099a2bc 100644
--- a/include/android/surface_control.h
+++ b/include/android/surface_control.h
@@ -62,16 +62,18 @@
  *
  * Available since API level 29.
  */
-ASurfaceControl* ASurfaceControl_createFromWindow(ANativeWindow* parent, const char* debug_name)
-                                                  __INTRODUCED_IN(29);
+ASurfaceControl* _Nullable ASurfaceControl_createFromWindow(ANativeWindow* _Nonnull parent,
+                                                            const char* _Nonnull debug_name)
+        __INTRODUCED_IN(29);
 
 /**
  * See ASurfaceControl_createFromWindow.
  *
  * Available since API level 29.
  */
-ASurfaceControl* ASurfaceControl_create(ASurfaceControl* parent, const char* debug_name)
-                                        __INTRODUCED_IN(29);
+ASurfaceControl* _Nullable ASurfaceControl_create(ASurfaceControl* _Nonnull parent,
+                                                  const char* _Nonnull debug_name)
+        __INTRODUCED_IN(29);
 
 /**
  * Acquires a reference on the given ASurfaceControl object.  This prevents the object
@@ -81,7 +83,7 @@
  *
  * Available since API level 31.
  */
-void ASurfaceControl_acquire(ASurfaceControl* surface_control) __INTRODUCED_IN(31);
+void ASurfaceControl_acquire(ASurfaceControl* _Nonnull surface_control) __INTRODUCED_IN(31);
 
 /**
  * Removes a reference that was previously acquired with one of the following functions:
@@ -92,7 +94,7 @@
  *
  * Available since API level 29.
  */
-void ASurfaceControl_release(ASurfaceControl* surface_control) __INTRODUCED_IN(29);
+void ASurfaceControl_release(ASurfaceControl* _Nonnull surface_control) __INTRODUCED_IN(29);
 
 struct ASurfaceTransaction;
 
@@ -108,14 +110,14 @@
  *
  * Available since API level 29.
  */
-ASurfaceTransaction* ASurfaceTransaction_create() __INTRODUCED_IN(29);
+ASurfaceTransaction* _Nonnull ASurfaceTransaction_create() __INTRODUCED_IN(29);
 
 /**
  * Destroys the \a transaction object.
  *
  * Available since API level 29.
  */
-void ASurfaceTransaction_delete(ASurfaceTransaction* transaction) __INTRODUCED_IN(29);
+void ASurfaceTransaction_delete(ASurfaceTransaction* _Nullable transaction) __INTRODUCED_IN(29);
 
 /**
  * Applies the updates accumulated in \a transaction.
@@ -126,7 +128,7 @@
  *
  * Available since API level 29.
  */
-void ASurfaceTransaction_apply(ASurfaceTransaction* transaction) __INTRODUCED_IN(29);
+void ASurfaceTransaction_apply(ASurfaceTransaction* _Nonnull transaction) __INTRODUCED_IN(29);
 
 /**
  * An opaque handle returned during a callback that can be used to query general stats and stats for
@@ -154,9 +156,9 @@
  *
  * Available since API level 29.
  */
-typedef void (*ASurfaceTransaction_OnComplete)(void* context, ASurfaceTransactionStats* stats)
-                                               __INTRODUCED_IN(29);
-
+typedef void (*ASurfaceTransaction_OnComplete)(void* _Null_unspecified context,
+                                               ASurfaceTransactionStats* _Nonnull stats)
+        __INTRODUCED_IN(29);
 
 /**
  * The ASurfaceTransaction_OnCommit callback is invoked when transaction is applied and the updates
@@ -183,8 +185,9 @@
  *
  * Available since API level 31.
  */
-typedef void (*ASurfaceTransaction_OnCommit)(void* context, ASurfaceTransactionStats* stats)
-                                               __INTRODUCED_IN(31);
+typedef void (*ASurfaceTransaction_OnCommit)(void* _Null_unspecified context,
+                                             ASurfaceTransactionStats* _Nonnull stats)
+        __INTRODUCED_IN(31);
 
 /**
  * Returns the timestamp of when the frame was latched by the framework. Once a frame is
@@ -192,8 +195,8 @@
  *
  * Available since API level 29.
  */
-int64_t ASurfaceTransactionStats_getLatchTime(ASurfaceTransactionStats* surface_transaction_stats)
-                                              __INTRODUCED_IN(29);
+int64_t ASurfaceTransactionStats_getLatchTime(
+        ASurfaceTransactionStats* _Nonnull surface_transaction_stats) __INTRODUCED_IN(29);
 
 /**
  * Returns a sync fence that signals when the transaction has been presented.
@@ -204,8 +207,8 @@
  *
  * Available since API level 29.
  */
-int ASurfaceTransactionStats_getPresentFenceFd(ASurfaceTransactionStats* surface_transaction_stats)
-                                               __INTRODUCED_IN(29);
+int ASurfaceTransactionStats_getPresentFenceFd(
+        ASurfaceTransactionStats* _Nonnull surface_transaction_stats) __INTRODUCED_IN(29);
 
 /**
  * \a outASurfaceControls returns an array of ASurfaceControl pointers that were updated during the
@@ -217,18 +220,18 @@
  *
  * \a outASurfaceControlsSize returns the size of the ASurfaceControls array.
  */
-void ASurfaceTransactionStats_getASurfaceControls(ASurfaceTransactionStats* surface_transaction_stats,
-                                                  ASurfaceControl*** outASurfaceControls,
-                                                  size_t* outASurfaceControlsSize)
-                                                  __INTRODUCED_IN(29);
+void ASurfaceTransactionStats_getASurfaceControls(
+        ASurfaceTransactionStats* _Nonnull surface_transaction_stats,
+        ASurfaceControl* _Nullable* _Nullable* _Nonnull outASurfaceControls,
+        size_t* _Nonnull outASurfaceControlsSize) __INTRODUCED_IN(29);
 /**
  * Releases the array of ASurfaceControls that were returned by
  * ASurfaceTransactionStats_getASurfaceControls().
  *
  * Available since API level 29.
  */
-void ASurfaceTransactionStats_releaseASurfaceControls(ASurfaceControl** surface_controls)
-                                                      __INTRODUCED_IN(29);
+void ASurfaceTransactionStats_releaseASurfaceControls(
+        ASurfaceControl* _Nonnull* _Nonnull surface_controls) __INTRODUCED_IN(29);
 
 /**
  * Returns the timestamp of when the CURRENT buffer was acquired. A buffer is considered
@@ -241,9 +244,9 @@
  * fence has signaled, depending on internal timing differences. Therefore the caller should
  * use the acquire fence passed in to setBuffer and query the signal time.
  */
-int64_t ASurfaceTransactionStats_getAcquireTime(ASurfaceTransactionStats* surface_transaction_stats,
-                                                ASurfaceControl* surface_control)
-                                                __INTRODUCED_IN(29);
+int64_t ASurfaceTransactionStats_getAcquireTime(
+        ASurfaceTransactionStats* _Nonnull surface_transaction_stats,
+        ASurfaceControl* _Nonnull surface_control) __INTRODUCED_IN(29);
 
 /**
  * The returns the fence used to signal the release of the PREVIOUS buffer set on
@@ -268,9 +271,8 @@
  * Available since API level 29.
  */
 int ASurfaceTransactionStats_getPreviousReleaseFenceFd(
-                                                ASurfaceTransactionStats* surface_transaction_stats,
-                                                ASurfaceControl* surface_control)
-                                                __INTRODUCED_IN(29);
+        ASurfaceTransactionStats* _Nonnull surface_transaction_stats,
+        ASurfaceControl* _Nonnull surface_control) __INTRODUCED_IN(29);
 
 /**
  * Sets the callback that will be invoked when the updates from this transaction
@@ -279,8 +281,10 @@
  *
  * Available since API level 29.
  */
-void ASurfaceTransaction_setOnComplete(ASurfaceTransaction* transaction, void* context,
-                                       ASurfaceTransaction_OnComplete func) __INTRODUCED_IN(29);
+void ASurfaceTransaction_setOnComplete(ASurfaceTransaction* _Nonnull transaction,
+                                       void* _Null_unspecified context,
+                                       ASurfaceTransaction_OnComplete _Nonnull func)
+        __INTRODUCED_IN(29);
 
 /**
  * Sets the callback that will be invoked when the updates from this transaction are applied and are
@@ -289,8 +293,10 @@
  *
  * Available since API level 31.
  */
-void ASurfaceTransaction_setOnCommit(ASurfaceTransaction* transaction, void* context,
-                                    ASurfaceTransaction_OnCommit func) __INTRODUCED_IN(31);
+void ASurfaceTransaction_setOnCommit(ASurfaceTransaction* _Nonnull transaction,
+                                     void* _Null_unspecified context,
+                                     ASurfaceTransaction_OnCommit _Nonnull func)
+        __INTRODUCED_IN(31);
 
 /**
  * Reparents the \a surface_control from its old parent to the \a new_parent surface control.
@@ -300,14 +306,14 @@
  *
  * Available since API level 29.
  */
-void ASurfaceTransaction_reparent(ASurfaceTransaction* transaction,
-                                  ASurfaceControl* surface_control, ASurfaceControl* new_parent)
-                                  __INTRODUCED_IN(29);
+void ASurfaceTransaction_reparent(ASurfaceTransaction* _Nonnull transaction,
+                                  ASurfaceControl* _Nonnull surface_control,
+                                  ASurfaceControl* _Nullable new_parent) __INTRODUCED_IN(29);
 
 /**
  * Parameter for ASurfaceTransaction_setVisibility().
  */
-enum {
+enum ASurfaceTransactionVisibility : int8_t {
     ASURFACE_TRANSACTION_VISIBILITY_HIDE = 0,
     ASURFACE_TRANSACTION_VISIBILITY_SHOW = 1,
 };
@@ -318,9 +324,10 @@
  *
  * Available since API level 29.
  */
-void ASurfaceTransaction_setVisibility(ASurfaceTransaction* transaction,
-                                       ASurfaceControl* surface_control, int8_t visibility)
-                                       __INTRODUCED_IN(29);
+void ASurfaceTransaction_setVisibility(ASurfaceTransaction* _Nonnull transaction,
+                                       ASurfaceControl* _Nonnull surface_control,
+                                       enum ASurfaceTransactionVisibility visibility)
+        __INTRODUCED_IN(29);
 
 /**
  * Updates the z order index for \a surface_control. Note that the z order for a surface
@@ -331,9 +338,9 @@
  *
  * Available since API level 29.
  */
-void ASurfaceTransaction_setZOrder(ASurfaceTransaction* transaction,
-                                   ASurfaceControl* surface_control, int32_t z_order)
-                                   __INTRODUCED_IN(29);
+void ASurfaceTransaction_setZOrder(ASurfaceTransaction* _Nonnull transaction,
+                                   ASurfaceControl* _Nonnull surface_control, int32_t z_order)
+        __INTRODUCED_IN(29);
 
 /**
  * Updates the AHardwareBuffer displayed for \a surface_control. If not -1, the
@@ -348,9 +355,10 @@
  *
  * Available since API level 29.
  */
-void ASurfaceTransaction_setBuffer(ASurfaceTransaction* transaction,
-                                   ASurfaceControl* surface_control, AHardwareBuffer* buffer,
-                                   int acquire_fence_fd) __INTRODUCED_IN(29);
+void ASurfaceTransaction_setBuffer(ASurfaceTransaction* _Nonnull transaction,
+                                   ASurfaceControl* _Nonnull surface_control,
+                                   AHardwareBuffer* _Nonnull buffer, int acquire_fence_fd)
+        __INTRODUCED_IN(29);
 
 /**
  * Updates the color for \a surface_control.  This will make the background color for the
@@ -360,23 +368,23 @@
  *
  * Available since API level 29.
  */
-void ASurfaceTransaction_setColor(ASurfaceTransaction* transaction,
-                                  ASurfaceControl* surface_control, float r, float g, float b,
-                                  float alpha, ADataSpace dataspace)
-                                  __INTRODUCED_IN(29);
+void ASurfaceTransaction_setColor(ASurfaceTransaction* _Nonnull transaction,
+                                  ASurfaceControl* _Nonnull surface_control, float r, float g,
+                                  float b, float alpha, enum ADataSpace dataspace)
+        __INTRODUCED_IN(29);
 
 /**
  * \param source The sub-rect within the buffer's content to be rendered inside the surface's area
  * The surface's source rect is clipped by the bounds of its current buffer. The source rect's width
  * and height must be > 0.
  *
- * \param destination Specifies the rect in the parent's space where this surface will be drawn. The post
- * source rect bounds are scaled to fit the destination rect. The surface's destination rect is
+ * \param destination Specifies the rect in the parent's space where this surface will be drawn. The
+ * post source rect bounds are scaled to fit the destination rect. The surface's destination rect is
  * clipped by the bounds of its parent. The destination rect's width and height must be > 0.
  *
- * \param transform The transform applied after the source rect is applied to the buffer. This parameter
- * should be set to 0 for no transform. To specify a transfrom use the NATIVE_WINDOW_TRANSFORM_*
- * enum.
+ * \param transform The transform applied after the source rect is applied to the buffer. This
+ * parameter should be set to 0 for no transform. To specify a transfrom use the
+ * NATIVE_WINDOW_TRANSFORM_* enum.
  *
  * Available since API level 29.
  *
@@ -385,10 +393,10 @@
  * to set different properties at different times, instead of having to specify all the desired
  * properties at once.
  */
-void ASurfaceTransaction_setGeometry(ASurfaceTransaction* transaction,
-                                     ASurfaceControl* surface_control, const ARect& source,
+void ASurfaceTransaction_setGeometry(ASurfaceTransaction* _Nonnull transaction,
+                                     ASurfaceControl* _Nonnull surface_control, const ARect& source,
                                      const ARect& destination, int32_t transform)
-                                     __INTRODUCED_IN(29);
+        __INTRODUCED_IN(29);
 
 /**
  * Bounds the surface and its children to the bounds specified. The crop and buffer size will be
@@ -399,9 +407,9 @@
  *
  * Available since API level 31.
  */
-void ASurfaceTransaction_setCrop(ASurfaceTransaction* transaction,
-                                       ASurfaceControl* surface_control, const ARect& crop)
-                                       __INTRODUCED_IN(31);
+void ASurfaceTransaction_setCrop(ASurfaceTransaction* _Nonnull transaction,
+                                 ASurfaceControl* _Nonnull surface_control, const ARect& crop)
+        __INTRODUCED_IN(31);
 
 /**
  * Specifies the position in the parent's space where the surface will be drawn.
@@ -411,9 +419,9 @@
  *
  * Available since API level 31.
  */
-void ASurfaceTransaction_setPosition(ASurfaceTransaction* transaction,
-                                     ASurfaceControl* surface_control, int32_t x, int32_t y)
-                                     __INTRODUCED_IN(31);
+void ASurfaceTransaction_setPosition(ASurfaceTransaction* _Nonnull transaction,
+                                     ASurfaceControl* _Nonnull surface_control, int32_t x,
+                                     int32_t y) __INTRODUCED_IN(31);
 
 /**
  * \param transform The transform applied after the source rect is applied to the buffer. This
@@ -422,9 +430,9 @@
  *
  * Available since API level 31.
  */
-void ASurfaceTransaction_setBufferTransform(ASurfaceTransaction* transaction,
-                                      ASurfaceControl* surface_control, int32_t transform)
-                                      __INTRODUCED_IN(31);
+void ASurfaceTransaction_setBufferTransform(ASurfaceTransaction* _Nonnull transaction,
+                                            ASurfaceControl* _Nonnull surface_control,
+                                            int32_t transform) __INTRODUCED_IN(31);
 
 /**
  * Sets an x and y scale of a surface with (0, 0) as the centerpoint of the scale.
@@ -434,13 +442,13 @@
  *
  * Available since API level 31.
  */
-void ASurfaceTransaction_setScale(ASurfaceTransaction* transaction,
-                                      ASurfaceControl* surface_control, float xScale, float yScale)
-                                      __INTRODUCED_IN(31);
+void ASurfaceTransaction_setScale(ASurfaceTransaction* _Nonnull transaction,
+                                  ASurfaceControl* _Nonnull surface_control, float xScale,
+                                  float yScale) __INTRODUCED_IN(31);
 /**
  * Parameter for ASurfaceTransaction_setBufferTransparency().
  */
-enum {
+enum ASurfaceTransactionTransparency : int8_t {
     ASURFACE_TRANSACTION_TRANSPARENCY_TRANSPARENT = 0,
     ASURFACE_TRANSACTION_TRANSPARENCY_TRANSLUCENT = 1,
     ASURFACE_TRANSACTION_TRANSPARENCY_OPAQUE = 2,
@@ -452,9 +460,9 @@
  *
  * Available since API level 29.
  */
-void ASurfaceTransaction_setBufferTransparency(ASurfaceTransaction* transaction,
-                                               ASurfaceControl* surface_control,
-                                               int8_t transparency)
+void ASurfaceTransaction_setBufferTransparency(ASurfaceTransaction* _Nonnull transaction,
+                                               ASurfaceControl* _Nonnull surface_control,
+                                               enum ASurfaceTransactionTransparency transparency)
                                                __INTRODUCED_IN(29);
 
 /**
@@ -463,9 +471,10 @@
  *
  * Available since API level 29.
  */
-void ASurfaceTransaction_setDamageRegion(ASurfaceTransaction* transaction,
-                                         ASurfaceControl* surface_control, const ARect rects[],
-                                         uint32_t count) __INTRODUCED_IN(29);
+void ASurfaceTransaction_setDamageRegion(ASurfaceTransaction* _Nonnull transaction,
+                                         ASurfaceControl* _Nonnull surface_control,
+                                         const ARect* _Nullable rects, uint32_t count)
+                                         __INTRODUCED_IN(29);
 
 /**
  * Specifies a desiredPresentTime for the transaction. The framework will try to present
@@ -479,7 +488,7 @@
  *
  * Available since API level 29.
  */
-void ASurfaceTransaction_setDesiredPresentTime(ASurfaceTransaction* transaction,
+void ASurfaceTransaction_setDesiredPresentTime(ASurfaceTransaction* _Nonnull transaction,
                                                int64_t desiredPresentTime) __INTRODUCED_IN(29);
 
 /**
@@ -489,8 +498,8 @@
  *
  * Available since API level 29.
  */
-void ASurfaceTransaction_setBufferAlpha(ASurfaceTransaction* transaction,
-                                        ASurfaceControl* surface_control, float alpha)
+void ASurfaceTransaction_setBufferAlpha(ASurfaceTransaction* _Nonnull transaction,
+                                        ASurfaceControl* _Nonnull surface_control, float alpha)
                                         __INTRODUCED_IN(29);
 
 /**
@@ -500,9 +509,9 @@
  *
  * Available since API level 29.
  */
-void ASurfaceTransaction_setBufferDataSpace(ASurfaceTransaction* transaction,
-                                            ASurfaceControl* surface_control, ADataSpace data_space)
-                                            __INTRODUCED_IN(29);
+void ASurfaceTransaction_setBufferDataSpace(ASurfaceTransaction* _Nonnull transaction,
+                                            ASurfaceControl* _Nonnull surface_control,
+                                            enum ADataSpace data_space) __INTRODUCED_IN(29);
 
 /**
  * SMPTE ST 2086 "Mastering Display Color Volume" static metadata
@@ -512,9 +521,9 @@
  *
  * Available since API level 29.
  */
-void ASurfaceTransaction_setHdrMetadata_smpte2086(ASurfaceTransaction* transaction,
-                                                  ASurfaceControl* surface_control,
-                                                  struct AHdrMetadata_smpte2086* metadata)
+void ASurfaceTransaction_setHdrMetadata_smpte2086(ASurfaceTransaction* _Nonnull transaction,
+                                                  ASurfaceControl* _Nonnull surface_control,
+                                                  struct AHdrMetadata_smpte2086* _Nullable metadata)
                                                   __INTRODUCED_IN(29);
 
 /**
@@ -525,9 +534,9 @@
  *
  * Available since API level 29.
  */
-void ASurfaceTransaction_setHdrMetadata_cta861_3(ASurfaceTransaction* transaction,
-                                                 ASurfaceControl* surface_control,
-                                                 struct AHdrMetadata_cta861_3* metadata)
+void ASurfaceTransaction_setHdrMetadata_cta861_3(ASurfaceTransaction* _Nonnull transaction,
+                                                 ASurfaceControl* _Nonnull surface_control,
+                                                 struct AHdrMetadata_cta861_3* _Nullable metadata)
                                                  __INTRODUCED_IN(29);
 
 /**
@@ -577,10 +586,10 @@
  *
  * Available since API level 34.
  */
-void ASurfaceTransaction_setExtendedRangeBrightness(ASurfaceTransaction* transaction,
-                                            ASurfaceControl* surface_control,
-                                            float currentBufferRatio,
-                                            float desiredRatio) __INTRODUCED_IN(__ANDROID_API_U__);
+void ASurfaceTransaction_setExtendedRangeBrightness(ASurfaceTransaction* _Nonnull transaction,
+                                                    ASurfaceControl* _Nonnull surface_control,
+                                                    float currentBufferRatio, float desiredRatio)
+                                                    __INTRODUCED_IN(__ANDROID_API_U__);
 
 /**
  * Sets the desired HDR headroom for the layer. See: ASurfaceTransaction_setExtendedRangeBrightness,
@@ -616,8 +625,8 @@
  *
  * Available since API level 35.
  */
-void ASurfaceTransaction_setDesiredHdrHeadroom(ASurfaceTransaction* transaction,
-                                               ASurfaceControl* surface_control,
+void ASurfaceTransaction_setDesiredHdrHeadroom(ASurfaceTransaction* _Nonnull transaction,
+                                               ASurfaceControl* _Nonnull surface_control,
                                                float desiredHeadroom)
         __INTRODUCED_IN(__ANDROID_API_V__);
 
@@ -629,8 +638,8 @@
  *
  * Available since API level 30.
  */
-void ASurfaceTransaction_setFrameRate(ASurfaceTransaction* transaction,
-                                      ASurfaceControl* surface_control, float frameRate,
+void ASurfaceTransaction_setFrameRate(ASurfaceTransaction* _Nonnull transaction,
+                                      ASurfaceControl* _Nonnull surface_control, float frameRate,
                                       int8_t compatibility) __INTRODUCED_IN(30);
 
 /**
@@ -665,10 +674,11 @@
  *
  * Available since API level 31.
  */
-void ASurfaceTransaction_setFrameRateWithChangeStrategy(ASurfaceTransaction* transaction,
-                                      ASurfaceControl* surface_control, float frameRate,
-                                      int8_t compatibility, int8_t changeFrameRateStrategy)
-                                      __INTRODUCED_IN(31);
+void ASurfaceTransaction_setFrameRateWithChangeStrategy(ASurfaceTransaction* _Nonnull transaction,
+                                                        ASurfaceControl* _Nonnull surface_control,
+                                                        float frameRate, int8_t compatibility,
+                                                        int8_t changeFrameRateStrategy)
+                                                        __INTRODUCED_IN(31);
 
 /**
  * Clears the frame rate which is set for \a surface_control.
@@ -691,8 +701,8 @@
  *
  * Available since API level 34.
  */
-void ASurfaceTransaction_clearFrameRate(ASurfaceTransaction* transaction,
-                                        ASurfaceControl* surface_control)
+void ASurfaceTransaction_clearFrameRate(ASurfaceTransaction* _Nonnull transaction,
+                                        ASurfaceControl* _Nonnull surface_control)
                                         __INTRODUCED_IN(__ANDROID_API_U__);
 
 /**
@@ -721,10 +731,9 @@
  * \param surface_control The ASurfaceControl on which to control buffer backpressure behavior.
  * \param enableBackPressure Whether to enable back pressure.
  */
-void ASurfaceTransaction_setEnableBackPressure(ASurfaceTransaction* transaction,
-                                               ASurfaceControl* surface_control,
-                                               bool enableBackPressure)
-                                               __INTRODUCED_IN(31);
+void ASurfaceTransaction_setEnableBackPressure(ASurfaceTransaction* _Nonnull transaction,
+                                               ASurfaceControl* _Nonnull surface_control,
+                                               bool enableBackPressure) __INTRODUCED_IN(31);
 
 /**
  * Sets the frame timeline to use in SurfaceFlinger.
@@ -744,7 +753,7 @@
  * to the corresponding expected presentation time and deadline from the frame to be rendered. A
  * stale or invalid value will be ignored.
  */
-void ASurfaceTransaction_setFrameTimeline(ASurfaceTransaction* transaction,
+void ASurfaceTransaction_setFrameTimeline(ASurfaceTransaction* _Nonnull transaction,
                                           AVsyncId vsyncId) __INTRODUCED_IN(33);
 
 __END_DECLS
diff --git a/include/ftl/algorithm.h b/include/ftl/algorithm.h
index c0f6768..68826bb 100644
--- a/include/ftl/algorithm.h
+++ b/include/ftl/algorithm.h
@@ -24,6 +24,18 @@
 
 namespace android::ftl {
 
+// Determines if a container contains a value. This is a simplified version of the C++23
+// std::ranges::contains function.
+//
+//   const ftl::StaticVector vector = {1, 2, 3};
+//   assert(ftl::contains(vector, 1));
+//
+// TODO: Remove in C++23.
+template <typename Container, typename Value>
+auto contains(const Container& container, const Value& value) -> bool {
+  return std::find(container.begin(), container.end(), value) != container.end();
+}
+
 // Adapter for std::find_if that converts the return value from iterator to optional.
 //
 //   const ftl::StaticVector vector = {"upside"sv, "down"sv, "cake"sv};
diff --git a/include/ftl/non_null.h b/include/ftl/non_null.h
index 35d09d7..4a5d8bf 100644
--- a/include/ftl/non_null.h
+++ b/include/ftl/non_null.h
@@ -68,26 +68,28 @@
   constexpr NonNull(const NonNull&) = default;
   constexpr NonNull& operator=(const NonNull&) = default;
 
-  constexpr const Pointer& get() const { return pointer_; }
-  constexpr explicit operator const Pointer&() const { return get(); }
+  [[nodiscard]] constexpr const Pointer& get() const { return pointer_; }
+  [[nodiscard]] constexpr explicit operator const Pointer&() const { return get(); }
 
   // Move operations. These break the invariant, so care must be taken to avoid subsequent access.
 
   constexpr NonNull(NonNull&&) = default;
   constexpr NonNull& operator=(NonNull&&) = default;
 
-  constexpr Pointer take() && { return std::move(pointer_); }
-  constexpr explicit operator Pointer() && { return take(); }
+  [[nodiscard]] constexpr Pointer take() && { return std::move(pointer_); }
+  [[nodiscard]] constexpr explicit operator Pointer() && { return take(); }
 
   // Dereferencing.
-  constexpr decltype(auto) operator*() const { return *get(); }
-  constexpr decltype(auto) operator->() const { return get(); }
+  [[nodiscard]] constexpr decltype(auto) operator*() const { return *get(); }
+  [[nodiscard]] constexpr decltype(auto) operator->() const { return get(); }
+
+  [[nodiscard]] constexpr explicit operator bool() const { return !(pointer_ == nullptr); }
 
   // Private constructor for ftl::as_non_null. Excluded from candidate constructors for conversions
   // through the passkey idiom, for clear compilation errors.
   template <typename P>
   constexpr NonNull(Passkey, P&& pointer) : pointer_(std::forward<P>(pointer)) {
-    if (!pointer_) std::abort();
+    if (pointer_ == nullptr) std::abort();
   }
 
  private:
@@ -98,11 +100,13 @@
 };
 
 template <typename P>
-constexpr auto as_non_null(P&& pointer) -> NonNull<std::decay_t<P>> {
+[[nodiscard]] constexpr auto as_non_null(P&& pointer) -> NonNull<std::decay_t<P>> {
   using Passkey = typename NonNull<std::decay_t<P>>::Passkey;
   return {Passkey{}, std::forward<P>(pointer)};
 }
 
+// NonNull<P> <=> NonNull<Q>
+
 template <typename P, typename Q>
 constexpr bool operator==(const NonNull<P>& lhs, const NonNull<Q>& rhs) {
   return lhs.get() == rhs.get();
@@ -113,4 +117,96 @@
   return !operator==(lhs, rhs);
 }
 
+template <typename P, typename Q>
+constexpr bool operator<(const NonNull<P>& lhs, const NonNull<Q>& rhs) {
+  return lhs.get() < rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator<=(const NonNull<P>& lhs, const NonNull<Q>& rhs) {
+  return lhs.get() <= rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator>=(const NonNull<P>& lhs, const NonNull<Q>& rhs) {
+  return lhs.get() >= rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator>(const NonNull<P>& lhs, const NonNull<Q>& rhs) {
+  return lhs.get() > rhs.get();
+}
+
+// NonNull<P> <=> Q
+
+template <typename P, typename Q>
+constexpr bool operator==(const NonNull<P>& lhs, const Q& rhs) {
+  return lhs.get() == rhs;
+}
+
+template <typename P, typename Q>
+constexpr bool operator!=(const NonNull<P>& lhs, const Q& rhs) {
+  return lhs.get() != rhs;
+}
+
+template <typename P, typename Q>
+constexpr bool operator<(const NonNull<P>& lhs, const Q& rhs) {
+  return lhs.get() < rhs;
+}
+
+template <typename P, typename Q>
+constexpr bool operator<=(const NonNull<P>& lhs, const Q& rhs) {
+  return lhs.get() <= rhs;
+}
+
+template <typename P, typename Q>
+constexpr bool operator>=(const NonNull<P>& lhs, const Q& rhs) {
+  return lhs.get() >= rhs;
+}
+
+template <typename P, typename Q>
+constexpr bool operator>(const NonNull<P>& lhs, const Q& rhs) {
+  return lhs.get() > rhs;
+}
+
+// P <=> NonNull<Q>
+
+template <typename P, typename Q>
+constexpr bool operator==(const P& lhs, const NonNull<Q>& rhs) {
+  return lhs == rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator!=(const P& lhs, const NonNull<Q>& rhs) {
+  return lhs != rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator<(const P& lhs, const NonNull<Q>& rhs) {
+  return lhs < rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator<=(const P& lhs, const NonNull<Q>& rhs) {
+  return lhs <= rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator>=(const P& lhs, const NonNull<Q>& rhs) {
+  return lhs >= rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator>(const P& lhs, const NonNull<Q>& rhs) {
+  return lhs > rhs.get();
+}
+
 }  // namespace android::ftl
+
+// Specialize std::hash for ftl::NonNull<T>
+template <typename P>
+struct std::hash<android::ftl::NonNull<P>> {
+  std::size_t operator()(const android::ftl::NonNull<P>& ptr) const {
+    return std::hash<P>()(ptr.get());
+  }
+};
diff --git a/include/input/Input.h b/include/input/Input.h
index 00757a7..3f81fb0 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -43,7 +43,7 @@
 #ifdef __linux__
     /* This event was generated or modified by accessibility service. */
     AKEY_EVENT_FLAG_IS_ACCESSIBILITY_EVENT =
-            android::os::IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT, // 0x800,
+            android::os::IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT,
 #else
     AKEY_EVENT_FLAG_IS_ACCESSIBILITY_EVENT = 0x800,
 #endif
@@ -54,11 +54,11 @@
     AKEY_EVENT_FLAG_START_TRACKING = 0x40000000,
 
     /* Key event is inconsistent with previously sent key events. */
-    AKEY_EVENT_FLAG_TAINTED = 0x80000000,
+    AKEY_EVENT_FLAG_TAINTED = android::os::IInputConstants::INPUT_EVENT_FLAG_TAINTED,
 };
 
 enum {
-
+    // AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED is defined in include/android/input.h
     /**
      * This flag indicates that the window that received this motion event is partly
      * or wholly obscured by another visible window above it.  This flag is set to true
@@ -69,13 +69,16 @@
      * to drop the suspect touches or to take additional precautions to confirm the user's
      * actual intent.
      */
-    AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED = 0x2,
-
+    AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED =
+            android::os::IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED,
+    AMOTION_EVENT_FLAG_HOVER_EXIT_PENDING =
+            android::os::IInputConstants::MOTION_EVENT_FLAG_HOVER_EXIT_PENDING,
     /**
      * This flag indicates that the event has been generated by a gesture generator. It
      * provides a hint to the GestureDetector to not apply any touch slop.
      */
-    AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE = 0x8,
+    AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE =
+            android::os::IInputConstants::MOTION_EVENT_FLAG_IS_GENERATED_GESTURE,
 
     /**
      * This flag indicates that the event will not cause a focus change if it is directed to an
@@ -83,20 +86,24 @@
      * gestures to allow the user to direct gestures to an unfocused window without bringing it
      * into focus.
      */
-    AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE = 0x40,
+    AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE =
+            android::os::IInputConstants::MOTION_EVENT_FLAG_NO_FOCUS_CHANGE,
 
 #if defined(__linux__)
     /**
      * This event was generated or modified by accessibility service.
      */
     AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT =
-            android::os::IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT, // 0x800,
+            android::os::IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT,
 #else
     AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT = 0x800,
 #endif
 
+    AMOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS =
+            android::os::IInputConstants::MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS,
+
     /* Motion event is inconsistent with previously sent motion events. */
-    AMOTION_EVENT_FLAG_TAINTED = 0x80000000,
+    AMOTION_EVENT_FLAG_TAINTED = android::os::IInputConstants::INPUT_EVENT_FLAG_TAINTED,
 };
 
 /**
@@ -116,9 +123,10 @@
 /**
  * This flag indicates that the point up event has been canceled.
  * Typically this is used for palm event when the user has accidental touches.
- * TODO: Adjust flag to public api
+ * TODO(b/338143308): Add this to NDK
  */
-constexpr int32_t AMOTION_EVENT_FLAG_CANCELED = 0x20;
+constexpr int32_t AMOTION_EVENT_FLAG_CANCELED =
+        android::os::IInputConstants::INPUT_EVENT_FLAG_CANCELED;
 
 enum {
     /*
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index 663c0c4..e93fe8c 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -115,6 +115,8 @@
     ACCURACY_LOW = 1,
     ACCURACY_MEDIUM = 2,
     ACCURACY_HIGH = 3,
+
+    ftl_last = ACCURACY_HIGH,
 };
 
 enum class InputDeviceSensorReportingMode : int32_t {
@@ -279,7 +281,7 @@
     void initialize(int32_t id, int32_t generation, int32_t controllerNumber,
                     const InputDeviceIdentifier& identifier, const std::string& alias,
                     bool isExternal, bool hasMic, int32_t associatedDisplayId,
-                    InputDeviceViewBehavior viewBehavior = {{}});
+                    InputDeviceViewBehavior viewBehavior = {{}}, bool enabled = true);
 
     inline int32_t getId() const { return mId; }
     inline int32_t getControllerNumber() const { return mControllerNumber; }
@@ -348,6 +350,9 @@
 
     inline int32_t getAssociatedDisplayId() const { return mAssociatedDisplayId; }
 
+    inline void setEnabled(bool enabled) { mEnabled = enabled; }
+    inline bool isEnabled() const { return mEnabled; }
+
 private:
     int32_t mId;
     int32_t mGeneration;
@@ -362,6 +367,7 @@
     std::shared_ptr<KeyCharacterMap> mKeyCharacterMap;
     std::optional<InputDeviceUsiVersion> mUsiVersion;
     int32_t mAssociatedDisplayId;
+    bool mEnabled;
 
     bool mHasVibrator;
     bool mHasBattery;
diff --git a/include/private/performance_hint_private.h b/include/private/performance_hint_private.h
index d8f9db4..8c356d0 100644
--- a/include/private/performance_hint_private.h
+++ b/include/private/performance_hint_private.h
@@ -18,6 +18,7 @@
 #define ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H
 
 #include <stdint.h>
+#include <android/performance_hint.h>
 
 __BEGIN_DECLS
 
@@ -75,6 +76,15 @@
     GPU_LOAD_RESET = 7,
 };
 
+// Allows access to PowerHAL's SessionTags without needing to import its AIDL
+enum class SessionTag : int32_t {
+  OTHER = 0,
+  SURFACEFLINGER = 1,
+  HWUI = 2,
+  GAME = 3,
+  APP = 4,
+};
+
 /**
  * Sends performance hints to inform the hint session of changes in the workload.
  *
@@ -83,14 +93,22 @@
  * @return 0 on success
  *         EPIPE if communication with the system service has failed.
  */
-int APerformanceHint_sendHint(void* session, SessionHint hint);
+int APerformanceHint_sendHint(APerformanceHintSession* session, SessionHint hint);
 
 /**
  * Return the list of thread ids, this API should only be used for testing only.
  */
-int APerformanceHint_getThreadIds(void* aPerformanceHintSession,
+int APerformanceHint_getThreadIds(APerformanceHintSession* session,
                                   int32_t* const threadIds, size_t* const size);
 
+/**
+ * Creates a session with additional options
+ */
+APerformanceHintSession* APerformanceHint_createSessionInternal(APerformanceHintManager* manager,
+                                        const int32_t* threadIds, size_t size,
+                                        int64_t initialTargetWorkDurationNanos, SessionTag tag);
+
+
 __END_DECLS
 
 #endif // ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H
diff --git a/libs/battery/MultiStateCounter.h b/libs/battery/MultiStateCounter.h
index 7da8d51..04b7186 100644
--- a/libs/battery/MultiStateCounter.h
+++ b/libs/battery/MultiStateCounter.h
@@ -62,6 +62,12 @@
 
     void setState(state_t state, time_t timestamp);
 
+    /**
+     * Copies the current state and accumulated times-in-state from the source. Resets
+     * the accumulated value.
+     */
+    void copyStatesFrom(const MultiStateCounter<T>& source);
+
     void setValue(state_t state, const T& value);
 
     /**
@@ -193,6 +199,22 @@
 }
 
 template <class T>
+void MultiStateCounter<T>::copyStatesFrom(const MultiStateCounter<T>& source) {
+    if (stateCount != source.stateCount) {
+        ALOGE("State count mismatch: %u vs. %u\n", stateCount, source.stateCount);
+        return;
+    }
+
+    currentState = source.currentState;
+    for (int i = 0; i < stateCount; i++) {
+        states[i].timeInStateSinceUpdate = source.states[i].timeInStateSinceUpdate;
+        states[i].counter = emptyValue;
+    }
+    lastStateChangeTimestamp = source.lastStateChangeTimestamp;
+    lastUpdateTimestamp = source.lastUpdateTimestamp;
+}
+
+template <class T>
 void MultiStateCounter<T>::setValue(state_t state, const T& value) {
     states[state].counter = value;
 }
diff --git a/libs/battery/MultiStateCounterTest.cpp b/libs/battery/MultiStateCounterTest.cpp
index cb11a54..a51a38a 100644
--- a/libs/battery/MultiStateCounterTest.cpp
+++ b/libs/battery/MultiStateCounterTest.cpp
@@ -72,6 +72,22 @@
     EXPECT_DOUBLE_EQ(4.0, testCounter.getCount(2));
 }
 
+TEST_F(MultiStateCounterTest, copyStatesFrom) {
+    DoubleMultiStateCounter sourceCounter(3, 0);
+
+    sourceCounter.updateValue(0, 0);
+    sourceCounter.setState(1, 0);
+    sourceCounter.setState(2, 1000);
+
+    DoubleMultiStateCounter testCounter(3, 0);
+    testCounter.copyStatesFrom(sourceCounter);
+    testCounter.updateValue(6.0, 3000);
+
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(0));
+    EXPECT_DOUBLE_EQ(2.0, testCounter.getCount(1));
+    EXPECT_DOUBLE_EQ(4.0, testCounter.getCount(2));
+}
+
 TEST_F(MultiStateCounterTest, setEnabled) {
     DoubleMultiStateCounter testCounter(3, 0);
     testCounter.updateValue(0, 0);
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index eec12e4..090e35b 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -229,6 +229,7 @@
 cc_library_headers {
     name: "trusty_mock_headers",
     host_supported: true,
+    vendor_available: true,
 
     export_include_dirs: [
         "trusty/include",
@@ -243,12 +244,18 @@
 cc_defaults {
     name: "trusty_mock_defaults",
     host_supported: true,
+    vendor_available: true,
 
     header_libs: [
         "libbinder_headers_base",
         "liblog_stub",
         "trusty_mock_headers",
     ],
+    export_header_lib_headers: [
+        "libbinder_headers_base",
+        "liblog_stub",
+        "trusty_mock_headers",
+    ],
 
     shared_libs: [
         "libutils_binder_sdk",
@@ -358,9 +365,6 @@
 
     // for vndbinder
     vendor_available: true,
-    vndk: {
-        enabled: true,
-    },
     recovery_available: true,
     double_loadable: true,
     // TODO(b/153609531): remove when no longer needed.
@@ -442,7 +446,7 @@
     },
 }
 
-cc_library_static {
+cc_library {
     name: "libbinder_rpc_no_kernel",
     vendor_available: true,
     defaults: [
@@ -454,7 +458,7 @@
     ],
 }
 
-cc_library_static {
+cc_library {
     name: "libbinder_rpc_no_blob",
     vendor_available: true,
     defaults: [
@@ -470,7 +474,7 @@
     ],
 }
 
-cc_library_static {
+cc_library {
     name: "libbinder_rpc_no_native_handle",
     vendor_available: true,
     defaults: [
@@ -486,7 +490,7 @@
     ],
 }
 
-cc_library_static {
+cc_library {
     name: "libbinder_rpc_single_threaded",
     defaults: [
         "libbinder_common_defaults",
@@ -501,7 +505,7 @@
     ],
 }
 
-cc_library_static {
+cc_library {
     name: "libbinder_rpc_single_threaded_no_kernel",
     defaults: [
         "libbinder_common_defaults",
@@ -669,6 +673,7 @@
         "//packages/modules/Virtualization:__subpackages__",
         "//device/google/cuttlefish/shared/minidroid:__subpackages__",
         "//system/software_defined_vehicle:__subpackages__",
+        "//visibility:any_system_partition",
     ],
 }
 
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 1ffdad5..611169d 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -30,6 +30,7 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
+#include <algorithm>
 
 #include <binder/Binder.h>
 #include <binder/BpBinder.h>
diff --git a/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl b/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl
index f8a8843..3ddfefa 100644
--- a/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl
+++ b/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl
@@ -43,6 +43,18 @@
     @utf8InCpp String[] getNamesForUids(in int[] uids);
 
     /**
+     * Return the UID associated with the given package name.
+     * Note that the same package will have different UIDs under different UserHandle on
+     * the same device.
+     * @param packageName The full name (i.e. com.google.apps.contacts) of the desired package.
+     * @param flags Additional option flags to modify the data returned.
+     * @param userId The user handle identifier to look up the package under.
+     * @return Returns an integer UID who owns the given package name, or -1 if no such package is
+     *            available to the caller.
+     */
+     int getPackageUid(in @utf8InCpp String packageName, in long flags, in int userId);
+
+    /**
      * Returns the name of the installer (a package) which installed the named
      * package. Preloaded packages return the string "preload". Sideloaded packages
      * return an empty string. Unknown or unknowable are returned as empty strings.
diff --git a/libs/binder/binder_module.h b/libs/binder/binder_module.h
index eef07ae..b3a2d9e 100644
--- a/libs/binder/binder_module.h
+++ b/libs/binder/binder_module.h
@@ -32,77 +32,4 @@
 #include <linux/android/binder.h>
 #include <sys/ioctl.h>
 
-#ifndef BR_FROZEN_REPLY
-// Temporary definition of BR_FROZEN_REPLY. For production
-// this will come from UAPI binder.h
-#define BR_FROZEN_REPLY _IO('r', 18)
-#endif // BR_FROZEN_REPLY
-
-#ifndef BINDER_FREEZE
-/*
- * Temporary definitions for freeze support. For the final version
- * these will be defined in the UAPI binder.h file from upstream kernel.
- */
-#define BINDER_FREEZE _IOW('b', 14, struct binder_freeze_info)
-
-struct binder_freeze_info {
-    //
-    // Group-leader PID of process to be frozen
-    //
-    uint32_t pid;
-    //
-    // Enable(1) / Disable(0) freeze for given PID
-    //
-    uint32_t enable;
-    //
-    // Timeout to wait for transactions to drain.
-    // 0: don't wait (ioctl will return EAGAIN if not drained)
-    // N: number of ms to wait
-    uint32_t timeout_ms;
-};
-#endif // BINDER_FREEZE
-
-#ifndef BINDER_GET_FROZEN_INFO
-
-#define BINDER_GET_FROZEN_INFO _IOWR('b', 15, struct binder_frozen_status_info)
-
-struct binder_frozen_status_info {
-    //
-    // Group-leader PID of process to be queried
-    //
-    __u32 pid;
-    //
-    // Indicates whether the process has received any sync calls since last
-    // freeze (cleared at freeze/unfreeze)
-    // bit 0: received sync transaction after being frozen
-    // bit 1: new pending sync transaction during freezing
-    //
-    __u32 sync_recv;
-    //
-    // Indicates whether the process has received any async calls since last
-    // freeze (cleared at freeze/unfreeze)
-    //
-    __u32 async_recv;
-};
-#endif // BINDER_GET_FROZEN_INFO
-
-#ifndef BR_ONEWAY_SPAM_SUSPECT
-// Temporary definition of BR_ONEWAY_SPAM_SUSPECT. For production
-// this will come from UAPI binder.h
-#define BR_ONEWAY_SPAM_SUSPECT _IO('r', 19)
-#endif // BR_ONEWAY_SPAM_SUSPECT
-
-#ifndef BINDER_ENABLE_ONEWAY_SPAM_DETECTION
-/*
- * Temporary definitions for oneway spam detection support. For the final version
- * these will be defined in the UAPI binder.h file from upstream kernel.
- */
-#define BINDER_ENABLE_ONEWAY_SPAM_DETECTION _IOW('b', 16, __u32)
-#endif // BINDER_ENABLE_ONEWAY_SPAM_DETECTION
-
-#ifndef BR_TRANSACTION_PENDING_FROZEN
-// Temporary definition of BR_TRANSACTION_PENDING_FROZEN until UAPI binder.h includes it.
-#define BR_TRANSACTION_PENDING_FROZEN _IO('r', 20)
-#endif // BR_TRANSACTION_PENDING_FROZEN
-
 #endif // _BINDER_MODULE_H_
diff --git a/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp b/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
index 7d0acd1..392ebb5 100644
--- a/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
+++ b/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
@@ -73,6 +73,17 @@
         const ARpcSession_FileDescriptorTransportMode modes[],
         size_t modes_len);
 
+// Sets the maximum number of threads that the Server will use for
+// incoming client connections.
+//
+// This must be called before adding a client session. This corresponds
+// to the number of incoming connections to RpcSession objects in the
+// server, which will correspond to the number of outgoing connections
+// in client RpcSession objects.
+//
+// If this is not specified, this will be a single-threaded server.
+void ARpcServer_setMaxThreads(ARpcServer* server, size_t threads);
+
 // Runs ARpcServer_join() in a background thread. Immediately returns.
 void ARpcServer_start(ARpcServer* server);
 
diff --git a/libs/binder/libbinder_rpc_unstable.cpp b/libs/binder/libbinder_rpc_unstable.cpp
index cb44c58..21537fc 100644
--- a/libs/binder/libbinder_rpc_unstable.cpp
+++ b/libs/binder/libbinder_rpc_unstable.cpp
@@ -167,6 +167,10 @@
     server->setSupportedFileDescriptorTransportModes(modevec);
 }
 
+void ARpcServer_setMaxThreads(ARpcServer* handle, size_t threads) {
+    handleToStrongPointer<RpcServer>(handle)->setMaxThreads(threads);
+}
+
 void ARpcServer_start(ARpcServer* handle) {
     handleToStrongPointer<RpcServer>(handle)->start();
 }
diff --git a/libs/binder/liblog_stub/include/log/log.h b/libs/binder/liblog_stub/include/log/log.h
index 91c9632..dad0020 100644
--- a/libs/binder/liblog_stub/include/log/log.h
+++ b/libs/binder/liblog_stub/include/log/log.h
@@ -54,16 +54,16 @@
 #define IF_ALOGW() IF_ALOG(LOG_WARN, LOG_TAG)
 #define IF_ALOGE() IF_ALOG(LOG_ERROR, LOG_TAG)
 
-#define ALOG(priority, tag, fmt, ...)                                          \
-    do {                                                                       \
-        if (false)[[/*VERY*/ unlikely]] { /* ignore unused __VA_ARGS__ */      \
-            std::fprintf(stderr, fmt __VA_OPT__(, ) __VA_ARGS__);              \
-        }                                                                      \
-        IF_ALOG(priority, tag) {                                               \
-            __android_log_print(ANDROID_##priority, tag,                       \
-                                tag ": " fmt "\n" __VA_OPT__(, ) __VA_ARGS__); \
-        }                                                                      \
-        if constexpr (ANDROID_##priority == ANDROID_LOG_FATAL) std::abort();   \
+#define ALOG(priority, tag, fmt, ...)                                        \
+    do {                                                                     \
+        if (false)[[/*VERY*/ unlikely]] { /* ignore unused __VA_ARGS__ */    \
+            std::fprintf(stderr, fmt __VA_OPT__(, ) __VA_ARGS__);            \
+        }                                                                    \
+        IF_ALOG(priority, tag) {                                             \
+            __android_log_print(ANDROID_##priority, tag, "%s: " fmt "\n",    \
+                                (tag)__VA_OPT__(, ) __VA_ARGS__);            \
+        }                                                                    \
+        if constexpr (ANDROID_##priority == ANDROID_LOG_FATAL) std::abort(); \
     } while (false)
 #define ALOGV(...) ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
 #define ALOGD(...) ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)
diff --git a/libs/binder/ndk/Android.bp b/libs/binder/ndk/Android.bp
index 30dbddd..9a2d14a 100644
--- a/libs/binder/ndk/Android.bp
+++ b/libs/binder/ndk/Android.bp
@@ -50,8 +50,10 @@
     ],
 
     cflags: [
+        "-DBINDER_WITH_KERNEL_IPC",
         "-Wall",
         "-Wextra",
+        "-Wextra-semi",
         "-Werror",
     ],
 
@@ -145,6 +147,46 @@
     afdo: true,
 }
 
+cc_library {
+    name: "libbinder_ndk_on_trusty_mock",
+    defaults: [
+        "trusty_mock_defaults",
+    ],
+
+    export_include_dirs: [
+        "include_cpp",
+        "include_ndk",
+        "include_platform",
+    ],
+
+    srcs: [
+        "ibinder.cpp",
+        "libbinder.cpp",
+        "parcel.cpp",
+        "stability.cpp",
+        "status.cpp",
+    ],
+
+    shared_libs: [
+        "libbinder_on_trusty_mock",
+    ],
+
+    header_libs: [
+        "libbinder_trusty_ndk_headers",
+    ],
+    export_header_lib_headers: [
+        "libbinder_trusty_ndk_headers",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+    ],
+
+    visibility: ["//frameworks/native/libs/binder:__subpackages__"],
+}
+
 cc_library_headers {
     name: "libbinder_headers_platform_shared",
     export_include_dirs: ["include_cpp"],
diff --git a/libs/binder/ndk/ibinder.cpp b/libs/binder/ndk/ibinder.cpp
index e2ede3f..af280d3 100644
--- a/libs/binder/ndk/ibinder.cpp
+++ b/libs/binder/ndk/ibinder.cpp
@@ -24,6 +24,7 @@
 #include <private/android_filesystem_config.h>
 #endif
 
+#include "../BuildFlags.h"
 #include "ibinder_internal.h"
 #include "parcel_internal.h"
 #include "status_internal.h"
@@ -44,7 +45,9 @@
 
 static const void* kId = "ABBinder";
 static void* kValue = static_cast<void*>(new bool{true});
-void clean(const void* /*id*/, void* /*obj*/, void* /*cookie*/){/* do nothing */};
+void clean(const void* /*id*/, void* /*obj*/, void* /*cookie*/) {
+    /* do nothing */
+}
 
 static void attach(const sp<IBinder>& binder) {
     auto alreadyAttached = binder->attachObject(kId, kValue, nullptr /*cookie*/, clean);
@@ -69,7 +72,7 @@
     LOG_ALWAYS_FATAL_IF(id != kId, "%p %p %p", id, obj, cookie);
 
     delete static_cast<Value*>(obj);
-};
+}
 
 }  // namespace ABpBinderTag
 
@@ -211,6 +214,12 @@
         binder_status_t status = getClass()->onTransact(this, code, &in, &out);
         return PruneStatusT(status);
     } else if (code == SHELL_COMMAND_TRANSACTION && getClass()->handleShellCommand != nullptr) {
+        if constexpr (!android::kEnableKernelIpc) {
+            // Non-IPC builds do not have getCallingUid(),
+            // so we have no way of authenticating the caller
+            return STATUS_PERMISSION_DENIED;
+        }
+
         int in = data.readFileDescriptor();
         int out = data.readFileDescriptor();
         int err = data.readFileDescriptor();
@@ -469,6 +478,21 @@
 
     std::lock_guard<std::mutex> l(mDeathRecipientsMutex);
 
+    if (mOnUnlinked && cookie &&
+        std::find_if(mDeathRecipients.begin(), mDeathRecipients.end(),
+                     [&cookie](android::sp<TransferDeathRecipient> recipient) {
+                         return recipient->getCookie() == cookie;
+                     }) != mDeathRecipients.end()) {
+        ALOGE("Attempting to AIBinder_linkToDeath with the same cookie with an onUnlink callback. "
+              "This will cause the onUnlinked callback to be called multiple times with the same "
+              "cookie, which is usually not intended.");
+    }
+    if (!mOnUnlinked && cookie) {
+        ALOGW("AIBinder_linkToDeath is being called with a non-null cookie and no onUnlink "
+              "callback set. This might not be intended. AIBinder_DeathRecipient_setOnUnlinked "
+              "should be called first.");
+    }
+
     sp<TransferDeathRecipient> recipient =
             new TransferDeathRecipient(binder, cookie, this, mOnDied, mOnUnlinked);
 
@@ -602,6 +626,7 @@
     return recipient->unlinkToDeath(binder->getBinder(), cookie);
 }
 
+#ifdef BINDER_WITH_KERNEL_IPC
 uid_t AIBinder_getCallingUid() {
     return ::android::IPCThreadState::self()->getCallingUid();
 }
@@ -613,6 +638,7 @@
 bool AIBinder_isHandlingTransaction() {
     return ::android::IPCThreadState::self()->getServingStackPointer() != nullptr;
 }
+#endif
 
 void AIBinder_incStrong(AIBinder* binder) {
     if (binder == nullptr) {
@@ -830,9 +856,11 @@
     localBinder->setRequestingSid(requestingSid);
 }
 
+#ifdef BINDER_WITH_KERNEL_IPC
 const char* AIBinder_getCallingSid() {
     return ::android::IPCThreadState::self()->getCallingSid();
 }
+#endif
 
 void AIBinder_setMinSchedulerPolicy(AIBinder* binder, int policy, int priority) {
     binder->asABBinder()->setMinSchedulerPolicy(policy, priority);
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index ce63b82..471ab0c 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -697,6 +697,56 @@
     }
 }
 
+void LambdaOnUnlinkMultiple(void* cookie) {
+    auto funcs = static_cast<DeathRecipientCookie*>(cookie);
+    (*funcs->onUnlink)();
+};
+
+TEST(NdkBinder, DeathRecipientMultipleLinks) {
+    using namespace std::chrono_literals;
+
+    ndk::SpAIBinder binder;
+    sp<IFoo> foo = IFoo::getService(IFoo::kSomeInstanceName, binder.getR());
+    ASSERT_NE(nullptr, foo.get());
+    ASSERT_NE(nullptr, binder);
+
+    std::function<void(void)> onDeath = [&] {};
+
+    std::mutex unlinkMutex;
+    std::condition_variable unlinkCv;
+    bool unlinkReceived = false;
+    constexpr uint32_t kNumberOfLinksToDeath = 4;
+    uint32_t countdown = kNumberOfLinksToDeath;
+
+    std::function<void(void)> onUnlink = [&] {
+        std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
+        countdown--;
+        if (countdown == 0) {
+            unlinkReceived = true;
+            unlinkCv.notify_one();
+        }
+    };
+
+    DeathRecipientCookie* cookie = new DeathRecipientCookie{&onDeath, &onUnlink};
+
+    ndk::ScopedAIBinder_DeathRecipient recipient(AIBinder_DeathRecipient_new(LambdaOnDeath));
+    AIBinder_DeathRecipient_setOnUnlinked(recipient.get(), LambdaOnUnlinkMultiple);
+
+    for (int32_t i = 0; i < kNumberOfLinksToDeath; i++) {
+        EXPECT_EQ(STATUS_OK,
+                  AIBinder_linkToDeath(binder.get(), recipient.get(), static_cast<void*>(cookie)));
+    }
+
+    foo = nullptr;
+    binder = nullptr;
+
+    std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
+    EXPECT_TRUE(unlinkCv.wait_for(lockUnlink, 5s, [&] { return unlinkReceived; }))
+            << "countdown: " << countdown;
+    EXPECT_TRUE(unlinkReceived);
+    EXPECT_EQ(countdown, 0);
+}
+
 TEST(NdkBinder, RetrieveNonNdkService) {
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
diff --git a/libs/binder/rust/Android.bp b/libs/binder/rust/Android.bp
index 57a38dc..ef556d7 100644
--- a/libs/binder/rust/Android.bp
+++ b/libs/binder/rust/Android.bp
@@ -35,6 +35,21 @@
 }
 
 rust_library {
+    name: "libbinder_rs_on_trusty_mock",
+    crate_name: "binder",
+    srcs: ["src/lib.rs"],
+    cfgs: [
+        "trusty",
+    ],
+    rustlibs: [
+        "libbinder_ndk_sys_on_trusty_mock",
+        "libdowncast_rs",
+        "liblibc",
+    ],
+    vendor: true,
+}
+
+rust_library {
     name: "libbinder_tokio_rs",
     crate_name: "binder_tokio",
     srcs: ["binder_tokio/lib.rs"],
@@ -89,6 +104,26 @@
     visibility: [":__subpackages__"],
 }
 
+rust_library {
+    name: "libbinder_ndk_sys_on_trusty_mock",
+    crate_name: "binder_ndk_sys",
+    srcs: [
+        "sys/lib.rs",
+        ":libbinder_ndk_bindgen_on_trusty_mock",
+    ],
+    cfgs: [
+        "trusty",
+    ],
+    shared_libs: [
+        "libbinder_ndk_on_trusty_mock",
+    ],
+    vendor: true,
+    // Lints are checked separately for libbinder_ndk_sys.
+    // The Trusty mock copy pulls in extra headers that
+    // don't pass the lints for the bindgen output.
+    lints: "none",
+}
+
 rust_bindgen {
     name: "libbinder_ndk_bindgen",
     crate_name: "binder_ndk_bindgen",
@@ -125,6 +160,28 @@
     min_sdk_version: "Tiramisu",
 }
 
+rust_bindgen {
+    name: "libbinder_ndk_bindgen_on_trusty_mock",
+    crate_name: "binder_ndk_bindgen",
+    wrapper_src: "sys/BinderBindings.hpp",
+    source_stem: "bindings",
+    defaults: [
+        "trusty_mock_defaults",
+    ],
+
+    bindgen_flag_files: [
+        // Unfortunately the only way to specify the rust_non_exhaustive enum
+        // style for a type is to make it the default
+        // and then specify constified enums for the enums we don't want
+        // rustified
+        "libbinder_ndk_bindgen_flags.txt",
+    ],
+    shared_libs: [
+        "libbinder_ndk_on_trusty_mock",
+        "libc++",
+    ],
+}
+
 rust_test {
     name: "libbinder_rs-internal_test",
     crate_name: "binder",
diff --git a/libs/binder/rust/rpcbinder/Android.bp b/libs/binder/rust/rpcbinder/Android.bp
index 535ce01..2e46345 100644
--- a/libs/binder/rust/rpcbinder/Android.bp
+++ b/libs/binder/rust/rpcbinder/Android.bp
@@ -18,6 +18,7 @@
         "libbinder_ndk_sys",
         "libbinder_rpc_unstable_bindgen_sys",
         "libbinder_rs",
+        "libcfg_if",
         "libdowncast_rs",
         "libforeign_types",
         "liblibc",
diff --git a/libs/binder/rust/rpcbinder/src/lib.rs b/libs/binder/rust/rpcbinder/src/lib.rs
index 163f000..7e5c9dd 100644
--- a/libs/binder/rust/rpcbinder/src/lib.rs
+++ b/libs/binder/rust/rpcbinder/src/lib.rs
@@ -16,10 +16,10 @@
 
 //! API for RPC Binder services.
 
-#[cfg(not(target_os = "trusty"))]
 mod server;
 mod session;
 
+pub use server::RpcServer;
 #[cfg(not(target_os = "trusty"))]
-pub use server::{RpcServer, RpcServerRef};
+pub use server::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 6fda878..d6bdbd8 100644
--- a/libs/binder/rust/rpcbinder/src/server.rs
+++ b/libs/binder/rust/rpcbinder/src/server.rs
@@ -14,160 +14,12 @@
  * limitations under the License.
  */
 
-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::os::unix::io::{IntoRawFd, OwnedFd};
-
-foreign_type! {
-    type CType = binder_rpc_unstable_bindgen::ARpcServer;
-    fn drop = binder_rpc_unstable_bindgen::ARpcServer_free;
-
-    /// A type that represents a foreign instance of RpcServer.
-    #[derive(Debug)]
-    pub struct RpcServer;
-    /// A borrowed RpcServer.
-    pub struct RpcServerRef;
-}
-
-/// SAFETY: The opaque handle can be cloned freely.
-unsafe impl Send for RpcServer {}
-/// SAFETY: The underlying C++ RpcServer class is thread-safe.
-unsafe impl Sync for RpcServer {}
-
-impl RpcServer {
-    /// Creates a binder RPC server, serving the supplied binder service implementation on the given
-    /// vsock port. Only connections from the given CID are accepted.
-    ///
-    // Set `cid` to libc::VMADDR_CID_ANY to accept connections from any client.
-    // Set `cid` to libc::VMADDR_CID_LOCAL to only bind to the local vsock interface.
-    pub fn new_vsock(mut service: SpIBinder, cid: u32, port: u32) -> 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.
-        unsafe {
-            Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newVsock(
-                service, cid, port,
-            ))
-        }
-    }
-
-    /// Creates a binder RPC server, serving the supplied binder service implementation on the given
-    /// socket file descriptor. The socket should be bound to an address before calling this
-    /// function.
-    pub fn new_bound_socket(
-        mut service: SpIBinder,
-        socket_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 socket FD.
-        unsafe {
-            Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newBoundSocket(
-                service,
-                socket_fd.into_raw_fd(),
-            ))
-        }
-    }
-
-    /// 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(),
-            ))
-        }
-    }
-
-    /// Creates a binder RPC server, serving the supplied binder service implementation on the given
-    /// IP address and port.
-    pub fn new_inet(mut service: SpIBinder, address: &str, port: u32) -> Result<RpcServer, Error> {
-        let address = match CString::new(address) {
-            Ok(s) => s,
-            Err(e) => {
-                log::error!("Cannot convert {} to CString. Error: {:?}", address, e);
-                return Err(Error::from(ErrorKind::InvalidInput));
-            }
-        };
-        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.
-        unsafe {
-            Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newInet(
-                service,
-                address.as_ptr(),
-                port,
-            ))
-        }
-    }
-
-    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"));
-        }
-        // SAFETY: Our caller must pass us a valid or null pointer, and we've checked that it's not
-        // null.
-        Ok(unsafe { RpcServer::from_ptr(ptr) })
-    }
-}
-
-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) {
-        // SAFETY: RpcServerRef wraps a valid pointer to an ARpcServer.
-        unsafe { binder_rpc_unstable_bindgen::ARpcServer_start(self.as_ptr()) };
-    }
-
-    /// Joins the RpcServer thread. The call blocks until the server terminates.
-    /// This must be called from exactly one thread.
-    pub fn join(&self) {
-        // SAFETY: RpcServerRef wraps a valid pointer to an ARpcServer.
-        unsafe { binder_rpc_unstable_bindgen::ARpcServer_join(self.as_ptr()) };
-    }
-
-    /// Shuts down the running RpcServer. Can be called multiple times and from
-    /// multiple threads. Called automatically during drop().
-    pub fn shutdown(&self) -> Result<(), Error> {
-        // SAFETY: RpcServerRef wraps a valid pointer to an ARpcServer.
-        if unsafe { binder_rpc_unstable_bindgen::ARpcServer_shutdown(self.as_ptr()) } {
-            Ok(())
-        } else {
-            Err(Error::from(ErrorKind::UnexpectedEof))
-        }
+cfg_if::cfg_if! {
+    if #[cfg(target_os = "trusty")] {
+        mod trusty;
+        pub use trusty::*;
+    } else {
+        mod android;
+        pub use android::*;
     }
 }
diff --git a/libs/binder/rust/rpcbinder/src/server/android.rs b/libs/binder/rust/rpcbinder/src/server/android.rs
new file mode 100644
index 0000000..2ab3447
--- /dev/null
+++ b/libs/binder/rust/rpcbinder/src/server/android.rs
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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::os::unix::io::{IntoRawFd, OwnedFd};
+
+foreign_type! {
+    type CType = binder_rpc_unstable_bindgen::ARpcServer;
+    fn drop = binder_rpc_unstable_bindgen::ARpcServer_free;
+
+    /// A type that represents a foreign instance of RpcServer.
+    #[derive(Debug)]
+    pub struct RpcServer;
+    /// A borrowed RpcServer.
+    pub struct RpcServerRef;
+}
+
+/// SAFETY: The opaque handle can be cloned freely.
+unsafe impl Send for RpcServer {}
+/// SAFETY: The underlying C++ RpcServer class is thread-safe.
+unsafe impl Sync for RpcServer {}
+
+impl RpcServer {
+    /// Creates a binder RPC server, serving the supplied binder service implementation on the given
+    /// vsock port. Only connections from the given CID are accepted.
+    ///
+    // Set `cid` to libc::VMADDR_CID_ANY to accept connections from any client.
+    // Set `cid` to libc::VMADDR_CID_LOCAL to only bind to the local vsock interface.
+    pub fn new_vsock(mut service: SpIBinder, cid: u32, port: u32) -> 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.
+        unsafe {
+            Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newVsock(
+                service, cid, port,
+            ))
+        }
+    }
+
+    /// Creates a binder RPC server, serving the supplied binder service implementation on the given
+    /// socket file descriptor. The socket should be bound to an address before calling this
+    /// function.
+    pub fn new_bound_socket(
+        mut service: SpIBinder,
+        socket_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 socket FD.
+        unsafe {
+            Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newBoundSocket(
+                service,
+                socket_fd.into_raw_fd(),
+            ))
+        }
+    }
+
+    /// 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(),
+            ))
+        }
+    }
+
+    /// Creates a binder RPC server, serving the supplied binder service implementation on the given
+    /// IP address and port.
+    pub fn new_inet(mut service: SpIBinder, address: &str, port: u32) -> Result<RpcServer, Error> {
+        let address = match CString::new(address) {
+            Ok(s) => s,
+            Err(e) => {
+                log::error!("Cannot convert {} to CString. Error: {:?}", address, e);
+                return Err(Error::from(ErrorKind::InvalidInput));
+            }
+        };
+        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.
+        unsafe {
+            Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newInet(
+                service,
+                address.as_ptr(),
+                port,
+            ))
+        }
+    }
+
+    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"));
+        }
+        // SAFETY: Our caller must pass us a valid or null pointer, and we've checked that it's not
+        // null.
+        Ok(unsafe { RpcServer::from_ptr(ptr) })
+    }
+}
+
+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(),
+            )
+        }
+    }
+
+    /// Sets the max number of threads this Server uses for incoming client connections.
+    ///
+    /// This must be called before adding a client session. This corresponds
+    /// to the number of incoming connections to RpcSession objects in the
+    /// server, which will correspond to the number of outgoing connections
+    /// in client RpcSession objects. Specifically this is useful for handling
+    /// client-side callback connections.
+    ///
+    /// If this is not specified, this will be a single-threaded server.
+    pub fn set_max_threads(&self, count: usize) {
+        // SAFETY: RpcServerRef wraps a valid pointer to an ARpcServer.
+        unsafe { binder_rpc_unstable_bindgen::ARpcServer_setMaxThreads(self.as_ptr(), count) };
+    }
+
+    /// Starts a new background thread and calls join(). Returns immediately.
+    pub fn start(&self) {
+        // SAFETY: RpcServerRef wraps a valid pointer to an ARpcServer.
+        unsafe { binder_rpc_unstable_bindgen::ARpcServer_start(self.as_ptr()) };
+    }
+
+    /// Joins the RpcServer thread. The call blocks until the server terminates.
+    /// This must be called from exactly one thread.
+    pub fn join(&self) {
+        // SAFETY: RpcServerRef wraps a valid pointer to an ARpcServer.
+        unsafe { binder_rpc_unstable_bindgen::ARpcServer_join(self.as_ptr()) };
+    }
+
+    /// Shuts down the running RpcServer. Can be called multiple times and from
+    /// multiple threads. Called automatically during drop().
+    pub fn shutdown(&self) -> Result<(), Error> {
+        // SAFETY: RpcServerRef wraps a valid pointer to an ARpcServer.
+        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/server/trusty.rs b/libs/binder/rust/rpcbinder/src/server/trusty.rs
new file mode 100644
index 0000000..fe45dec
--- /dev/null
+++ b/libs/binder/rust/rpcbinder/src/server/trusty.rs
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use binder::{unstable_api::AsNative, SpIBinder};
+use libc::size_t;
+use std::ffi::{c_char, c_void};
+use std::ptr;
+use tipc::{ConnectResult, Handle, MessageResult, PortCfg, TipcError, UnbufferedService, Uuid};
+
+pub trait PerSessionCallback: Fn(Uuid) -> Option<SpIBinder> + Send + Sync + 'static {}
+impl<T> PerSessionCallback for T where T: Fn(Uuid) -> Option<SpIBinder> + Send + Sync + 'static {}
+
+pub struct RpcServer {
+    inner: *mut binder_rpc_server_bindgen::ARpcServerTrusty,
+}
+
+/// SAFETY: The opaque handle points to a heap allocation
+/// that should be process-wide and not tied to the current thread.
+unsafe impl Send for RpcServer {}
+/// SAFETY: The underlying C++ RpcServer class is thread-safe.
+unsafe impl Sync for RpcServer {}
+
+impl Drop for RpcServer {
+    fn drop(&mut self) {
+        // SAFETY: `ARpcServerTrusty_delete` is the correct destructor to call
+        // on pointers returned by `ARpcServerTrusty_new`.
+        unsafe {
+            binder_rpc_server_bindgen::ARpcServerTrusty_delete(self.inner);
+        }
+    }
+}
+
+impl RpcServer {
+    /// Allocates a new RpcServer object.
+    pub fn new(service: SpIBinder) -> RpcServer {
+        Self::new_per_session(move |_uuid| Some(service.clone()))
+    }
+
+    /// Allocates a new per-session RpcServer object.
+    ///
+    /// Per-session objects take a closure that gets called once
+    /// for every new connection. The closure gets the UUID of
+    /// the peer and can accept or reject that connection.
+    pub fn new_per_session<F: PerSessionCallback>(f: F) -> RpcServer {
+        // SAFETY: Takes ownership of the returned handle, which has correct refcount.
+        let inner = unsafe {
+            binder_rpc_server_bindgen::ARpcServerTrusty_newPerSession(
+                Some(per_session_callback_wrapper::<F>),
+                Box::into_raw(Box::new(f)).cast(),
+                Some(per_session_callback_deleter::<F>),
+            )
+        };
+        RpcServer { inner }
+    }
+}
+
+unsafe extern "C" fn per_session_callback_wrapper<F: PerSessionCallback>(
+    uuid_ptr: *const c_void,
+    len: size_t,
+    cb_ptr: *mut c_char,
+) -> *mut binder_rpc_server_bindgen::AIBinder {
+    // SAFETY: This callback should only get called while the RpcServer is alive.
+    let cb = unsafe { &mut *cb_ptr.cast::<F>() };
+
+    if len != std::mem::size_of::<Uuid>() {
+        return ptr::null_mut();
+    }
+
+    // SAFETY: On the previous lines we check that we got exactly the right amount of bytes.
+    let uuid = unsafe {
+        let mut uuid = std::mem::MaybeUninit::<Uuid>::uninit();
+        uuid.as_mut_ptr().copy_from(uuid_ptr.cast(), 1);
+        uuid.assume_init()
+    };
+
+    cb(uuid).map_or_else(ptr::null_mut, |b| {
+        // Prevent AIBinder_decStrong from being called before AIBinder_toPlatformBinder.
+        // The per-session callback in C++ is supposed to call AIBinder_decStrong on the
+        // pointer we return here.
+        std::mem::ManuallyDrop::new(b).as_native_mut().cast()
+    })
+}
+
+unsafe extern "C" fn per_session_callback_deleter<F: PerSessionCallback>(cb: *mut c_char) {
+    // SAFETY: shared_ptr calls this to delete the pointer we gave it.
+    // It should only get called once the last shared reference goes away.
+    unsafe {
+        drop(Box::<F>::from_raw(cb.cast()));
+    }
+}
+
+pub struct RpcServerConnection {
+    ctx: *mut c_void,
+}
+
+impl Drop for RpcServerConnection {
+    fn drop(&mut self) {
+        // We do not need to close handle_fd since we do not own it.
+        unsafe {
+            binder_rpc_server_bindgen::ARpcServerTrusty_handleChannelCleanup(self.ctx);
+        }
+    }
+}
+
+impl UnbufferedService for RpcServer {
+    type Connection = RpcServerConnection;
+
+    fn on_connect(
+        &self,
+        _port: &PortCfg,
+        handle: &Handle,
+        peer: &Uuid,
+    ) -> tipc::Result<ConnectResult<Self::Connection>> {
+        let mut conn = RpcServerConnection { ctx: std::ptr::null_mut() };
+        let rc = unsafe {
+            binder_rpc_server_bindgen::ARpcServerTrusty_handleConnect(
+                self.inner,
+                handle.as_raw_fd(),
+                peer.as_ptr().cast(),
+                &mut conn.ctx,
+            )
+        };
+        if rc < 0 {
+            Err(TipcError::from_uapi(rc.into()))
+        } else {
+            Ok(ConnectResult::Accept(conn))
+        }
+    }
+
+    fn on_message(
+        &self,
+        conn: &Self::Connection,
+        _handle: &Handle,
+        buffer: &mut [u8],
+    ) -> tipc::Result<MessageResult> {
+        assert!(buffer.is_empty());
+        let rc = unsafe { binder_rpc_server_bindgen::ARpcServerTrusty_handleMessage(conn.ctx) };
+        if rc < 0 {
+            Err(TipcError::from_uapi(rc.into()))
+        } else {
+            Ok(MessageResult::MaintainConnection)
+        }
+    }
+
+    fn on_disconnect(&self, conn: &Self::Connection) {
+        unsafe { binder_rpc_server_bindgen::ARpcServerTrusty_handleDisconnect(conn.ctx) };
+    }
+}
diff --git a/libs/binder/rust/src/lib.rs b/libs/binder/rust/src/lib.rs
index 16049f2..0f9c58c 100644
--- a/libs/binder/rust/src/lib.rs
+++ b/libs/binder/rust/src/lib.rs
@@ -100,7 +100,9 @@
 mod native;
 mod parcel;
 mod proxy;
-#[cfg(not(target_os = "trusty"))]
+#[cfg(not(trusty))]
+mod service;
+#[cfg(not(trusty))]
 mod state;
 
 use binder_ndk_sys as sys;
@@ -108,16 +110,15 @@
 pub use crate::binder_async::{BinderAsyncPool, BoxFuture};
 pub use binder::{BinderFeatures, FromIBinder, IBinder, Interface, Strong, Weak};
 pub use error::{ExceptionCode, IntoBinderResult, Status, StatusCode};
-pub use native::{
-    add_service, force_lazy_services_persist, is_handling_transaction, register_lazy_service,
-    LazyServiceGuard,
-};
 pub use parcel::{ParcelFileDescriptor, Parcelable, ParcelableHolder};
-pub use proxy::{
-    get_declared_instances, get_interface, get_service, is_declared, wait_for_interface,
-    wait_for_service, DeathRecipient, SpIBinder, WpIBinder,
+pub use proxy::{DeathRecipient, SpIBinder, WpIBinder};
+#[cfg(not(trusty))]
+pub use service::{
+    add_service, force_lazy_services_persist, get_declared_instances, get_interface, get_service,
+    is_declared, is_handling_transaction, register_lazy_service, wait_for_interface,
+    wait_for_service, LazyServiceGuard,
 };
-#[cfg(not(target_os = "trusty"))]
+#[cfg(not(trusty))]
 pub use state::{ProcessState, ThreadState};
 
 /// Binder result containing a [`Status`] on error.
diff --git a/libs/binder/rust/src/native.rs b/libs/binder/rust/src/native.rs
index 8ae010e..c87cc94 100644
--- a/libs/binder/rust/src/native.rs
+++ b/libs/binder/rust/src/native.rs
@@ -23,12 +23,11 @@
 use crate::sys;
 
 use std::convert::TryFrom;
-use std::ffi::{c_void, CStr, CString};
+use std::ffi::{c_void, CStr};
 use std::io::Write;
 use std::mem::ManuallyDrop;
 use std::ops::Deref;
 use std::os::raw::c_char;
-use std::sync::Mutex;
 
 /// Rust wrapper around Binder remotable objects.
 ///
@@ -328,7 +327,7 @@
     /// contains a `T` pointer in its user data. fd should be a non-owned file
     /// descriptor, and args must be an array of null-terminated string
     /// pointers with length num_args.
-    #[cfg(not(target_os = "trusty"))]
+    #[cfg(not(trusty))]
     unsafe extern "C" fn on_dump(
         binder: *mut sys::AIBinder,
         fd: i32,
@@ -375,7 +374,7 @@
     }
 
     /// Called to handle the `dump` transaction.
-    #[cfg(target_os = "trusty")]
+    #[cfg(trusty)]
     unsafe extern "C" fn on_dump(
         _binder: *mut sys::AIBinder,
         _fd: i32,
@@ -462,110 +461,6 @@
     }
 }
 
-/// Register a new service with the default service manager.
-///
-/// Registers the given binder object with the given identifier. If successful,
-/// this service can then be retrieved using that identifier.
-///
-/// This function will panic if the identifier contains a 0 byte (NUL).
-pub fn add_service(identifier: &str, mut binder: SpIBinder) -> Result<()> {
-    let instance = CString::new(identifier).unwrap();
-    let status =
-    // Safety: `AServiceManager_addService` expects valid `AIBinder` and C
-    // string pointers. Caller retains ownership of both pointers.
-    // `AServiceManager_addService` creates a new strong reference and copies
-    // the string, so both pointers need only be valid until the call returns.
-        unsafe { sys::AServiceManager_addService(binder.as_native_mut(), instance.as_ptr()) };
-    status_result(status)
-}
-
-/// Register a dynamic service via the LazyServiceRegistrar.
-///
-/// Registers the given binder object with the given identifier. If successful,
-/// this service can then be retrieved using that identifier. The service process
-/// will be shut down once all registered services are no longer in use.
-///
-/// If any service in the process is registered as lazy, all should be, otherwise
-/// the process may be shut down while a service is in use.
-///
-/// This function will panic if the identifier contains a 0 byte (NUL).
-pub fn register_lazy_service(identifier: &str, mut binder: SpIBinder) -> Result<()> {
-    let instance = CString::new(identifier).unwrap();
-    // Safety: `AServiceManager_registerLazyService` expects valid `AIBinder` and C
-    // string pointers. Caller retains ownership of both
-    // pointers. `AServiceManager_registerLazyService` creates a new strong reference
-    // and copies the string, so both pointers need only be valid until the
-    // call returns.
-    let status = unsafe {
-        sys::AServiceManager_registerLazyService(binder.as_native_mut(), instance.as_ptr())
-    };
-    status_result(status)
-}
-
-/// Prevent a process which registers lazy services from being shut down even when none
-/// of the services is in use.
-///
-/// If persist is true then shut down will be blocked until this function is called again with
-/// persist false. If this is to be the initial state, call this function before calling
-/// register_lazy_service.
-///
-/// Consider using [`LazyServiceGuard`] rather than calling this directly.
-pub fn force_lazy_services_persist(persist: bool) {
-    // Safety: No borrowing or transfer of ownership occurs here.
-    unsafe { sys::AServiceManager_forceLazyServicesPersist(persist) }
-}
-
-/// An RAII object to ensure a process which registers lazy services is not killed. During the
-/// lifetime of any of these objects the service manager will not not kill the process even if none
-/// of its lazy services are in use.
-#[must_use]
-#[derive(Debug)]
-pub struct LazyServiceGuard {
-    // Prevent construction outside this module.
-    _private: (),
-}
-
-// Count of how many LazyServiceGuard objects are in existence.
-static GUARD_COUNT: Mutex<u64> = Mutex::new(0);
-
-impl LazyServiceGuard {
-    /// Create a new LazyServiceGuard to prevent the service manager prematurely killing this
-    /// process.
-    pub fn new() -> Self {
-        let mut count = GUARD_COUNT.lock().unwrap();
-        *count += 1;
-        if *count == 1 {
-            // It's important that we make this call with the mutex held, to make sure
-            // that multiple calls (e.g. if the count goes 1 -> 0 -> 1) are correctly
-            // sequenced. (That also means we can't just use an AtomicU64.)
-            force_lazy_services_persist(true);
-        }
-        Self { _private: () }
-    }
-}
-
-impl Drop for LazyServiceGuard {
-    fn drop(&mut self) {
-        let mut count = GUARD_COUNT.lock().unwrap();
-        *count -= 1;
-        if *count == 0 {
-            force_lazy_services_persist(false);
-        }
-    }
-}
-
-impl Clone for LazyServiceGuard {
-    fn clone(&self) -> Self {
-        Self::new()
-    }
-}
-
-impl Default for LazyServiceGuard {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
 /// Tests often create a base BBinder instance; so allowing the unit
 /// type to be remotable translates nicely to Binder::new(()).
 impl Remotable for () {
@@ -590,10 +485,3 @@
 }
 
 impl Interface for () {}
-
-/// Determine whether the current thread is currently executing an incoming
-/// transaction.
-pub fn is_handling_transaction() -> bool {
-    // Safety: This method is always safe to call.
-    unsafe { sys::AIBinder_isHandlingTransaction() }
-}
diff --git a/libs/binder/rust/src/proxy.rs b/libs/binder/rust/src/proxy.rs
index 7434e9d..340014a 100644
--- a/libs/binder/rust/src/proxy.rs
+++ b/libs/binder/rust/src/proxy.rs
@@ -29,11 +29,10 @@
 
 use std::cmp::Ordering;
 use std::convert::TryInto;
-use std::ffi::{c_void, CStr, CString};
+use std::ffi::{c_void, CString};
 use std::fmt;
 use std::mem;
 use std::os::fd::AsRawFd;
-use std::os::raw::c_char;
 use std::ptr;
 use std::sync::Arc;
 
@@ -129,14 +128,6 @@
     }
 }
 
-fn interface_cast<T: FromIBinder + ?Sized>(service: Option<SpIBinder>) -> Result<Strong<T>> {
-    if let Some(service) = service {
-        FromIBinder::try_from(service)
-    } else {
-        Err(StatusCode::NAME_NOT_FOUND)
-    }
-}
-
 pub mod unstable_api {
     use super::{sys, SpIBinder};
 
@@ -739,93 +730,6 @@
     }
 }
 
-/// Retrieve an existing service, blocking for a few seconds if it doesn't yet
-/// exist.
-pub fn get_service(name: &str) -> Option<SpIBinder> {
-    let name = CString::new(name).ok()?;
-    // Safety: `AServiceManager_getService` returns either a null pointer or a
-    // valid pointer to an owned `AIBinder`. Either of these values is safe to
-    // pass to `SpIBinder::from_raw`.
-    unsafe { SpIBinder::from_raw(sys::AServiceManager_getService(name.as_ptr())) }
-}
-
-/// Retrieve an existing service, or start it if it is configured as a dynamic
-/// service and isn't yet started.
-pub fn wait_for_service(name: &str) -> Option<SpIBinder> {
-    let name = CString::new(name).ok()?;
-    // Safety: `AServiceManager_waitforService` returns either a null pointer or
-    // a valid pointer to an owned `AIBinder`. Either of these values is safe to
-    // pass to `SpIBinder::from_raw`.
-    unsafe { SpIBinder::from_raw(sys::AServiceManager_waitForService(name.as_ptr())) }
-}
-
-/// Retrieve an existing service for a particular interface, blocking for a few
-/// seconds if it doesn't yet exist.
-pub fn get_interface<T: FromIBinder + ?Sized>(name: &str) -> Result<Strong<T>> {
-    interface_cast(get_service(name))
-}
-
-/// Retrieve an existing service for a particular interface, or start it if it
-/// is configured as a dynamic service and isn't yet started.
-pub fn wait_for_interface<T: FromIBinder + ?Sized>(name: &str) -> Result<Strong<T>> {
-    interface_cast(wait_for_service(name))
-}
-
-/// Check if a service is declared (e.g. in a VINTF manifest)
-pub fn is_declared(interface: &str) -> Result<bool> {
-    let interface = CString::new(interface).or(Err(StatusCode::UNEXPECTED_NULL))?;
-
-    // Safety: `interface` is a valid null-terminated C-style string and is only
-    // borrowed for the lifetime of the call. The `interface` local outlives
-    // this call as it lives for the function scope.
-    unsafe { Ok(sys::AServiceManager_isDeclared(interface.as_ptr())) }
-}
-
-/// Retrieve all declared instances for a particular interface
-///
-/// For instance, if 'android.foo.IFoo/foo' is declared, and 'android.foo.IFoo'
-/// is passed here, then ["foo"] would be returned.
-pub fn get_declared_instances(interface: &str) -> Result<Vec<String>> {
-    unsafe extern "C" fn callback(instance: *const c_char, opaque: *mut c_void) {
-        // Safety: opaque was a mutable pointer created below from a Vec of
-        // CString, and outlives this callback. The null handling here is just
-        // to avoid the possibility of unwinding across C code if this crate is
-        // ever compiled with panic=unwind.
-        if let Some(instances) = unsafe { opaque.cast::<Vec<CString>>().as_mut() } {
-            // Safety: instance is a valid null-terminated C string with a
-            // lifetime at least as long as this function, and we immediately
-            // copy it into an owned CString.
-            unsafe {
-                instances.push(CStr::from_ptr(instance).to_owned());
-            }
-        } else {
-            eprintln!("Opaque pointer was null in get_declared_instances callback!");
-        }
-    }
-
-    let interface = CString::new(interface).or(Err(StatusCode::UNEXPECTED_NULL))?;
-    let mut instances: Vec<CString> = vec![];
-    // Safety: `interface` and `instances` are borrowed for the length of this
-    // call and both outlive the call. `interface` is guaranteed to be a valid
-    // null-terminated C-style string.
-    unsafe {
-        sys::AServiceManager_forEachDeclaredInstance(
-            interface.as_ptr(),
-            &mut instances as *mut _ as *mut c_void,
-            Some(callback),
-        );
-    }
-
-    instances
-        .into_iter()
-        .map(CString::into_string)
-        .collect::<std::result::Result<Vec<String>, _>>()
-        .map_err(|e| {
-            eprintln!("An interface instance name was not a valid UTF-8 string: {}", e);
-            StatusCode::BAD_VALUE
-        })
-}
-
 /// Safety: `SpIBinder` guarantees that `binder` always contains a valid pointer
 /// to an `AIBinder`, so we can trivially extract this pointer here.
 unsafe impl AsNative<sys::AIBinder> for SpIBinder {
diff --git a/libs/binder/rust/src/service.rs b/libs/binder/rust/src/service.rs
new file mode 100644
index 0000000..3ca3b54
--- /dev/null
+++ b/libs/binder/rust/src/service.rs
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use crate::binder::{AsNative, FromIBinder, Strong};
+use crate::error::{status_result, Result, StatusCode};
+use crate::proxy::SpIBinder;
+use crate::sys;
+
+use std::ffi::{c_void, CStr, CString};
+use std::os::raw::c_char;
+use std::sync::Mutex;
+
+/// Register a new service with the default service manager.
+///
+/// Registers the given binder object with the given identifier. If successful,
+/// this service can then be retrieved using that identifier.
+///
+/// This function will panic if the identifier contains a 0 byte (NUL).
+pub fn add_service(identifier: &str, mut binder: SpIBinder) -> Result<()> {
+    let instance = CString::new(identifier).unwrap();
+    let status =
+    // Safety: `AServiceManager_addService` expects valid `AIBinder` and C
+    // string pointers. Caller retains ownership of both pointers.
+    // `AServiceManager_addService` creates a new strong reference and copies
+    // the string, so both pointers need only be valid until the call returns.
+        unsafe { sys::AServiceManager_addService(binder.as_native_mut(), instance.as_ptr()) };
+    status_result(status)
+}
+
+/// Register a dynamic service via the LazyServiceRegistrar.
+///
+/// Registers the given binder object with the given identifier. If successful,
+/// this service can then be retrieved using that identifier. The service process
+/// will be shut down once all registered services are no longer in use.
+///
+/// If any service in the process is registered as lazy, all should be, otherwise
+/// the process may be shut down while a service is in use.
+///
+/// This function will panic if the identifier contains a 0 byte (NUL).
+pub fn register_lazy_service(identifier: &str, mut binder: SpIBinder) -> Result<()> {
+    let instance = CString::new(identifier).unwrap();
+    // Safety: `AServiceManager_registerLazyService` expects valid `AIBinder` and C
+    // string pointers. Caller retains ownership of both
+    // pointers. `AServiceManager_registerLazyService` creates a new strong reference
+    // and copies the string, so both pointers need only be valid until the
+    // call returns.
+    let status = unsafe {
+        sys::AServiceManager_registerLazyService(binder.as_native_mut(), instance.as_ptr())
+    };
+    status_result(status)
+}
+
+/// Prevent a process which registers lazy services from being shut down even when none
+/// of the services is in use.
+///
+/// If persist is true then shut down will be blocked until this function is called again with
+/// persist false. If this is to be the initial state, call this function before calling
+/// register_lazy_service.
+///
+/// Consider using [`LazyServiceGuard`] rather than calling this directly.
+pub fn force_lazy_services_persist(persist: bool) {
+    // Safety: No borrowing or transfer of ownership occurs here.
+    unsafe { sys::AServiceManager_forceLazyServicesPersist(persist) }
+}
+
+/// An RAII object to ensure a process which registers lazy services is not killed. During the
+/// lifetime of any of these objects the service manager will not kill the process even if none
+/// of its lazy services are in use.
+#[must_use]
+#[derive(Debug)]
+pub struct LazyServiceGuard {
+    // Prevent construction outside this module.
+    _private: (),
+}
+
+// Count of how many LazyServiceGuard objects are in existence.
+static GUARD_COUNT: Mutex<u64> = Mutex::new(0);
+
+impl LazyServiceGuard {
+    /// Create a new LazyServiceGuard to prevent the service manager prematurely killing this
+    /// process.
+    pub fn new() -> Self {
+        let mut count = GUARD_COUNT.lock().unwrap();
+        *count += 1;
+        if *count == 1 {
+            // It's important that we make this call with the mutex held, to make sure
+            // that multiple calls (e.g. if the count goes 1 -> 0 -> 1) are correctly
+            // sequenced. (That also means we can't just use an AtomicU64.)
+            force_lazy_services_persist(true);
+        }
+        Self { _private: () }
+    }
+}
+
+impl Drop for LazyServiceGuard {
+    fn drop(&mut self) {
+        let mut count = GUARD_COUNT.lock().unwrap();
+        *count -= 1;
+        if *count == 0 {
+            force_lazy_services_persist(false);
+        }
+    }
+}
+
+impl Clone for LazyServiceGuard {
+    fn clone(&self) -> Self {
+        Self::new()
+    }
+}
+
+impl Default for LazyServiceGuard {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+/// Determine whether the current thread is currently executing an incoming
+/// transaction.
+pub fn is_handling_transaction() -> bool {
+    // Safety: This method is always safe to call.
+    unsafe { sys::AIBinder_isHandlingTransaction() }
+}
+
+fn interface_cast<T: FromIBinder + ?Sized>(service: Option<SpIBinder>) -> Result<Strong<T>> {
+    if let Some(service) = service {
+        FromIBinder::try_from(service)
+    } else {
+        Err(StatusCode::NAME_NOT_FOUND)
+    }
+}
+
+/// Retrieve an existing service, blocking for a few seconds if it doesn't yet
+/// exist.
+pub fn get_service(name: &str) -> Option<SpIBinder> {
+    let name = CString::new(name).ok()?;
+    // Safety: `AServiceManager_getService` returns either a null pointer or a
+    // valid pointer to an owned `AIBinder`. Either of these values is safe to
+    // pass to `SpIBinder::from_raw`.
+    unsafe { SpIBinder::from_raw(sys::AServiceManager_getService(name.as_ptr())) }
+}
+
+/// Retrieve an existing service, or start it if it is configured as a dynamic
+/// service and isn't yet started.
+pub fn wait_for_service(name: &str) -> Option<SpIBinder> {
+    let name = CString::new(name).ok()?;
+    // Safety: `AServiceManager_waitforService` returns either a null pointer or
+    // a valid pointer to an owned `AIBinder`. Either of these values is safe to
+    // pass to `SpIBinder::from_raw`.
+    unsafe { SpIBinder::from_raw(sys::AServiceManager_waitForService(name.as_ptr())) }
+}
+
+/// Retrieve an existing service for a particular interface, blocking for a few
+/// seconds if it doesn't yet exist.
+pub fn get_interface<T: FromIBinder + ?Sized>(name: &str) -> Result<Strong<T>> {
+    interface_cast(get_service(name))
+}
+
+/// Retrieve an existing service for a particular interface, or start it if it
+/// is configured as a dynamic service and isn't yet started.
+pub fn wait_for_interface<T: FromIBinder + ?Sized>(name: &str) -> Result<Strong<T>> {
+    interface_cast(wait_for_service(name))
+}
+
+/// Check if a service is declared (e.g. in a VINTF manifest)
+pub fn is_declared(interface: &str) -> Result<bool> {
+    let interface = CString::new(interface).or(Err(StatusCode::UNEXPECTED_NULL))?;
+
+    // Safety: `interface` is a valid null-terminated C-style string and is only
+    // borrowed for the lifetime of the call. The `interface` local outlives
+    // this call as it lives for the function scope.
+    unsafe { Ok(sys::AServiceManager_isDeclared(interface.as_ptr())) }
+}
+
+/// Retrieve all declared instances for a particular interface
+///
+/// For instance, if 'android.foo.IFoo/foo' is declared, and 'android.foo.IFoo'
+/// is passed here, then ["foo"] would be returned.
+pub fn get_declared_instances(interface: &str) -> Result<Vec<String>> {
+    unsafe extern "C" fn callback(instance: *const c_char, opaque: *mut c_void) {
+        // Safety: opaque was a mutable pointer created below from a Vec of
+        // CString, and outlives this callback. The null handling here is just
+        // to avoid the possibility of unwinding across C code if this crate is
+        // ever compiled with panic=unwind.
+        if let Some(instances) = unsafe { opaque.cast::<Vec<CString>>().as_mut() } {
+            // Safety: instance is a valid null-terminated C string with a
+            // lifetime at least as long as this function, and we immediately
+            // copy it into an owned CString.
+            unsafe {
+                instances.push(CStr::from_ptr(instance).to_owned());
+            }
+        } else {
+            eprintln!("Opaque pointer was null in get_declared_instances callback!");
+        }
+    }
+
+    let interface = CString::new(interface).or(Err(StatusCode::UNEXPECTED_NULL))?;
+    let mut instances: Vec<CString> = vec![];
+    // Safety: `interface` and `instances` are borrowed for the length of this
+    // call and both outlive the call. `interface` is guaranteed to be a valid
+    // null-terminated C-style string.
+    unsafe {
+        sys::AServiceManager_forEachDeclaredInstance(
+            interface.as_ptr(),
+            &mut instances as *mut _ as *mut c_void,
+            Some(callback),
+        );
+    }
+
+    instances
+        .into_iter()
+        .map(CString::into_string)
+        .collect::<std::result::Result<Vec<String>, _>>()
+        .map_err(|e| {
+            eprintln!("An interface instance name was not a valid UTF-8 string: {}", e);
+            StatusCode::BAD_VALUE
+        })
+}
diff --git a/libs/binder/rust/sys/lib.rs b/libs/binder/rust/sys/lib.rs
index c5c847b..5352473 100644
--- a/libs/binder/rust/sys/lib.rs
+++ b/libs/binder/rust/sys/lib.rs
@@ -25,7 +25,9 @@
 }
 
 // Trusty puts the full path to the auto-generated file in BINDGEN_INC_FILE
-// and builds it with warnings-as-errors, so we need to use #[allow(bad_style)]
+// and builds it with warnings-as-errors, so we need to use #[allow(bad_style)].
+// We need to use cfg(target_os) instead of cfg(trusty) here because of
+// the difference between the two build systems, which we cannot mock.
 #[cfg(target_os = "trusty")]
 #[allow(bad_style)]
 mod bindings {
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index 2f0987f..6800a8d 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -200,9 +200,11 @@
     defaults: [
         "binder_test_defaults",
     ],
+    header_libs: [
+        "libbinder_headers_base",
+    ],
     shared_libs: [
         "libbase",
-        "liblog",
     ],
     srcs: [
         "FileUtils.cpp",
@@ -435,6 +437,8 @@
     // Add the Trusty mock library as a fake dependency so it gets built
     required: [
         "libbinder_on_trusty_mock",
+        "libbinder_ndk_on_trusty_mock",
+        "libbinder_rs_on_trusty_mock",
         "binderRpcTestService_on_trusty_mock",
         "binderRpcTest_on_trusty_mock",
     ],
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index 2cea14f..1f61f18 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -506,10 +506,11 @@
 
     // Pass test on devices where BINDER_FREEZE ioctl is not supported
     int ret = IPCThreadState::self()->freeze(pid, false, 0);
-    if (ret != 0) {
+    if (ret == -EINVAL) {
         GTEST_SKIP();
         return;
     }
+    EXPECT_EQ(NO_ERROR, ret);
 
     EXPECT_EQ(-EAGAIN, IPCThreadState::self()->freeze(pid, true, 0));
 
diff --git a/libs/binder/tests/binderThroughputTest.cpp b/libs/binder/tests/binderThroughputTest.cpp
index 10912c7..f912348 100644
--- a/libs/binder/tests/binderThroughputTest.cpp
+++ b/libs/binder/tests/binderThroughputTest.cpp
@@ -7,9 +7,10 @@
 #include <cstdlib>
 #include <cstdio>
 
+#include <fstream>
 #include <iostream>
-#include <vector>
 #include <tuple>
+#include <vector>
 
 #include <unistd.h>
 #include <sys/wait.h>
@@ -63,6 +64,18 @@
     uint64_t worst() {
         return *max_element(data.begin(), data.end());
     }
+    void dump_to_file(string filename) {
+        ofstream output;
+        output.open(filename);
+        if (!output.is_open()) {
+            cerr << "Failed to open '" << filename << "'." << endl;
+            exit(EXIT_FAILURE);
+        }
+        for (uint64_t value : data) {
+            output << value << "\n";
+        }
+        output.close();
+    }
     void dump() {
         if (data.size() == 0) {
             // This avoids index-out-of-bounds below.
@@ -293,12 +306,8 @@
     }
 }
 
-void run_main(int iterations,
-              int workers,
-              int payload_size,
-              int cs_pair,
-              bool training_round=false)
-{
+void run_main(int iterations, int workers, int payload_size, int cs_pair,
+              bool training_round = false, bool dump_to_file = false, string dump_filename = "") {
     vector<Pipe> pipes;
     // Create all the workers and wait for them to spawn.
     for (int i = 0; i < workers; i++) {
@@ -349,6 +358,9 @@
         warn_latency = 2 * tot_results.worst();
         cout << "Max latency during training: " << tot_results.worst() / 1.0E6 << "ms" << endl;
     } else {
+        if (dump_to_file) {
+            tot_results.dump_to_file(dump_filename);
+        }
         tot_results.dump();
     }
 }
@@ -361,6 +373,8 @@
     bool cs_pair = false;
     bool training_round = false;
     int max_time_us;
+    bool dump_to_file = false;
+    string dump_filename;
 
     // Parse arguments.
     for (int i = 1; i < argc; i++) {
@@ -372,6 +386,7 @@
             cout << "\t-s N    : Specify payload size." << endl;
             cout << "\t-t      : Run training round." << endl;
             cout << "\t-w N    : Specify total number of workers." << endl;
+            cout << "\t-d FILE : Dump raw data to file." << endl;
             return 0;
         }
         if (string(argv[i]) == "-w") {
@@ -430,14 +445,24 @@
             i++;
             continue;
         }
+        if (string(argv[i]) == "-d") {
+            if (i + 1 == argc) {
+                cout << "-d requires an argument\n" << endl;
+                exit(EXIT_FAILURE);
+            }
+            dump_to_file = true;
+            dump_filename = argv[i + 1];
+            i++;
+            continue;
+        }
     }
 
     if (training_round) {
         cout << "Start training round" << endl;
-        run_main(iterations, workers, payload_size, cs_pair, training_round=true);
+        run_main(iterations, workers, payload_size, cs_pair, true);
         cout << "Completed training round" << endl << endl;
     }
 
-    run_main(iterations, workers, payload_size, cs_pair);
+    run_main(iterations, workers, payload_size, cs_pair, false, dump_to_file, dump_filename);
     return 0;
 }
diff --git a/libs/binder/tests/unit_fuzzers/Android.bp b/libs/binder/tests/unit_fuzzers/Android.bp
index a881582..6871cca 100644
--- a/libs/binder/tests/unit_fuzzers/Android.bp
+++ b/libs/binder/tests/unit_fuzzers/Android.bp
@@ -52,6 +52,18 @@
             enabled: false,
         },
     },
+    fuzz_config: {
+        cc: [
+            "smoreland@google.com",
+            "waghpawan@google.com",
+        ],
+        componentid: 32456,
+        description: "The fuzzer targets the APIs of libbinder",
+        vector: "local_no_privileges_required",
+        service_privilege: "privileged",
+        users: "multi_user",
+        fuzzed_code_usage: "shipped",
+    },
 }
 
 cc_fuzz {
diff --git a/libs/binder/trusty/RpcServerTrusty.cpp b/libs/binder/trusty/RpcServerTrusty.cpp
index 1f857a0..17919c2 100644
--- a/libs/binder/trusty/RpcServerTrusty.cpp
+++ b/libs/binder/trusty/RpcServerTrusty.cpp
@@ -60,7 +60,7 @@
 
 RpcServerTrusty::RpcServerTrusty(std::unique_ptr<RpcTransportCtx> ctx, std::string&& portName,
                                  std::shared_ptr<const PortAcl>&& portAcl, size_t msgMaxSize)
-      : mRpcServer(sp<RpcServer>::make(std::move(ctx))),
+      : mRpcServer(makeRpcServer(std::move(ctx))),
         mPortName(std::move(portName)),
         mPortAcl(std::move(portAcl)) {
     mTipcPort.name = mPortName.c_str();
@@ -68,10 +68,6 @@
     mTipcPort.msg_queue_len = 6; // Three each way
     mTipcPort.priv = this;
 
-    // TODO(b/266741352): follow-up to prevent needing this in the future
-    // Trusty needs to be set to the latest stable version that is in prebuilts there.
-    LOG_ALWAYS_FATAL_IF(!mRpcServer->setProtocolVersion(0));
-
     if (mPortAcl) {
         // Initialize the array of pointers to uuids.
         // The pointers in mUuidPtrs should stay valid across moves of
@@ -101,8 +97,13 @@
 int RpcServerTrusty::handleConnect(const tipc_port* port, handle_t chan, const uuid* peer,
                                    void** ctx_p) {
     auto* server = reinterpret_cast<RpcServerTrusty*>(const_cast<void*>(port->priv));
-    server->mRpcServer->mShutdownTrigger = FdTrigger::make();
-    server->mRpcServer->mConnectingThreads[rpc_this_thread::get_id()] = RpcMaybeThread();
+    return handleConnectInternal(server->mRpcServer.get(), chan, peer, ctx_p);
+}
+
+int RpcServerTrusty::handleConnectInternal(RpcServer* rpcServer, handle_t chan, const uuid* peer,
+                                           void** ctx_p) {
+    rpcServer->mShutdownTrigger = FdTrigger::make();
+    rpcServer->mConnectingThreads[rpc_this_thread::get_id()] = RpcMaybeThread();
 
     int rc = NO_ERROR;
     auto joinFn = [&](sp<RpcSession>&& session, RpcSession::PreJoinSetupResult&& result) {
@@ -138,13 +139,17 @@
     std::array<uint8_t, RpcServer::kRpcAddressSize> addr;
     constexpr size_t addrLen = sizeof(*peer);
     memcpy(addr.data(), peer, addrLen);
-    RpcServer::establishConnection(sp(server->mRpcServer), std::move(transportFd), addr, addrLen,
-                                   joinFn);
+    RpcServer::establishConnection(sp<RpcServer>::fromExisting(rpcServer), std::move(transportFd),
+                                   addr, addrLen, joinFn);
 
     return rc;
 }
 
 int RpcServerTrusty::handleMessage(const tipc_port* /*port*/, handle_t /*chan*/, void* ctx) {
+    return handleMessageInternal(ctx);
+}
+
+int RpcServerTrusty::handleMessageInternal(void* ctx) {
     auto* channelContext = reinterpret_cast<ChannelContext*>(ctx);
     LOG_ALWAYS_FATAL_IF(channelContext == nullptr,
                         "bad state: message received on uninitialized channel");
@@ -162,6 +167,10 @@
 }
 
 void RpcServerTrusty::handleDisconnect(const tipc_port* /*port*/, handle_t /*chan*/, void* ctx) {
+    return handleDisconnectInternal(ctx);
+}
+
+void RpcServerTrusty::handleDisconnectInternal(void* ctx) {
     auto* channelContext = reinterpret_cast<ChannelContext*>(ctx);
     if (channelContext == nullptr) {
         // Connections marked "incoming" (outgoing from the server's side)
diff --git a/libs/binder/trusty/build-config-usertests b/libs/binder/trusty/build-config-usertests
index d0a1fbc..72e5ff9 100644
--- a/libs/binder/trusty/build-config-usertests
+++ b/libs/binder/trusty/build-config-usertests
@@ -16,4 +16,5 @@
 
 [
     porttest("com.android.trusty.binderRpcTest"),
+    porttest("com.android.trusty.rust.binder_rpc_test.test"),
 ]
diff --git a/libs/binder/trusty/include/binder/ARpcServerTrusty.h b/libs/binder/trusty/include/binder/ARpcServerTrusty.h
new file mode 100644
index 0000000..c82268b
--- /dev/null
+++ b/libs/binder/trusty/include/binder/ARpcServerTrusty.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <lib/tipc/tipc_srv.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+struct AIBinder;
+struct ARpcServerTrusty;
+
+struct ARpcServerTrusty* ARpcServerTrusty_newPerSession(struct AIBinder* (*)(const void*, size_t,
+                                                                             char*),
+                                                        char*, void (*)(char*));
+void ARpcServerTrusty_delete(struct ARpcServerTrusty*);
+int ARpcServerTrusty_handleConnect(struct ARpcServerTrusty*, handle_t, const struct uuid*, void**);
+int ARpcServerTrusty_handleMessage(void*);
+void ARpcServerTrusty_handleDisconnect(void*);
+void ARpcServerTrusty_handleChannelCleanup(void*);
+
+#if defined(__cplusplus)
+}
+#endif
diff --git a/libs/binder/trusty/include/binder/RpcServerTrusty.h b/libs/binder/trusty/include/binder/RpcServerTrusty.h
index f35d6c2..fe44ea5 100644
--- a/libs/binder/trusty/include/binder/RpcServerTrusty.h
+++ b/libs/binder/trusty/include/binder/RpcServerTrusty.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <binder/ARpcServerTrusty.h>
 #include <binder/IBinder.h>
 #include <binder/RpcServer.h>
 #include <binder/RpcSession.h>
@@ -88,6 +89,28 @@
     explicit RpcServerTrusty(std::unique_ptr<RpcTransportCtx> ctx, std::string&& portName,
                              std::shared_ptr<const PortAcl>&& portAcl, size_t msgMaxSize);
 
+    // Internal helper that creates the RpcServer.
+    // This is used both from here and Rust.
+    static sp<RpcServer> makeRpcServer(std::unique_ptr<RpcTransportCtx> ctx) {
+        auto rpcServer = sp<RpcServer>::make(std::move(ctx));
+
+        // TODO(b/266741352): follow-up to prevent needing this in the future
+        // Trusty needs to be set to the latest stable version that is in prebuilts there.
+        LOG_ALWAYS_FATAL_IF(!rpcServer->setProtocolVersion(0));
+
+        return rpcServer;
+    }
+
+    friend struct ::ARpcServerTrusty;
+    friend ::ARpcServerTrusty* ::ARpcServerTrusty_newPerSession(::AIBinder* (*)(const void*, size_t,
+                                                                                char*),
+                                                                char*, void (*)(char*));
+    friend void ::ARpcServerTrusty_delete(::ARpcServerTrusty*);
+    friend int ::ARpcServerTrusty_handleConnect(::ARpcServerTrusty*, handle_t, const uuid*, void**);
+    friend int ::ARpcServerTrusty_handleMessage(void*);
+    friend void ::ARpcServerTrusty_handleDisconnect(void*);
+    friend void ::ARpcServerTrusty_handleChannelCleanup(void*);
+
     // The Rpc-specific context maintained for every open TIPC channel.
     struct ChannelContext {
         sp<RpcSession> session;
@@ -99,6 +122,11 @@
     static void handleDisconnect(const tipc_port* port, handle_t chan, void* ctx);
     static void handleChannelCleanup(void* ctx);
 
+    static int handleConnectInternal(RpcServer* rpcServer, handle_t chan, const uuid* peer,
+                                     void** ctx_p);
+    static int handleMessageInternal(void* ctx);
+    static void handleDisconnectInternal(void* ctx);
+
     static constexpr tipc_srv_ops kTipcOps = {
             .on_connect = &handleConnect,
             .on_message = &handleMessage,
diff --git a/libs/binder/trusty/ndk/Android.bp b/libs/binder/trusty/ndk/Android.bp
new file mode 100644
index 0000000..af9874a
--- /dev/null
+++ b/libs/binder/trusty/ndk/Android.bp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_library_headers {
+    name: "libbinder_trusty_ndk_headers",
+    export_include_dirs: ["include"],
+    host_supported: true,
+    vendor_available: true,
+}
diff --git a/libs/binder/trusty/ndk/include/sys/cdefs.h b/libs/binder/trusty/ndk/include/sys/cdefs.h
index eabfe60..7528f2b 100644
--- a/libs/binder/trusty/ndk/include/sys/cdefs.h
+++ b/libs/binder/trusty/ndk/include/sys/cdefs.h
@@ -15,11 +15,16 @@
  */
 #pragma once
 
+#if __has_include(<lk/compiler.h>)
 #include <lk/compiler.h>
 
 /* Alias the bionic macros to the ones from lk/compiler.h */
 #define __BEGIN_DECLS __BEGIN_CDECLS
 #define __END_DECLS __END_CDECLS
 
+#else // __has_include(<lk/compiler.h>)
+#include_next <sys/cdefs.h>
+#endif
+
 #define __INTRODUCED_IN(x) /* nothing on Trusty */
 #define __INTRODUCED_IN_LLNDK(x) /* nothing on Trusty */
diff --git a/libs/binder/trusty/rust/binder_ndk_sys/rules.mk b/libs/binder/trusty/rust/binder_ndk_sys/rules.mk
index 672d9b7..2aaa061 100644
--- a/libs/binder/trusty/rust/binder_ndk_sys/rules.mk
+++ b/libs/binder/trusty/rust/binder_ndk_sys/rules.mk
@@ -29,6 +29,10 @@
 	$(LIBBINDER_DIR)/trusty/ndk \
 	trusty/user/base/lib/trusty-sys \
 
+MODULE_RUSTFLAGS += \
+	--cfg 'android_vendor' \
+	--cfg 'trusty' \
+
 MODULE_BINDGEN_SRC_HEADER := $(LIBBINDER_DIR)/rust/sys/BinderBindings.hpp
 
 # Add the flags from the flag file
diff --git a/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/ARpcServerTrusty.cpp b/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/ARpcServerTrusty.cpp
new file mode 100644
index 0000000..451383a
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/ARpcServerTrusty.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/binder_libbinder.h>
+#include <binder/RpcServer.h>
+#include <binder/RpcServerTrusty.h>
+#include <binder/RpcSession.h>
+#include <binder/RpcTransportTipcTrusty.h>
+
+using android::RpcServer;
+using android::RpcServerTrusty;
+using android::RpcSession;
+using android::RpcTransportCtxFactoryTipcTrusty;
+using android::sp;
+using android::wp;
+
+struct ARpcServerTrusty {
+    sp<RpcServer> mRpcServer;
+
+    ARpcServerTrusty() = delete;
+    ARpcServerTrusty(sp<RpcServer> rpcServer) : mRpcServer(std::move(rpcServer)) {}
+};
+
+ARpcServerTrusty* ARpcServerTrusty_newPerSession(AIBinder* (*cb)(const void*, size_t, char*),
+                                                 char* cbArg, void (*cbArgDeleter)(char*)) {
+    std::shared_ptr<char> cbArgSp(cbArg, cbArgDeleter);
+
+    auto rpcTransportCtxFactory = RpcTransportCtxFactoryTipcTrusty::make();
+    if (rpcTransportCtxFactory == nullptr) {
+        return nullptr;
+    }
+
+    auto ctx = rpcTransportCtxFactory->newServerCtx();
+    if (ctx == nullptr) {
+        return nullptr;
+    }
+
+    auto rpcServer = RpcServerTrusty::makeRpcServer(std::move(ctx));
+    if (rpcServer == nullptr) {
+        return nullptr;
+    }
+
+    rpcServer->setPerSessionRootObject(
+            [cb, cbArgSp](wp<RpcSession> /*session*/, const void* addrPtr, size_t len) {
+                auto* aib = (*cb)(addrPtr, len, cbArgSp.get());
+                auto b = AIBinder_toPlatformBinder(aib);
+
+                // We have a new sp<IBinder> backed by the same binder, so we can
+                // finally release the AIBinder* from the callback
+                AIBinder_decStrong(aib);
+
+                return b;
+            });
+
+    return new (std::nothrow) ARpcServerTrusty(std::move(rpcServer));
+}
+
+void ARpcServerTrusty_delete(ARpcServerTrusty* rstr) {
+    delete rstr;
+}
+
+int ARpcServerTrusty_handleConnect(ARpcServerTrusty* rstr, handle_t chan, const uuid* peer,
+                                   void** ctx_p) {
+    return RpcServerTrusty::handleConnectInternal(rstr->mRpcServer.get(), chan, peer, ctx_p);
+}
+
+int ARpcServerTrusty_handleMessage(void* ctx) {
+    return RpcServerTrusty::handleMessageInternal(ctx);
+}
+
+void ARpcServerTrusty_handleDisconnect(void* ctx) {
+    RpcServerTrusty::handleDisconnectInternal(ctx);
+}
+
+void ARpcServerTrusty_handleChannelCleanup(void* ctx) {
+    RpcServerTrusty::handleChannelCleanup(ctx);
+}
diff --git a/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/rules.mk b/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/rules.mk
new file mode 100644
index 0000000..6def634
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/rules.mk
@@ -0,0 +1,29 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../../../..
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := \
+	$(LOCAL_DIR)/ARpcServerTrusty.cpp \
+
+MODULE_LIBRARY_DEPS += \
+	$(LIBBINDER_DIR)/trusty \
+	$(LIBBINDER_DIR)/trusty/ndk \
+	trusty/user/base/lib/libstdc++-trusty \
+
+include make/library.mk
diff --git a/libs/binder/trusty/rust/binder_rpc_server_bindgen/lib.rs b/libs/binder/trusty/rust/binder_rpc_server_bindgen/lib.rs
new file mode 100644
index 0000000..2e8b3ec
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_server_bindgen/lib.rs
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Generated Rust bindings to binder_rpc_server
+
+#[allow(bad_style)]
+mod sys {
+    include!(env!("BINDGEN_INC_FILE"));
+}
+
+pub use sys::*;
diff --git a/libs/binder/trusty/rust/binder_rpc_server_bindgen/rules.mk b/libs/binder/trusty/rust/binder_rpc_server_bindgen/rules.mk
new file mode 100644
index 0000000..4ee333f
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_server_bindgen/rules.mk
@@ -0,0 +1,37 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../../..
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := $(LOCAL_DIR)/lib.rs
+
+MODULE_CRATE_NAME := binder_rpc_server_bindgen
+
+MODULE_LIBRARY_DEPS += \
+	$(LOCAL_DIR)/cpp \
+	trusty/user/base/lib/libstdc++-trusty \
+	trusty/user/base/lib/trusty-sys \
+
+MODULE_BINDGEN_SRC_HEADER := \
+	$(LIBBINDER_DIR)/trusty/include/binder/ARpcServerTrusty.h
+
+MODULE_BINDGEN_FLAGS += \
+	--allowlist-type="ARpcServerTrusty" \
+	--allowlist-function="ARpcServerTrusty_.*" \
+
+include make/library.mk
diff --git a/libs/binder/trusty/rust/binder_rpc_test/aidl/rules.mk b/libs/binder/trusty/rust/binder_rpc_test/aidl/rules.mk
new file mode 100644
index 0000000..1b0dca0
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/aidl/rules.mk
@@ -0,0 +1,34 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_TESTS_DIR := $(LOCAL_DIR)/../../../../tests
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_AIDL_LANGUAGE := rust
+
+MODULE_CRATE_NAME := binder_rpc_test_aidl
+
+MODULE_AIDLS := \
+	$(LIBBINDER_TESTS_DIR)/BinderRpcTestClientInfo.aidl \
+	$(LIBBINDER_TESTS_DIR)/BinderRpcTestServerConfig.aidl \
+	$(LIBBINDER_TESTS_DIR)/BinderRpcTestServerInfo.aidl \
+	$(LIBBINDER_TESTS_DIR)/IBinderRpcCallback.aidl \
+	$(LIBBINDER_TESTS_DIR)/IBinderRpcSession.aidl \
+	$(LIBBINDER_TESTS_DIR)/IBinderRpcTest.aidl \
+	$(LIBBINDER_TESTS_DIR)/ParcelableCertificateData.aidl \
+
+include make/aidl.mk
diff --git a/libs/binder/trusty/rust/binder_rpc_test/binder_rpc_test_session/lib.rs b/libs/binder/trusty/rust/binder_rpc_test/binder_rpc_test_session/lib.rs
new file mode 100644
index 0000000..22cba44
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/binder_rpc_test_session/lib.rs
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use binder::{Interface, ParcelFileDescriptor, SpIBinder, Status, StatusCode, Strong};
+use binder_rpc_test_aidl::aidl::IBinderRpcCallback::IBinderRpcCallback;
+use binder_rpc_test_aidl::aidl::IBinderRpcSession::IBinderRpcSession;
+use binder_rpc_test_aidl::aidl::IBinderRpcTest::IBinderRpcTest;
+use std::sync::Mutex;
+
+static G_NUM: Mutex<i32> = Mutex::new(0);
+
+#[derive(Debug, Default)]
+pub struct MyBinderRpcSession {
+    name: String,
+}
+
+impl MyBinderRpcSession {
+    pub fn new(name: &str) -> Self {
+        Self::increment_instance_count();
+        Self { name: name.to_string() }
+    }
+
+    pub fn get_instance_count() -> i32 {
+        *G_NUM.lock().unwrap()
+    }
+
+    fn increment_instance_count() {
+        *G_NUM.lock().unwrap() += 1;
+    }
+
+    fn decrement_instance_count() {
+        *G_NUM.lock().unwrap() -= 1;
+    }
+}
+
+impl Drop for MyBinderRpcSession {
+    fn drop(&mut self) {
+        MyBinderRpcSession::decrement_instance_count();
+    }
+}
+
+impl Interface for MyBinderRpcSession {}
+
+impl IBinderRpcSession for MyBinderRpcSession {
+    fn getName(&self) -> Result<String, Status> {
+        Ok(self.name.clone())
+    }
+}
+
+impl IBinderRpcTest for MyBinderRpcSession {
+    fn sendString(&self, _: &str) -> Result<(), Status> {
+        todo!()
+    }
+    fn doubleString(&self, _s: &str) -> Result<String, Status> {
+        todo!()
+    }
+    fn getClientPort(&self) -> Result<i32, Status> {
+        todo!()
+    }
+    fn countBinders(&self) -> Result<Vec<i32>, Status> {
+        todo!()
+    }
+    fn getNullBinder(&self) -> Result<SpIBinder, Status> {
+        todo!()
+    }
+    fn pingMe(&self, _binder: &SpIBinder) -> Result<i32, Status> {
+        todo!()
+    }
+    fn repeatBinder(&self, _binder: Option<&SpIBinder>) -> Result<Option<SpIBinder>, Status> {
+        todo!()
+    }
+    fn holdBinder(&self, _binder: Option<&SpIBinder>) -> Result<(), Status> {
+        todo!()
+    }
+    fn getHeldBinder(&self) -> Result<Option<SpIBinder>, Status> {
+        todo!()
+    }
+    fn nestMe(
+        &self,
+        binder: &Strong<(dyn IBinderRpcTest + 'static)>,
+        count: i32,
+    ) -> Result<(), Status> {
+        if count < 0 {
+            Ok(())
+        } else {
+            binder.nestMe(binder, count - 1)
+        }
+    }
+    fn alwaysGiveMeTheSameBinder(&self) -> Result<SpIBinder, Status> {
+        todo!()
+    }
+    fn openSession(
+        &self,
+        _name: &str,
+    ) -> Result<Strong<(dyn IBinderRpcSession + 'static)>, Status> {
+        todo!()
+    }
+    fn getNumOpenSessions(&self) -> Result<i32, Status> {
+        todo!()
+    }
+    fn lock(&self) -> Result<(), Status> {
+        todo!()
+    }
+    fn unlockInMsAsync(&self, _: i32) -> Result<(), Status> {
+        todo!()
+    }
+    fn lockUnlock(&self) -> Result<(), Status> {
+        todo!()
+    }
+    fn sleepMs(&self, _: i32) -> Result<(), Status> {
+        todo!()
+    }
+    fn sleepMsAsync(&self, _: i32) -> Result<(), Status> {
+        todo!()
+    }
+    fn doCallback(
+        &self,
+        _: &Strong<(dyn IBinderRpcCallback + 'static)>,
+        _: bool,
+        _: bool,
+        _: &str,
+    ) -> Result<(), Status> {
+        todo!()
+    }
+    fn doCallbackAsync(
+        &self,
+        _: &Strong<(dyn IBinderRpcCallback + 'static)>,
+        _: bool,
+        _: bool,
+        _: &str,
+    ) -> Result<(), Status> {
+        todo!()
+    }
+    fn die(&self, _: bool) -> Result<(), Status> {
+        Err(Status::from(StatusCode::UNKNOWN_TRANSACTION))
+    }
+    fn scheduleShutdown(&self) -> Result<(), Status> {
+        todo!()
+    }
+    fn useKernelBinderCallingId(&self) -> Result<(), Status> {
+        todo!()
+    }
+    fn echoAsFile(&self, _: &str) -> Result<ParcelFileDescriptor, Status> {
+        todo!()
+    }
+    fn concatFiles(&self, _: &[ParcelFileDescriptor]) -> Result<ParcelFileDescriptor, Status> {
+        todo!()
+    }
+    fn blockingSendFdOneway(&self, _: &ParcelFileDescriptor) -> Result<(), Status> {
+        todo!()
+    }
+    fn blockingRecvFd(&self) -> Result<ParcelFileDescriptor, Status> {
+        todo!()
+    }
+    fn blockingSendIntOneway(&self, _: i32) -> Result<(), Status> {
+        todo!()
+    }
+    fn blockingRecvInt(&self) -> Result<i32, Status> {
+        todo!()
+    }
+}
diff --git a/libs/binder/trusty/rust/binder_rpc_test/binder_rpc_test_session/rules.mk b/libs/binder/trusty/rust/binder_rpc_test/binder_rpc_test_session/rules.mk
new file mode 100644
index 0000000..ae26355
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/binder_rpc_test_session/rules.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../../../..
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := $(LOCAL_DIR)/lib.rs
+
+MODULE_CRATE_NAME := binder_rpc_test_session
+
+MODULE_LIBRARY_DEPS += \
+	$(LIBBINDER_DIR)/trusty/rust \
+	$(LIBBINDER_DIR)/trusty/rust/rpcbinder \
+	$(LOCAL_DIR)/../aidl \
+	$(call FIND_CRATE,log) \
+	trusty/user/base/lib/trusty-std \
+
+include make/library.mk
diff --git a/libs/binder/trusty/rust/binder_rpc_test/main.rs b/libs/binder/trusty/rust/binder_rpc_test/main.rs
new file mode 100644
index 0000000..baea5a8
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/main.rs
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#![cfg(test)]
+
+use binder::{BinderFeatures, IBinder, Status, StatusCode, Strong};
+use binder_rpc_test_aidl::aidl::IBinderRpcSession::{BnBinderRpcSession, IBinderRpcSession};
+use binder_rpc_test_aidl::aidl::IBinderRpcTest::{BnBinderRpcTest, IBinderRpcTest};
+use binder_rpc_test_session::MyBinderRpcSession;
+use libc::{clock_gettime, CLOCK_REALTIME};
+use rpcbinder::RpcSession;
+use trusty_std::ffi::{CString, FallibleCString};
+
+test::init!();
+
+const SERVICE_PORT: &str = "com.android.trusty.binderRpcTestService.V1";
+const RUST_SERVICE_PORT: &str = "com.android.trusty.rust.binderRpcTestService.V1";
+
+macro_rules! service_test {
+    ($c_name:ident, $rust_name:ident, $body:expr) => {
+        #[test]
+        fn $c_name() {
+            $body(get_service(SERVICE_PORT))
+        }
+        #[test]
+        fn $rust_name() {
+            $body(get_service(RUST_SERVICE_PORT))
+        }
+    };
+}
+
+fn get_service(port: &str) -> Strong<dyn IBinderRpcTest> {
+    let port = CString::try_new(port).expect("Failed to allocate port name");
+    RpcSession::new().setup_trusty_client(port.as_c_str()).expect("Failed to create session")
+}
+
+fn expect_sessions(expected: i32, srv: &Strong<dyn IBinderRpcTest>) {
+    let count = srv.getNumOpenSessions();
+    assert!(count.is_ok());
+    assert_eq!(expected, count.unwrap());
+}
+
+fn get_time_ns() -> u64 {
+    let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 };
+
+    // Safety: Passing valid pointer to variable ts which lives past end of call
+    assert_eq!(unsafe { clock_gettime(CLOCK_REALTIME, &mut ts) }, 0);
+
+    ts.tv_sec as u64 * 1_000_000_000u64 + ts.tv_nsec as u64
+}
+
+fn get_time_ms() -> u64 {
+    get_time_ns() / 1_000_000u64
+}
+
+// ----------
+
+service_test! {ping, ping_rust, |srv: Strong<dyn IBinderRpcTest>| {
+    assert_eq!(srv.as_binder().ping_binder(), Ok(()));
+}}
+
+service_test! {send_something_oneway, send_something_oneway_rust, |srv: Strong<dyn IBinderRpcTest>| {
+    assert_eq!(srv.sendString("Foo"), Ok(()));
+}}
+
+service_test! {send_and_get_result_back, send_and_get_result_back_rust, |srv: Strong<dyn IBinderRpcTest>| {
+    assert_eq!(srv.doubleString("Foo"), Ok(String::from("FooFoo")));
+}}
+
+service_test! {send_and_get_result_back_big, send_and_get_result_back_big_rust, |srv: Strong<dyn IBinderRpcTest>| {
+    let single_len = 512;
+    let single = "a".repeat(single_len);
+    assert_eq!(srv.doubleString(&single), Ok(String::from(single.clone() + &single)));
+}}
+
+service_test! {invalid_null_binder_return, invalid_null_binder_return_rust, |srv: Strong<dyn IBinderRpcTest>| {
+    let binder = srv.getNullBinder();
+    assert!(binder == Err(Status::from(StatusCode::UNEXPECTED_NULL)) || binder == Err(Status::from(StatusCode::UNKNOWN_TRANSACTION)));
+}}
+
+service_test! {call_me_back, call_me_back_rust, |srv: Strong<dyn IBinderRpcTest>| {
+    let binder =
+        BnBinderRpcSession::new_binder(MyBinderRpcSession::new("Foo"), BinderFeatures::default())
+            .as_binder();
+    let result = srv.pingMe(&binder);
+    assert_eq!(result, Ok(0));
+}}
+
+service_test! {repeat_binder, repeat_binder_rust, |srv: Strong<dyn IBinderRpcTest>| {
+    let in_binder =
+        BnBinderRpcSession::new_binder(MyBinderRpcSession::new("Foo"), BinderFeatures::default())
+            .as_binder();
+    let result = srv.repeatBinder(Some(&in_binder));
+    assert_eq!(result.unwrap().unwrap(), in_binder);
+}}
+
+service_test! {repeat_their_binder, repeat_their_binder_rust, |srv: Strong<dyn IBinderRpcTest>| {
+    let session = srv.openSession("Test");
+    assert!(session.is_ok());
+
+    let in_binder = session.unwrap().as_binder();
+    let out_binder = srv.repeatBinder(Some(&in_binder));
+    assert_eq!(out_binder.unwrap().unwrap(), in_binder);
+}}
+
+service_test! {hold_binder, hold_binder_rust, |srv: Strong<dyn IBinderRpcTest>| {
+    let name = "Foo";
+
+    let binder =
+        BnBinderRpcSession::new_binder(MyBinderRpcSession::new(name), BinderFeatures::default())
+            .as_binder();
+    assert!(srv.holdBinder(Some(&binder)).is_ok());
+
+    let held = srv.getHeldBinder();
+    assert!(held.is_ok());
+    let held = held.unwrap();
+    assert!(held.is_some());
+    let held = held.unwrap();
+    assert_eq!(binder, held);
+
+    let session = held.into_interface::<dyn IBinderRpcSession>();
+    assert!(session.is_ok());
+
+    let session_name = session.unwrap().getName();
+    assert!(session_name.is_ok());
+    let session_name = session_name.unwrap();
+    assert_eq!(session_name, name);
+
+    assert!(srv.holdBinder(None).is_ok());
+}}
+
+service_test! {nested_transactions, nested_transactions_rust, |srv: Strong<dyn IBinderRpcTest>| {
+    let binder =
+        BnBinderRpcTest::new_binder(MyBinderRpcSession::new("Nest"), BinderFeatures::default());
+    assert!(srv.nestMe(&binder, 10).is_ok());
+}}
+
+service_test! {same_binder_equality, same_binder_equality_rust, |srv: Strong<dyn IBinderRpcTest>| {
+    let a = srv.alwaysGiveMeTheSameBinder();
+    assert!(a.is_ok());
+
+    let b = srv.alwaysGiveMeTheSameBinder();
+    assert!(b.is_ok());
+
+    assert_eq!(a.unwrap(), b.unwrap());
+}}
+
+service_test! {single_session, single_session_rust, |srv: Strong<dyn IBinderRpcTest>| {
+    let session = srv.openSession("aoeu");
+    assert!(session.is_ok());
+    let session = session.unwrap();
+    let name = session.getName();
+    assert!(name.is_ok());
+    assert_eq!(name.unwrap(), "aoeu");
+
+    let count = srv.getNumOpenSessions();
+    assert!(count.is_ok());
+    assert_eq!(count.unwrap(), 1);
+
+    drop(session);
+    let count = srv.getNumOpenSessions();
+    assert!(count.is_ok());
+    assert_eq!(count.unwrap(), 0);
+}}
+
+service_test! {many_session, many_session_rust, |srv: Strong<dyn IBinderRpcTest>| {
+    let mut sessions = Vec::new();
+
+    for i in 0..15 {
+        expect_sessions(i, &srv);
+
+        let session = srv.openSession(&(i.to_string()));
+        assert!(session.is_ok());
+        sessions.push(session.unwrap());
+    }
+
+    expect_sessions(sessions.len() as i32, &srv);
+
+    for i in 0..sessions.len() {
+        let name = sessions[i].getName();
+        assert!(name.is_ok());
+        assert_eq!(name.unwrap(), i.to_string());
+    }
+
+    expect_sessions(sessions.len() as i32, &srv);
+
+    while !sessions.is_empty() {
+        sessions.pop();
+
+        expect_sessions(sessions.len() as i32, &srv);
+    }
+
+    expect_sessions(0, &srv);
+}}
+
+service_test! {one_way_call_does_not_wait, one_way_call_does_not_wait_rust, |srv: Strong<dyn IBinderRpcTest>| {
+    let really_long_time_ms = 100;
+    let sleep_ms = really_long_time_ms * 5;
+
+    let before = get_time_ms();
+    let _ = srv.sleepMsAsync(sleep_ms);
+    let after = get_time_ms();
+
+    assert!(after < before + really_long_time_ms as u64);
+}}
diff --git a/libs/binder/trusty/rust/binder_rpc_test/manifest.json b/libs/binder/trusty/rust/binder_rpc_test/manifest.json
new file mode 100644
index 0000000..384ed44
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/manifest.json
@@ -0,0 +1,9 @@
+{
+    "uuid": "91eed949-8a9e-4569-9c83-5935fb624025",
+    "app_name": "rust_binder_rpc_test",
+    "min_heap": 32768,
+    "min_stack": 16384,
+    "mgmt_flags": {
+        "non_critical_app": true
+    }
+}
diff --git a/libs/binder/trusty/rust/binder_rpc_test/rules.mk b/libs/binder/trusty/rust/binder_rpc_test/rules.mk
new file mode 100644
index 0000000..8347a35
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/rules.mk
@@ -0,0 +1,37 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../../..
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := $(LOCAL_DIR)/main.rs
+
+MODULE_CRATE_NAME := binder_rpc_test
+
+MODULE_LIBRARY_DEPS += \
+	$(LIBBINDER_DIR)/trusty/rust \
+	$(LIBBINDER_DIR)/trusty/rust/rpcbinder \
+	$(LOCAL_DIR)/aidl \
+	$(LOCAL_DIR)/binder_rpc_test_session \
+	$(call FIND_CRATE,log) \
+	trusty/user/base/lib/trusty-std \
+
+MODULE_RUST_TESTS := true
+
+MANIFEST := $(LOCAL_DIR)/manifest.json
+
+include make/library.mk
diff --git a/libs/binder/trusty/rust/binder_rpc_test/service/main.rs b/libs/binder/trusty/rust/binder_rpc_test/service/main.rs
new file mode 100644
index 0000000..c4a758a
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/service/main.rs
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+use binder::{
+    BinderFeatures, IBinder, Interface, ParcelFileDescriptor, SpIBinder, Status, StatusCode, Strong,
+};
+use binder_rpc_test_aidl::aidl::IBinderRpcCallback::IBinderRpcCallback;
+use binder_rpc_test_aidl::aidl::IBinderRpcSession::{BnBinderRpcSession, IBinderRpcSession};
+use binder_rpc_test_aidl::aidl::IBinderRpcTest::{BnBinderRpcTest, IBinderRpcTest};
+use binder_rpc_test_session::MyBinderRpcSession;
+use libc::{c_long, nanosleep, timespec};
+use rpcbinder::RpcServer;
+use std::rc::Rc;
+use std::sync::Mutex;
+use tipc::{service_dispatcher, wrap_service, Manager, PortCfg};
+
+const RUST_SERVICE_PORT: &str = "com.android.trusty.rust.binderRpcTestService.V1";
+
+// -----------------------------------------------------------------------------
+
+static SESSION_COUNT: Mutex<i32> = Mutex::new(0);
+static HOLD_BINDER: Mutex<Option<SpIBinder>> = Mutex::new(None);
+static SAME_BINDER: Mutex<Option<SpIBinder>> = Mutex::new(None);
+
+#[derive(Debug, Default)]
+struct TestService {
+    port: i32,
+    name: String,
+}
+
+#[allow(dead_code)]
+impl TestService {
+    fn new(name: &str) -> Self {
+        *SESSION_COUNT.lock().unwrap() += 1;
+        Self { name: name.to_string(), ..Default::default() }
+    }
+
+    fn get_instance_count() -> i32 {
+        *SESSION_COUNT.lock().unwrap()
+    }
+}
+
+impl Drop for TestService {
+    fn drop(&mut self) {
+        *SESSION_COUNT.lock().unwrap() -= 1;
+    }
+}
+
+impl Interface for TestService {}
+
+impl IBinderRpcSession for TestService {
+    fn getName(&self) -> Result<String, Status> {
+        Ok(self.name.clone())
+    }
+}
+
+impl IBinderRpcTest for TestService {
+    fn sendString(&self, _: &str) -> Result<(), Status> {
+        // This is a oneway function, so caller returned immediately and gives back an Ok(()) regardless of what this returns
+        Ok(())
+    }
+    fn doubleString(&self, s: &str) -> Result<String, Status> {
+        let ss = [s, s].concat();
+        Ok(ss)
+    }
+    fn getClientPort(&self) -> Result<i32, Status> {
+        Ok(self.port)
+    }
+    fn countBinders(&self) -> Result<Vec<i32>, Status> {
+        todo!()
+    }
+    fn getNullBinder(&self) -> Result<SpIBinder, Status> {
+        Err(Status::from(StatusCode::UNKNOWN_TRANSACTION))
+    }
+    fn pingMe(&self, binder: &SpIBinder) -> Result<i32, Status> {
+        match binder.clone().ping_binder() {
+            Ok(()) => Ok(StatusCode::OK as i32),
+            Err(e) => Err(Status::from(e)),
+        }
+    }
+    fn repeatBinder(&self, binder: Option<&SpIBinder>) -> Result<Option<SpIBinder>, Status> {
+        match binder {
+            Some(x) => Ok(Some(x.clone())),
+            None => Err(Status::from(StatusCode::BAD_VALUE)),
+        }
+    }
+    fn holdBinder(&self, binder: Option<&SpIBinder>) -> Result<(), Status> {
+        *HOLD_BINDER.lock().unwrap() = binder.cloned();
+        Ok(())
+    }
+    fn getHeldBinder(&self) -> Result<Option<SpIBinder>, Status> {
+        Ok((*HOLD_BINDER.lock().unwrap()).clone())
+    }
+    fn nestMe(
+        &self,
+        binder: &Strong<(dyn IBinderRpcTest + 'static)>,
+        count: i32,
+    ) -> Result<(), Status> {
+        if count < 0 {
+            Ok(())
+        } else {
+            binder.nestMe(binder, count - 1)
+        }
+    }
+    fn alwaysGiveMeTheSameBinder(&self) -> Result<SpIBinder, Status> {
+        let mut locked = SAME_BINDER.lock().unwrap();
+        Ok((*locked)
+            .get_or_insert_with(|| {
+                BnBinderRpcTest::new_binder(TestService::default(), BinderFeatures::default())
+                    .as_binder()
+            })
+            .clone())
+    }
+    fn openSession(&self, name: &str) -> Result<Strong<(dyn IBinderRpcSession + 'static)>, Status> {
+        let s = BnBinderRpcSession::new_binder(
+            MyBinderRpcSession::new(name),
+            BinderFeatures::default(),
+        );
+        Ok(s)
+    }
+    fn getNumOpenSessions(&self) -> Result<i32, Status> {
+        let count = MyBinderRpcSession::get_instance_count();
+        Ok(count)
+    }
+    fn lock(&self) -> Result<(), Status> {
+        todo!()
+    }
+    fn unlockInMsAsync(&self, _: i32) -> Result<(), Status> {
+        todo!()
+    }
+    fn lockUnlock(&self) -> Result<(), Status> {
+        todo!()
+    }
+    fn sleepMs(&self, ms: i32) -> Result<(), Status> {
+        let ts = timespec {
+            tv_sec: (ms / 1000) as c_long,
+            tv_nsec: (ms % 1000) as c_long * 1_000_000 as c_long,
+        };
+
+        let mut rem = timespec { tv_sec: 0, tv_nsec: 0 };
+
+        // Safety: Passing valid pointers to variables ts & rem which live past end of call
+        assert_eq!(unsafe { nanosleep(&ts, &mut rem) }, 0);
+
+        Ok(())
+    }
+    fn sleepMsAsync(&self, ms: i32) -> Result<(), Status> {
+        self.sleepMs(ms)
+    }
+    fn doCallback(
+        &self,
+        _: &Strong<(dyn IBinderRpcCallback + 'static)>,
+        _: bool,
+        _: bool,
+        _: &str,
+    ) -> Result<(), Status> {
+        todo!()
+    }
+    fn doCallbackAsync(
+        &self,
+        _: &Strong<(dyn IBinderRpcCallback + 'static)>,
+        _: bool,
+        _: bool,
+        _: &str,
+    ) -> Result<(), Status> {
+        todo!()
+    }
+    fn die(&self, _: bool) -> Result<(), Status> {
+        Err(Status::from(StatusCode::UNKNOWN_TRANSACTION))
+    }
+    fn scheduleShutdown(&self) -> Result<(), Status> {
+        todo!()
+    }
+    fn useKernelBinderCallingId(&self) -> Result<(), Status> {
+        todo!()
+    }
+    fn echoAsFile(&self, _: &str) -> Result<ParcelFileDescriptor, Status> {
+        todo!()
+    }
+    fn concatFiles(&self, _: &[ParcelFileDescriptor]) -> Result<ParcelFileDescriptor, Status> {
+        todo!()
+    }
+    fn blockingSendFdOneway(&self, _: &ParcelFileDescriptor) -> Result<(), Status> {
+        todo!()
+    }
+    fn blockingRecvFd(&self) -> Result<ParcelFileDescriptor, Status> {
+        todo!()
+    }
+    fn blockingSendIntOneway(&self, _: i32) -> Result<(), Status> {
+        todo!()
+    }
+    fn blockingRecvInt(&self) -> Result<i32, Status> {
+        todo!()
+    }
+}
+
+wrap_service!(TestRpcServer(RpcServer: UnbufferedService));
+
+service_dispatcher! {
+    enum TestDispatcher {
+        TestRpcServer,
+    }
+}
+
+fn main() {
+    let mut dispatcher = TestDispatcher::<1>::new().expect("Could not create test dispatcher");
+
+    let service = BnBinderRpcTest::new_binder(TestService::default(), BinderFeatures::default());
+    let rpc_server =
+        TestRpcServer::new(RpcServer::new_per_session(move |_uuid| Some(service.as_binder())));
+
+    let cfg = PortCfg::new(RUST_SERVICE_PORT)
+        .expect("Could not create port config")
+        .allow_ta_connect()
+        .allow_ns_connect();
+    dispatcher.add_service(Rc::new(rpc_server), cfg).expect("Could not add service to dispatcher");
+
+    Manager::<_, _, 1, 4>::new_with_dispatcher(dispatcher, [])
+        .expect("Could not create service manager")
+        .run_event_loop()
+        .expect("Test event loop failed");
+}
diff --git a/libs/binder/trusty/rust/binder_rpc_test/service/manifest.json b/libs/binder/trusty/rust/binder_rpc_test/service/manifest.json
new file mode 100644
index 0000000..121ba11
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/service/manifest.json
@@ -0,0 +1,10 @@
+{
+    "uuid": "4741fc65-8b65-4893-ba55-b182c003c8b7",
+    "app_name": "rust_binder_rpc_test_service",
+    "min_heap": 16384,
+    "min_stack": 16384,
+    "mgmt_flags": {
+        "non_critical_app": true,
+        "restart_on_exit": true
+    }
+}
diff --git a/libs/binder/trusty/rust/binder_rpc_test/service/rules.mk b/libs/binder/trusty/rust/binder_rpc_test/service/rules.mk
new file mode 100644
index 0000000..f71ee9b
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/service/rules.mk
@@ -0,0 +1,36 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../../../..
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := $(LOCAL_DIR)/main.rs
+
+MODULE_CRATE_NAME := binder_rpc_test_service
+
+MODULE_LIBRARY_DEPS += \
+	$(LIBBINDER_DIR)/trusty/rust \
+	$(LIBBINDER_DIR)/trusty/rust/rpcbinder \
+	$(LIBBINDER_DIR)/trusty/rust/binder_rpc_server \
+	$(LOCAL_DIR)/../aidl \
+	$(LOCAL_DIR)/../binder_rpc_test_session \
+	$(LOCAL_DIR)/.. \
+	trusty/user/base/lib/tipc/rust \
+
+MANIFEST := $(LOCAL_DIR)/manifest.json
+
+include make/trusted_app.mk
diff --git a/libs/binder/trusty/rust/rpcbinder/rules.mk b/libs/binder/trusty/rust/rpcbinder/rules.mk
index 76f3b94..97f5c03 100644
--- a/libs/binder/trusty/rust/rpcbinder/rules.mk
+++ b/libs/binder/trusty/rust/rpcbinder/rules.mk
@@ -28,6 +28,8 @@
 	$(LIBBINDER_DIR)/trusty/rust \
 	$(LIBBINDER_DIR)/trusty/rust/binder_ndk_sys \
 	$(LIBBINDER_DIR)/trusty/rust/binder_rpc_unstable_bindgen \
+	$(LIBBINDER_DIR)/trusty/rust/binder_rpc_server_bindgen \
+	external/rust/crates/cfg-if \
 	external/rust/crates/foreign-types \
 	trusty/user/base/lib/tipc/rust \
 	trusty/user/base/lib/trusty-sys \
diff --git a/libs/binder/trusty/rust/rules.mk b/libs/binder/trusty/rust/rules.mk
index d343f14..36bd3a2 100644
--- a/libs/binder/trusty/rust/rules.mk
+++ b/libs/binder/trusty/rust/rules.mk
@@ -28,10 +28,12 @@
 	$(LIBBINDER_DIR)/trusty/rust/binder_ndk_sys \
 	$(LIBBINDER_DIR)/trusty/rust/binder_rpc_unstable_bindgen \
 	external/rust/crates/downcast-rs \
+	external/rust/crates/libc \
 	trusty/user/base/lib/trusty-sys \
 
 MODULE_RUSTFLAGS += \
 	--cfg 'android_vendor' \
+	--cfg 'trusty' \
 
 # Trusty does not have `ProcessState`, so there are a few
 # doc links in `IBinder` that are still broken.
diff --git a/libs/binder/trusty/usertests-inc.mk b/libs/binder/trusty/usertests-inc.mk
index 1300121..833d209 100644
--- a/libs/binder/trusty/usertests-inc.mk
+++ b/libs/binder/trusty/usertests-inc.mk
@@ -16,4 +16,8 @@
 TRUSTY_USER_TESTS += \
 	frameworks/native/libs/binder/trusty/binderRpcTest \
 	frameworks/native/libs/binder/trusty/binderRpcTest/service \
+	frameworks/native/libs/binder/trusty/rust/binder_rpc_test/service \
+
+TRUSTY_RUST_USER_TESTS += \
+	frameworks/native/libs/binder/trusty/rust/binder_rpc_test \
 
diff --git a/libs/binderdebug/BinderDebug.cpp b/libs/binderdebug/BinderDebug.cpp
index a8f2cbf..19f3aad 100644
--- a/libs/binderdebug/BinderDebug.cpp
+++ b/libs/binderdebug/BinderDebug.cpp
@@ -199,4 +199,31 @@
     return ret;
 }
 
+status_t getBinderTransactions(pid_t pid, std::string& transactionsOutput) {
+    std::ifstream ifs("/dev/binderfs/binder_logs/transactions");
+    if (!ifs.is_open()) {
+        ifs.open("/d/binder/transactions");
+        if (!ifs.is_open()) {
+            LOG(ERROR) << "Could not open /dev/binderfs/binder_logs/transactions. "
+                       << "Likely a permissions issue. errno: " << errno;
+            return -errno;
+        }
+    }
+
+    std::string line;
+    while (getline(ifs, line)) {
+        // The section for this pid ends with another "proc <pid>" for another
+        // process. There is only one entry per pid so we can stop looking after
+        // we've grabbed the whole section
+        if (base::StartsWith(line, "proc " + std::to_string(pid))) {
+            do {
+                transactionsOutput += line + '\n';
+            } while (getline(ifs, line) && !base::StartsWith(line, "proc "));
+            return OK;
+        }
+    }
+
+    return NAME_NOT_FOUND;
+}
+
 } // namespace  android
diff --git a/libs/binderdebug/include/binderdebug/BinderDebug.h b/libs/binderdebug/include/binderdebug/BinderDebug.h
index 6ce8edf..018393c 100644
--- a/libs/binderdebug/include/binderdebug/BinderDebug.h
+++ b/libs/binderdebug/include/binderdebug/BinderDebug.h
@@ -44,4 +44,12 @@
 status_t getBinderClientPids(BinderDebugContext context, pid_t pid, pid_t servicePid,
                              int32_t handle, std::vector<pid_t>* pids);
 
+/**
+ * Get the transactions for a given process from /dev/binderfs/binder_logs/transactions
+ * Return: OK if the file was found and the pid was found in the file.
+ *         -errno if there was an issue opening the file
+ *         NAME_NOT_FOUND if the pid wasn't found in the file
+ */
+status_t getBinderTransactions(pid_t pid, std::string& transactionOutput);
+
 } // namespace  android
diff --git a/libs/binderthreadstate/test.cpp b/libs/binderthreadstate/test.cpp
index b5c4010..e888b0a 100644
--- a/libs/binderthreadstate/test.cpp
+++ b/libs/binderthreadstate/test.cpp
@@ -22,6 +22,7 @@
 #include <binderthreadstateutilstest/1.0/IHidlStuff.h>
 #include <gtest/gtest.h>
 #include <hidl/HidlTransportSupport.h>
+#include <hidl/ServiceManagement.h>
 #include <hwbinder/IPCThreadState.h>
 
 #include <thread>
@@ -37,6 +38,7 @@
 using android::sp;
 using android::String16;
 using android::binder::Status;
+using android::hardware::isHidlSupported;
 using android::hardware::Return;
 using binderthreadstateutilstest::V1_0::IHidlStuff;
 
@@ -67,6 +69,7 @@
 // complicated calls are possible, but this should do here.
 
 static void callHidl(size_t id, int32_t idx) {
+    CHECK_EQ(true, isHidlSupported()) << "We shouldn't be calling HIDL if it's not supported";
     auto stuff = IHidlStuff::getService(id2name(id));
     CHECK(stuff->call(idx).isOk());
 }
@@ -174,6 +177,7 @@
 }
 
 TEST(BindThreadState, RemoteHidlCall) {
+    if (!isHidlSupported()) GTEST_SKIP() << "No  HIDL support on device";
     auto stuff = IHidlStuff::getService(id2name(kP1Id));
     ASSERT_NE(nullptr, stuff);
     ASSERT_TRUE(stuff->call(0).isOk());
@@ -186,11 +190,14 @@
 }
 
 TEST(BindThreadState, RemoteNestedStartHidlCall) {
+    if (!isHidlSupported()) GTEST_SKIP() << "No  HIDL support on device";
     auto stuff = IHidlStuff::getService(id2name(kP1Id));
     ASSERT_NE(nullptr, stuff);
     ASSERT_TRUE(stuff->call(100).isOk());
 }
 TEST(BindThreadState, RemoteNestedStartAidlCall) {
+    // this test case is trying ot nest a HIDL call which requires HIDL support
+    if (!isHidlSupported()) GTEST_SKIP() << "No  HIDL support on device";
     sp<IAidlStuff> stuff;
     ASSERT_EQ(OK, android::getService<IAidlStuff>(String16(id2name(kP1Id).c_str()), &stuff));
     ASSERT_NE(nullptr, stuff);
@@ -205,11 +212,15 @@
              defaultServiceManager()->addService(String16(id2name(thisId).c_str()), aidlServer));
     android::ProcessState::self()->startThreadPool();
 
-    // HIDL
-    android::hardware::configureRpcThreadpool(1, true /*callerWillJoin*/);
-    sp<IHidlStuff> hidlServer = new HidlServer(thisId, otherId);
-    CHECK_EQ(OK, hidlServer->registerAsService(id2name(thisId).c_str()));
-    android::hardware::joinRpcThreadpool();
+    if (isHidlSupported()) {
+        // HIDL
+        android::hardware::configureRpcThreadpool(1, true /*callerWillJoin*/);
+        sp<IHidlStuff> hidlServer = new HidlServer(thisId, otherId);
+        CHECK_EQ(OK, hidlServer->registerAsService(id2name(thisId).c_str()));
+        android::hardware::joinRpcThreadpool();
+    } else {
+        android::IPCThreadState::self()->joinThreadPool(true);
+    }
 
     return EXIT_FAILURE;
 }
@@ -227,9 +238,15 @@
     }
 
     android::waitForService<IAidlStuff>(String16(id2name(kP1Id).c_str()));
-    android::hardware::details::waitForHwService(IHidlStuff::descriptor, id2name(kP1Id).c_str());
+    if (isHidlSupported()) {
+        android::hardware::details::waitForHwService(IHidlStuff::descriptor,
+                                                     id2name(kP1Id).c_str());
+    }
     android::waitForService<IAidlStuff>(String16(id2name(kP2Id).c_str()));
-    android::hardware::details::waitForHwService(IHidlStuff::descriptor, id2name(kP2Id).c_str());
+    if (isHidlSupported()) {
+        android::hardware::details::waitForHwService(IHidlStuff::descriptor,
+                                                     id2name(kP2Id).c_str());
+    }
 
     return RUN_ALL_TESTS();
 }
diff --git a/libs/bufferqueueconverter/Android.bp b/libs/bufferqueueconverter/Android.bp
index 3c8c41f..196161b 100644
--- a/libs/bufferqueueconverter/Android.bp
+++ b/libs/bufferqueueconverter/Android.bp
@@ -17,9 +17,6 @@
 cc_library {
     name: "libbufferqueueconverter",
     vendor_available: true,
-    vndk: {
-        enabled: true,
-    },
     double_loadable: true,
 
     srcs: [
diff --git a/libs/ftl/algorithm_test.cpp b/libs/ftl/algorithm_test.cpp
index 487b1b8..11569f2 100644
--- a/libs/ftl/algorithm_test.cpp
+++ b/libs/ftl/algorithm_test.cpp
@@ -24,6 +24,17 @@
 namespace android::test {
 
 // Keep in sync with example usage in header file.
+TEST(Algorithm, Contains) {
+  const ftl::StaticVector vector = {1, 2, 3};
+  EXPECT_TRUE(ftl::contains(vector, 1));
+
+  EXPECT_FALSE(ftl::contains(vector, 0));
+  EXPECT_TRUE(ftl::contains(vector, 2));
+  EXPECT_TRUE(ftl::contains(vector, 3));
+  EXPECT_FALSE(ftl::contains(vector, 4));
+}
+
+// Keep in sync with example usage in header file.
 TEST(Algorithm, FindIf) {
   using namespace std::string_view_literals;
 
diff --git a/libs/ftl/non_null_test.cpp b/libs/ftl/non_null_test.cpp
index bd0462b..367b398 100644
--- a/libs/ftl/non_null_test.cpp
+++ b/libs/ftl/non_null_test.cpp
@@ -14,12 +14,17 @@
  * limitations under the License.
  */
 
+#include <ftl/algorithm.h>
 #include <ftl/non_null.h>
 #include <gtest/gtest.h>
 
 #include <memory>
+#include <set>
 #include <string>
 #include <string_view>
+#include <type_traits>
+#include <unordered_set>
+#include <vector>
 
 namespace android::test {
 namespace {
@@ -47,7 +52,7 @@
 // Keep in sync with example usage in header file.
 TEST(NonNull, Example) {
   const auto string_ptr = ftl::as_non_null(std::make_shared<std::string>("android"));
-  std::size_t size;
+  std::size_t size{};
   get_length(string_ptr, ftl::as_non_null(&size));
   EXPECT_EQ(size, 7u);
 
@@ -71,5 +76,84 @@
 
 static_assert(longest(kApplePtr, kOrangePtr) == kOrangePtr);
 
+static_assert(static_cast<bool>(kApplePtr));
+
+static_assert(std::is_same_v<decltype(ftl::as_non_null(std::declval<const int* const>())),
+                             ftl::NonNull<const int*>>);
+
 }  // namespace
+
+TEST(NonNull, SwapRawPtr) {
+  int i1 = 123;
+  int i2 = 456;
+  auto ptr1 = ftl::as_non_null(&i1);
+  auto ptr2 = ftl::as_non_null(&i2);
+
+  std::swap(ptr1, ptr2);
+
+  EXPECT_EQ(*ptr1, 456);
+  EXPECT_EQ(*ptr2, 123);
+}
+
+TEST(NonNull, SwapSmartPtr) {
+  auto ptr1 = ftl::as_non_null(std::make_shared<int>(123));
+  auto ptr2 = ftl::as_non_null(std::make_shared<int>(456));
+
+  std::swap(ptr1, ptr2);
+
+  EXPECT_EQ(*ptr1, 456);
+  EXPECT_EQ(*ptr2, 123);
+}
+
+TEST(NonNull, VectorOfRawPtr) {
+  int i = 1;
+  std::vector<ftl::NonNull<int*>> vpi;
+  vpi.push_back(ftl::as_non_null(&i));
+  EXPECT_FALSE(ftl::contains(vpi, nullptr));
+  EXPECT_TRUE(ftl::contains(vpi, &i));
+  EXPECT_TRUE(ftl::contains(vpi, vpi.front()));
+}
+
+TEST(NonNull, VectorOfSmartPtr) {
+  std::vector<ftl::NonNull<std::shared_ptr<int>>> vpi;
+  vpi.push_back(ftl::as_non_null(std::make_shared<int>(2)));
+  EXPECT_FALSE(ftl::contains(vpi, nullptr));
+  EXPECT_TRUE(ftl::contains(vpi, vpi.front().get()));
+  EXPECT_TRUE(ftl::contains(vpi, vpi.front()));
+}
+
+TEST(NonNull, SetOfRawPtr) {
+  int i = 1;
+  std::set<ftl::NonNull<int*>> spi;
+  spi.insert(ftl::as_non_null(&i));
+  EXPECT_FALSE(ftl::contains(spi, nullptr));
+  EXPECT_TRUE(ftl::contains(spi, &i));
+  EXPECT_TRUE(ftl::contains(spi, *spi.begin()));
+}
+
+TEST(NonNull, SetOfSmartPtr) {
+  std::set<ftl::NonNull<std::shared_ptr<int>>> spi;
+  spi.insert(ftl::as_non_null(std::make_shared<int>(2)));
+  EXPECT_FALSE(ftl::contains(spi, nullptr));
+  EXPECT_TRUE(ftl::contains(spi, spi.begin()->get()));
+  EXPECT_TRUE(ftl::contains(spi, *spi.begin()));
+}
+
+TEST(NonNull, UnorderedSetOfRawPtr) {
+  int i = 1;
+  std::unordered_set<ftl::NonNull<int*>> spi;
+  spi.insert(ftl::as_non_null(&i));
+  EXPECT_FALSE(ftl::contains(spi, nullptr));
+  EXPECT_TRUE(ftl::contains(spi, &i));
+  EXPECT_TRUE(ftl::contains(spi, *spi.begin()));
+}
+
+TEST(NonNull, UnorderedSetOfSmartPtr) {
+  std::unordered_set<ftl::NonNull<std::shared_ptr<int>>> spi;
+  spi.insert(ftl::as_non_null(std::make_shared<int>(2)));
+  EXPECT_FALSE(ftl::contains(spi, nullptr));
+  EXPECT_TRUE(ftl::contains(spi, spi.begin()->get()));
+  EXPECT_TRUE(ftl::contains(spi, *spi.begin()));
+}
+
 }  // namespace android::test
diff --git a/libs/gralloc/types/Android.bp b/libs/gralloc/types/Android.bp
index f300da5..8dabc2c 100644
--- a/libs/gralloc/types/Android.bp
+++ b/libs/gralloc/types/Android.bp
@@ -38,10 +38,7 @@
     },
 
     vendor_available: true,
-    vndk: {
-        enabled: true,
-        support_system_process: true,
-    },
+    double_loadable: true,
     apex_available: [
         "//apex_available:platform",
         "com.android.media.swcodec",
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index b70e80e..6c45746 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -155,9 +155,9 @@
     },
 }
 
-aidl_library {
-    name: "libgui_aidl_hdrs",
-    hdrs: [
+filegroup {
+    name: "libgui_extra_aidl_files",
+    srcs: [
         "android/gui/DisplayInfo.aidl",
         "android/gui/FocusRequest.aidl",
         "android/gui/InputApplicationInfo.aidl",
@@ -170,11 +170,34 @@
     ],
 }
 
+filegroup {
+    name: "libgui_extra_unstructured_aidl_files",
+    srcs: [
+        "android/gui/DisplayInfo.aidl",
+        "android/gui/InputApplicationInfo.aidl",
+        "android/gui/WindowInfo.aidl",
+        "android/gui/WindowInfosUpdate.aidl",
+    ],
+}
+
+aidl_library {
+    name: "libgui_aidl_hdrs",
+    hdrs: [":libgui_extra_aidl_files"],
+}
+
+aidl_library {
+    name: "libgui_extra_unstructured_aidl_hdrs",
+    hdrs: [":libgui_extra_unstructured_aidl_files"],
+}
+
 aidl_library {
     name: "libgui_aidl",
     srcs: ["aidl/**/*.aidl"],
     strip_import_prefix: "aidl",
-    deps: ["libgui_aidl_hdrs"],
+    deps: [
+        "libgui_aidl_hdrs",
+        "libgui_extra_unstructured_aidl_hdrs",
+    ],
 }
 
 filegroup {
@@ -282,10 +305,6 @@
 cc_library_shared {
     name: "libgui",
     vendor_available: true,
-    vndk: {
-        enabled: true,
-        private: true,
-    },
     double_loadable: true,
 
     defaults: [
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index fb69fda..69345a9 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -423,6 +423,11 @@
     sp<IConsumerListener> listener;
     bool callOnFrameDequeued = false;
     uint64_t bufferId = 0; // Only used if callOnFrameDequeued == true
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    std::vector<gui::AdditionalOptions> allocOptions;
+    uint32_t allocOptionsGenId = 0;
+#endif
+
     { // Autolock scope
         std::unique_lock<std::mutex> lock(mCore->mMutex);
 
@@ -486,11 +491,17 @@
         }
 
         const sp<GraphicBuffer>& buffer(mSlots[found].mGraphicBuffer);
-        if (mCore->mSharedBufferSlot == found &&
-                buffer->needsReallocation(width, height, format, BQ_LAYER_COUNT, usage)) {
-            BQ_LOGE("dequeueBuffer: cannot re-allocate a shared"
-                    "buffer");
 
+        bool needsReallocation = buffer == nullptr ||
+                buffer->needsReallocation(width, height, format, BQ_LAYER_COUNT, usage);
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+        needsReallocation |= mSlots[found].mAdditionalOptionsGenerationId !=
+                mCore->mAdditionalOptionsGenerationId;
+#endif
+
+        if (mCore->mSharedBufferSlot == found && needsReallocation) {
+            BQ_LOGE("dequeueBuffer: cannot re-allocate a shared buffer");
             return BAD_VALUE;
         }
 
@@ -505,9 +516,7 @@
 
         mSlots[found].mBufferState.dequeue();
 
-        if ((buffer == nullptr) ||
-                buffer->needsReallocation(width, height, format, BQ_LAYER_COUNT, usage))
-        {
+        if (needsReallocation) {
             if (CC_UNLIKELY(ATRACE_ENABLED())) {
                 if (buffer == nullptr) {
                     ATRACE_FORMAT_INSTANT("%s buffer reallocation: null", mConsumerName.c_str());
@@ -530,6 +539,10 @@
             mSlots[found].mFence = Fence::NO_FENCE;
             mCore->mBufferAge = 0;
             mCore->mIsAllocating = true;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+            allocOptions = mCore->mAdditionalOptions;
+            allocOptionsGenId = mCore->mAdditionalOptionsGenerationId;
+#endif
 
             returnFlags |= BUFFER_NEEDS_REALLOCATION;
         } else {
@@ -575,9 +588,29 @@
 
     if (returnFlags & BUFFER_NEEDS_REALLOCATION) {
         BQ_LOGV("dequeueBuffer: allocating a new buffer for slot %d", *outSlot);
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+        std::vector<GraphicBufferAllocator::AdditionalOptions> tempOptions;
+        tempOptions.reserve(allocOptions.size());
+        for (const auto& it : allocOptions) {
+            tempOptions.emplace_back(it.name.c_str(), it.value);
+        }
+        const GraphicBufferAllocator::AllocationRequest allocRequest = {
+                .importBuffer = true,
+                .width = width,
+                .height = height,
+                .format = format,
+                .layerCount = BQ_LAYER_COUNT,
+                .usage = usage,
+                .requestorName = {mConsumerName.c_str(), mConsumerName.size()},
+                .extras = std::move(tempOptions),
+        };
+        sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(allocRequest);
+#else
         sp<GraphicBuffer> graphicBuffer =
                 new GraphicBuffer(width, height, format, BQ_LAYER_COUNT, usage,
                                   {mConsumerName.c_str(), mConsumerName.size()});
+#endif
 
         status_t error = graphicBuffer->initCheck();
 
@@ -587,6 +620,9 @@
             if (error == NO_ERROR && !mCore->mIsAbandoned) {
                 graphicBuffer->setGenerationNumber(mCore->mGenerationNumber);
                 mSlots[*outSlot].mGraphicBuffer = graphicBuffer;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+                mSlots[*outSlot].mAdditionalOptionsGenerationId = allocOptionsGenId;
+#endif
                 callOnFrameDequeued = true;
                 bufferId = mSlots[*outSlot].mGraphicBuffer->getId();
             }
@@ -1342,6 +1378,9 @@
     }
 
     mCore->mAllowAllocation = true;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    mCore->mAdditionalOptions.clear();
+#endif
     VALIDATE_CONSISTENCY();
     return status;
 }
@@ -1410,6 +1449,9 @@
                     mCore->mSidebandStream.clear();
                     mCore->mDequeueCondition.notify_all();
                     mCore->mAutoPrerotation = false;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+                    mCore->mAdditionalOptions.clear();
+#endif
                     listener = mCore->mConsumerListener;
                 } else if (mCore->mConnectedApi == BufferQueueCore::NO_CONNECTED_API) {
                     BQ_LOGE("disconnect: not connected (req=%d)", api);
@@ -1462,6 +1504,10 @@
         PixelFormat allocFormat = PIXEL_FORMAT_UNKNOWN;
         uint64_t allocUsage = 0;
         std::string allocName;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+        std::vector<gui::AdditionalOptions> allocOptions;
+        uint32_t allocOptionsGenId = 0;
+#endif
         { // Autolock scope
             std::unique_lock<std::mutex> lock(mCore->mMutex);
             mCore->waitWhileAllocatingLocked(lock);
@@ -1490,14 +1536,42 @@
             allocUsage = usage | mCore->mConsumerUsageBits;
             allocName.assign(mCore->mConsumerName.c_str(), mCore->mConsumerName.size());
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+            allocOptions = mCore->mAdditionalOptions;
+            allocOptionsGenId = mCore->mAdditionalOptionsGenerationId;
+#endif
+
             mCore->mIsAllocating = true;
+
         } // Autolock scope
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+        std::vector<GraphicBufferAllocator::AdditionalOptions> tempOptions;
+        tempOptions.reserve(allocOptions.size());
+        for (const auto& it : allocOptions) {
+            tempOptions.emplace_back(it.name.c_str(), it.value);
+        }
+        const GraphicBufferAllocator::AllocationRequest allocRequest = {
+                .importBuffer = true,
+                .width = allocWidth,
+                .height = allocHeight,
+                .format = allocFormat,
+                .layerCount = BQ_LAYER_COUNT,
+                .usage = allocUsage,
+                .requestorName = allocName,
+                .extras = std::move(tempOptions),
+        };
+#endif
+
         Vector<sp<GraphicBuffer>> buffers;
         for (size_t i = 0; i < newBufferCount; ++i) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+            sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(allocRequest);
+#else
             sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(
                     allocWidth, allocHeight, allocFormat, BQ_LAYER_COUNT,
                     allocUsage, allocName);
+#endif
 
             status_t result = graphicBuffer->initCheck();
 
@@ -1524,8 +1598,12 @@
             PixelFormat checkFormat = format != 0 ?
                     format : mCore->mDefaultBufferFormat;
             uint64_t checkUsage = usage | mCore->mConsumerUsageBits;
+            bool allocOptionsChanged = false;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+            allocOptionsChanged = allocOptionsGenId != mCore->mAdditionalOptionsGenerationId;
+#endif
             if (checkWidth != allocWidth || checkHeight != allocHeight ||
-                checkFormat != allocFormat || checkUsage != allocUsage) {
+                checkFormat != allocFormat || checkUsage != allocUsage || allocOptionsChanged) {
                 // Something changed while we released the lock. Retry.
                 BQ_LOGV("allocateBuffers: size/format/usage changed while allocating. Retrying.");
                 mCore->mIsAllocating = false;
@@ -1543,6 +1621,9 @@
                 mCore->clearBufferSlotLocked(*slot); // Clean up the slot first
                 mSlots[*slot].mGraphicBuffer = buffers[i];
                 mSlots[*slot].mFence = Fence::NO_FENCE;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+                mSlots[*slot].mAdditionalOptionsGenerationId = allocOptionsGenId;
+#endif
 
                 // freeBufferLocked puts this slot on the free slots list. Since
                 // we then attached a buffer, move the slot to free buffer list.
@@ -1778,4 +1859,29 @@
 }
 #endif
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+status_t BufferQueueProducer::setAdditionalOptions(
+        const std::vector<gui::AdditionalOptions>& options) {
+    ATRACE_CALL();
+    BQ_LOGV("setAdditionalOptions, size = %zu", options.size());
+
+    if (!GraphicBufferAllocator::get().supportsAdditionalOptions()) {
+        return INVALID_OPERATION;
+    }
+
+    std::lock_guard<std::mutex> lock(mCore->mMutex);
+
+    if (mCore->mConnectedApi == BufferQueueCore::NO_CONNECTED_API) {
+        BQ_LOGE("setAdditionalOptions: BufferQueue not connected, cannot set additional options");
+        return NO_INIT;
+    }
+
+    if (mCore->mAdditionalOptions != options) {
+        mCore->mAdditionalOptions = options;
+        mCore->mAdditionalOptionsGenerationId++;
+    }
+    return NO_ERROR;
+}
+#endif
+
 } // namespace android
diff --git a/libs/gui/IGraphicBufferProducer.cpp b/libs/gui/IGraphicBufferProducer.cpp
index e81c098..0914480 100644
--- a/libs/gui/IGraphicBufferProducer.cpp
+++ b/libs/gui/IGraphicBufferProducer.cpp
@@ -80,6 +80,7 @@
     QUERY_MULTIPLE,
     GET_LAST_QUEUED_BUFFER2,
     SET_FRAME_RATE,
+    SET_ADDITIONAL_OPTIONS,
 };
 
 class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>
@@ -778,6 +779,25 @@
         return result;
     }
 #endif
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    virtual status_t setAdditionalOptions(const std::vector<gui::AdditionalOptions>& options) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+        if (options.size() > 100) {
+            return BAD_VALUE;
+        }
+        data.writeInt32(options.size());
+        for (const auto& it : options) {
+            data.writeCString(it.name.c_str());
+            data.writeInt64(it.value);
+        }
+        status_t result = remote()->transact(SET_ADDITIONAL_OPTIONS, data, &reply);
+        if (result == NO_ERROR) {
+            result = reply.readInt32();
+        }
+        return result;
+    }
+#endif
 };
 
 // Out-of-line virtual method definition to trigger vtable emission in this
@@ -981,6 +1001,13 @@
 }
 #endif
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+status_t IGraphicBufferProducer::setAdditionalOptions(const std::vector<gui::AdditionalOptions>&) {
+    // No-op for IGBP other than BufferQueue.
+    return INVALID_OPERATION;
+}
+#endif
+
 status_t IGraphicBufferProducer::exportToParcel(Parcel* parcel) {
     status_t res = OK;
     res = parcel->writeUint32(USE_BUFFER_QUEUE);
@@ -1533,6 +1560,28 @@
             return NO_ERROR;
         }
 #endif
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+        case SET_ADDITIONAL_OPTIONS: {
+            CHECK_INTERFACE(IGraphicBuffer, data, reply);
+            int optionCount = data.readInt32();
+            if (optionCount < 0 || optionCount > 100) {
+                return BAD_VALUE;
+            }
+            std::vector<gui::AdditionalOptions> opts;
+            opts.reserve(optionCount);
+            for (int i = 0; i < optionCount; i++) {
+                const char* name = data.readCString();
+                int64_t value = 0;
+                if (name == nullptr || data.readInt64(&value) != NO_ERROR) {
+                    return BAD_VALUE;
+                }
+                opts.emplace_back(name, value);
+            }
+            status_t result = setAdditionalOptions(opts);
+            reply->writeInt32(result);
+            return NO_ERROR;
+        }
+#endif
     }
     return BBinder::onTransact(code, data, reply, flags);
 }
diff --git a/libs/gui/LayerStatePermissions.cpp b/libs/gui/LayerStatePermissions.cpp
index 28697ca..c467cfd 100644
--- a/libs/gui/LayerStatePermissions.cpp
+++ b/libs/gui/LayerStatePermissions.cpp
@@ -23,31 +23,31 @@
 #include <gui/LayerState.h>
 
 namespace android {
-std::unordered_map<std::string, int> LayerStatePermissions::mPermissionMap = {
+std::vector<std::pair<String16, int>> LayerStatePermissions::mPermissionMap = {
         // If caller has ACCESS_SURFACE_FLINGER, they automatically get ROTATE_SURFACE_FLINGER
         // permission, as well
-        {"android.permission.ACCESS_SURFACE_FLINGER",
+        {String16("android.permission.ACCESS_SURFACE_FLINGER"),
          layer_state_t::Permission::ACCESS_SURFACE_FLINGER |
                  layer_state_t::Permission::ROTATE_SURFACE_FLINGER},
-        {"android.permission.ROTATE_SURFACE_FLINGER",
+        {String16("android.permission.ROTATE_SURFACE_FLINGER"),
          layer_state_t::Permission::ROTATE_SURFACE_FLINGER},
-        {"android.permission.INTERNAL_SYSTEM_WINDOW",
+        {String16("android.permission.INTERNAL_SYSTEM_WINDOW"),
          layer_state_t::Permission::INTERNAL_SYSTEM_WINDOW},
 };
 
-static bool callingThreadHasPermission(const std::string& permission __attribute__((unused)),
+static bool callingThreadHasPermission(const String16& permission __attribute__((unused)),
                                        int pid __attribute__((unused)),
                                        int uid __attribute__((unused))) {
 #ifndef __ANDROID_VNDK__
     return uid == AID_GRAPHICS || uid == AID_SYSTEM ||
-            PermissionCache::checkPermission(String16(permission.c_str()), pid, uid);
+            PermissionCache::checkPermission(permission, pid, uid);
 #endif // __ANDROID_VNDK__
     return false;
 }
 
 uint32_t LayerStatePermissions::getTransactionPermissions(int pid, int uid) {
     uint32_t permissions = 0;
-    for (auto [permissionName, permissionVal] : mPermissionMap) {
+    for (const auto& [permissionName, permissionVal] : mPermissionMap) {
         if (callingThreadHasPermission(permissionName, pid, uid)) {
             permissions |= permissionVal;
         }
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 086544e..87fd448 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -1475,6 +1475,9 @@
     case NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO:
         res = dispatchSetFrameTimelineInfo(args);
         break;
+    case NATIVE_WINDOW_SET_BUFFERS_ADDITIONAL_OPTIONS:
+        res = dispatchSetAdditionalOptions(args);
+        break;
     default:
         res = NAME_NOT_FOUND;
         break;
@@ -1833,6 +1836,24 @@
     return setFrameTimelineInfo(nativeWindowFtlInfo.frameNumber, ftlInfo);
 }
 
+int Surface::dispatchSetAdditionalOptions(va_list args) {
+    ATRACE_CALL();
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    const AHardwareBufferLongOptions* opts = va_arg(args, const AHardwareBufferLongOptions*);
+    const size_t optsSize = va_arg(args, size_t);
+    std::vector<gui::AdditionalOptions> convertedOpts;
+    convertedOpts.reserve(optsSize);
+    for (size_t i = 0; i < optsSize; i++) {
+        convertedOpts.emplace_back(opts[i].name, opts[i].value);
+    }
+    return setAdditionalOptions(convertedOpts);
+#else
+    (void)args;
+    return INVALID_OPERATION;
+#endif
+}
+
 bool Surface::transformToDisplayInverse() const {
     return (mTransform & NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY) ==
             NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY;
@@ -2619,6 +2640,17 @@
     return BAD_VALUE;
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+status_t Surface::setAdditionalOptions(const std::vector<gui::AdditionalOptions>& options) {
+    if (!GraphicBufferAllocator::get().supportsAdditionalOptions()) {
+        return INVALID_OPERATION;
+    }
+
+    Mutex::Autolock lock(mMutex);
+    return mGraphicBufferProducer->setAdditionalOptions(options);
+}
+#endif
+
 sp<IBinder> Surface::getSurfaceControlHandle() const {
     Mutex::Autolock lock(mMutex);
     return mSurfaceControlHandle;
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 1d2ea3e..3743025 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -706,6 +706,7 @@
 
 SurfaceComposerClient::Transaction::Transaction() {
     mId = generateId();
+    mTransactionCompletedListener = TransactionCompletedListener::getInstance();
 }
 
 SurfaceComposerClient::Transaction::Transaction(const Transaction& other)
@@ -723,6 +724,7 @@
     mComposerStates = other.mComposerStates;
     mInputWindowCommands = other.mInputWindowCommands;
     mListenerCallbacks = other.mListenerCallbacks;
+    mTransactionCompletedListener = TransactionCompletedListener::getInstance();
 }
 
 void SurfaceComposerClient::Transaction::sanitize(int pid, int uid) {
@@ -1000,8 +1002,8 @@
 
         // register all surface controls for all callbackIds for this listener that is merging
         for (const auto& surfaceControl : currentProcessCallbackInfo.surfaceControls) {
-            TransactionCompletedListener::getInstance()
-                    ->addSurfaceControlToCallbacks(currentProcessCallbackInfo, surfaceControl);
+            mTransactionCompletedListener->addSurfaceControlToCallbacks(currentProcessCallbackInfo,
+                                                                        surfaceControl);
         }
     }
 
@@ -1354,7 +1356,7 @@
     auto& callbackInfo = mListenerCallbacks[TransactionCompletedListener::getIInstance()];
     callbackInfo.surfaceControls.insert(sc);
 
-    TransactionCompletedListener::getInstance()->addSurfaceControlToCallbacks(callbackInfo, sc);
+    mTransactionCompletedListener->addSurfaceControlToCallbacks(callbackInfo, sc);
 }
 
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setPosition(
@@ -1672,7 +1674,7 @@
 
     std::shared_ptr<BufferData> bufferData = std::move(s->bufferData);
 
-    TransactionCompletedListener::getInstance()->removeReleaseBufferCallback(
+    mTransactionCompletedListener->removeReleaseBufferCallback(
             bufferData->generateReleaseCallbackId());
     s->what &= ~layer_state_t::eBufferChanged;
     s->bufferData = nullptr;
@@ -1715,8 +1717,7 @@
             bufferData->acquireFence = *fence;
             bufferData->flags |= BufferData::BufferDataChange::fenceChanged;
         }
-        bufferData->releaseBufferEndpoint =
-                IInterface::asBinder(TransactionCompletedListener::getIInstance());
+        bufferData->releaseBufferEndpoint = IInterface::asBinder(mTransactionCompletedListener);
         setReleaseBufferCallback(bufferData.get(), callback);
     }
 
@@ -1774,9 +1775,10 @@
         return;
     }
 
-    bufferData->releaseBufferListener = TransactionCompletedListener::getIInstance();
-    auto listener = TransactionCompletedListener::getInstance();
-    listener->setReleaseBufferCallback(bufferData->generateReleaseCallbackId(), callback);
+    bufferData->releaseBufferListener =
+            static_cast<sp<ITransactionCompletedListener>>(mTransactionCompletedListener);
+    mTransactionCompletedListener->setReleaseBufferCallback(bufferData->generateReleaseCallbackId(),
+                                                            callback);
 }
 
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setDataspace(
@@ -1932,18 +1934,15 @@
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::addTransactionCallback(
         TransactionCompletedCallbackTakesContext callback, void* callbackContext,
         CallbackId::Type callbackType) {
-    auto listener = TransactionCompletedListener::getInstance();
-
     auto callbackWithContext = std::bind(callback, callbackContext, std::placeholders::_1,
                                          std::placeholders::_2, std::placeholders::_3);
-    const auto& surfaceControls =
-            mListenerCallbacks[TransactionCompletedListener::getIInstance()].surfaceControls;
+    const auto& surfaceControls = mListenerCallbacks[mTransactionCompletedListener].surfaceControls;
 
     CallbackId callbackId =
-            listener->addCallbackFunction(callbackWithContext, surfaceControls, callbackType);
+            mTransactionCompletedListener->addCallbackFunction(callbackWithContext, surfaceControls,
+                                                               callbackType);
 
-    mListenerCallbacks[TransactionCompletedListener::getIInstance()].callbackIds.emplace(
-            callbackId);
+    mListenerCallbacks[mTransactionCompletedListener].callbackIds.emplace(callbackId);
     return *this;
 }
 
@@ -2333,8 +2332,9 @@
         const sp<SurfaceControl>& sc, TrustedPresentationCallback cb,
         const TrustedPresentationThresholds& thresholds, void* context,
         sp<SurfaceComposerClient::PresentationCallbackRAII>& outCallbackRef) {
-    auto listener = TransactionCompletedListener::getInstance();
-    outCallbackRef = listener->addTrustedPresentationCallback(cb, sc->getLayerId(), context);
+    outCallbackRef =
+            mTransactionCompletedListener->addTrustedPresentationCallback(cb, sc->getLayerId(),
+                                                                          context);
 
     layer_state_t* s = getLayerState(sc);
     if (!s) {
@@ -2351,8 +2351,7 @@
 
 SurfaceComposerClient::Transaction&
 SurfaceComposerClient::Transaction::clearTrustedPresentationCallback(const sp<SurfaceControl>& sc) {
-    auto listener = TransactionCompletedListener::getInstance();
-    listener->clearTrustedPresentationCallback(sc->getLayerId());
+    mTransactionCompletedListener->clearTrustedPresentationCallback(sc->getLayerId());
 
     layer_state_t* s = getLayerState(sc);
     if (!s) {
@@ -2443,7 +2442,6 @@
                                                      const sp<IBinder>& parentHandle,
                                                      LayerMetadata metadata,
                                                      uint32_t* outTransformHint) {
-    sp<SurfaceControl> sur;
     status_t err = mStatus;
 
     if (mStatus == NO_ERROR) {
diff --git a/libs/gui/aidl/Android.bp b/libs/gui/aidl/Android.bp
new file mode 100644
index 0000000..8ed08c2
--- /dev/null
+++ b/libs/gui/aidl/Android.bp
@@ -0,0 +1,85 @@
+// Copyright 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+    default_team: "trendy_team_android_core_graphics_stack",
+}
+
+filegroup {
+    name: "libgui_unstructured_aidl_files",
+    srcs: [
+        ":libgui_extra_unstructured_aidl_files",
+
+        "android/gui/BitTube.aidl",
+        "android/gui/CaptureArgs.aidl",
+        "android/gui/DisplayCaptureArgs.aidl",
+        "android/gui/LayerCaptureArgs.aidl",
+        "android/gui/LayerMetadata.aidl",
+        "android/gui/ParcelableVsyncEventData.aidl",
+        "android/gui/ScreenCaptureResults.aidl",
+    ],
+}
+
+aidl_library {
+    name: "libgui_unstructured_aidl",
+    hdrs: [":libgui_unstructured_aidl_files"],
+}
+
+filegroup {
+    name: "libgui_interface_aidl_files",
+    srcs: [
+        ":libgui_extra_aidl_files",
+        "**/*.aidl",
+    ],
+    exclude_srcs: [":libgui_unstructured_aidl_files"],
+}
+
+aidl_interface {
+    name: "android.gui",
+    unstable: true,
+    srcs: [
+        ":libgui_interface_aidl_files",
+    ],
+    include_dirs: [
+        "frameworks/native/libs/gui",
+        "frameworks/native/libs/gui/aidl",
+    ],
+    headers: [
+        "libgui_aidl_hdrs",
+        "libgui_extra_unstructured_aidl_hdrs",
+    ],
+    backend: {
+        rust: {
+            enabled: true,
+            additional_rustlibs: [
+                "libgui_aidl_types_rs",
+            ],
+        },
+        java: {
+            enabled: false,
+        },
+        cpp: {
+            enabled: false,
+        },
+        ndk: {
+            enabled: false,
+        },
+    },
+}
diff --git a/libs/gui/aidl/android/gui/BitTube.aidl b/libs/gui/aidl/android/gui/BitTube.aidl
index 6b0595e..eb231c1 100644
--- a/libs/gui/aidl/android/gui/BitTube.aidl
+++ b/libs/gui/aidl/android/gui/BitTube.aidl
@@ -16,4 +16,4 @@
 
 package android.gui;
 
-parcelable BitTube cpp_header "private/gui/BitTube.h";
+parcelable BitTube cpp_header "private/gui/BitTube.h" rust_type "gui_aidl_types_rs::BitTube";
diff --git a/libs/gui/aidl/android/gui/CaptureArgs.aidl b/libs/gui/aidl/android/gui/CaptureArgs.aidl
index 920d949..9f198ca 100644
--- a/libs/gui/aidl/android/gui/CaptureArgs.aidl
+++ b/libs/gui/aidl/android/gui/CaptureArgs.aidl
@@ -16,4 +16,4 @@
 
 package android.gui;
 
-parcelable CaptureArgs cpp_header "gui/DisplayCaptureArgs.h";
+parcelable CaptureArgs cpp_header "gui/DisplayCaptureArgs.h" rust_type "gui_aidl_types_rs::CaptureArgs";
diff --git a/libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl b/libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl
index 2caa2b9..fc97dbf 100644
--- a/libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl
+++ b/libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl
@@ -16,4 +16,5 @@
 
 package android.gui;
 
-parcelable DisplayCaptureArgs cpp_header "gui/DisplayCaptureArgs.h";
+parcelable DisplayCaptureArgs cpp_header "gui/DisplayCaptureArgs.h" rust_type "gui_aidl_types_rs::DisplayCaptureArgs";
+
diff --git a/libs/gui/aidl/android/gui/LayerCaptureArgs.aidl b/libs/gui/aidl/android/gui/LayerCaptureArgs.aidl
index f0def50..18d293f 100644
--- a/libs/gui/aidl/android/gui/LayerCaptureArgs.aidl
+++ b/libs/gui/aidl/android/gui/LayerCaptureArgs.aidl
@@ -16,4 +16,4 @@
 
 package android.gui;
 
-parcelable LayerCaptureArgs cpp_header "gui/LayerCaptureArgs.h";
+parcelable LayerCaptureArgs cpp_header "gui/LayerCaptureArgs.h" rust_type "gui_aidl_types_rs::LayerCaptureArgs";
diff --git a/libs/gui/aidl/android/gui/LayerMetadata.aidl b/libs/gui/aidl/android/gui/LayerMetadata.aidl
index 1368ac5..d8121be 100644
--- a/libs/gui/aidl/android/gui/LayerMetadata.aidl
+++ b/libs/gui/aidl/android/gui/LayerMetadata.aidl
@@ -16,4 +16,4 @@
 
 package android.gui;
 
-parcelable LayerMetadata cpp_header "gui/LayerMetadata.h";
+parcelable LayerMetadata cpp_header "gui/LayerMetadata.h" rust_type "gui_aidl_types_rs::LayerMetadata";
diff --git a/libs/gui/aidl/android/gui/ParcelableVsyncEventData.aidl b/libs/gui/aidl/android/gui/ParcelableVsyncEventData.aidl
index ba76671..53f443a 100644
--- a/libs/gui/aidl/android/gui/ParcelableVsyncEventData.aidl
+++ b/libs/gui/aidl/android/gui/ParcelableVsyncEventData.aidl
@@ -16,4 +16,4 @@
 
 package android.gui;
 
-parcelable ParcelableVsyncEventData cpp_header "gui/VsyncEventData.h";
+parcelable ParcelableVsyncEventData cpp_header "gui/VsyncEventData.h" rust_type "gui_aidl_types_rs::VsyncEventData";
diff --git a/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl b/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl
index 9908edd..97a9035 100644
--- a/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl
+++ b/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl
@@ -16,4 +16,4 @@
 
 package android.gui;
 
-parcelable ScreenCaptureResults cpp_header "gui/ScreenCaptureResults.h";
\ No newline at end of file
+parcelable ScreenCaptureResults cpp_header "gui/ScreenCaptureResults.h" rust_type "gui_aidl_types_rs::ScreenCaptureResults";
\ No newline at end of file
diff --git a/libs/gui/android/gui/DisplayInfo.aidl b/libs/gui/android/gui/DisplayInfo.aidl
index 30c0885..3b16724 100644
--- a/libs/gui/android/gui/DisplayInfo.aidl
+++ b/libs/gui/android/gui/DisplayInfo.aidl
@@ -16,4 +16,4 @@
 
 package android.gui;
 
-parcelable DisplayInfo cpp_header "gui/DisplayInfo.h";
+parcelable DisplayInfo cpp_header "gui/DisplayInfo.h" rust_type "gui_aidl_types_rs::DisplayInfo";
diff --git a/libs/gui/android/gui/WindowInfo.aidl b/libs/gui/android/gui/WindowInfo.aidl
index 2c85d15..b9d5ccf 100644
--- a/libs/gui/android/gui/WindowInfo.aidl
+++ b/libs/gui/android/gui/WindowInfo.aidl
@@ -16,4 +16,4 @@
 
 package android.gui;
 
-parcelable WindowInfo cpp_header "gui/WindowInfo.h";
+parcelable WindowInfo cpp_header "gui/WindowInfo.h" rust_type "gui_aidl_types_rs::WindowInfo";
diff --git a/libs/gui/android/gui/WindowInfosUpdate.aidl b/libs/gui/android/gui/WindowInfosUpdate.aidl
index 0c6109d..5c23e08 100644
--- a/libs/gui/android/gui/WindowInfosUpdate.aidl
+++ b/libs/gui/android/gui/WindowInfosUpdate.aidl
@@ -19,4 +19,4 @@
 import android.gui.DisplayInfo;
 import android.gui.WindowInfo;
 
-parcelable WindowInfosUpdate cpp_header "gui/WindowInfosUpdate.h";
+parcelable WindowInfosUpdate cpp_header "gui/WindowInfosUpdate.h" rust_type "gui_aidl_types_rs::WindowInfosUpdate";
diff --git a/libs/gui/include/gui/AdditionalOptions.h b/libs/gui/include/gui/AdditionalOptions.h
new file mode 100644
index 0000000..87cb913
--- /dev/null
+++ b/libs/gui/include/gui/AdditionalOptions.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+namespace android::gui {
+// Additional options to pass to AHardwareBuffer_allocateWithOptions.
+// See also allocator-v2's BufferDescriptorInfo.aidl
+struct AdditionalOptions {
+    std::string name;
+    int64_t value;
+
+    bool operator==(const AdditionalOptions& other) const {
+        return value == other.value && name == other.name;
+    }
+};
+} // namespace android::gui
diff --git a/libs/gui/include/gui/BufferQueueCore.h b/libs/gui/include/gui/BufferQueueCore.h
index 22c2be7..bb52c8e 100644
--- a/libs/gui/include/gui/BufferQueueCore.h
+++ b/libs/gui/include/gui/BufferQueueCore.h
@@ -17,6 +17,9 @@
 #ifndef ANDROID_GUI_BUFFERQUEUECORE_H
 #define ANDROID_GUI_BUFFERQUEUECORE_H
 
+#include <com_android_graphics_libgui_flags.h>
+
+#include <gui/AdditionalOptions.h>
 #include <gui/BufferItem.h>
 #include <gui/BufferQueueDefs.h>
 #include <gui/BufferSlot.h>
@@ -357,6 +360,14 @@
     // This allows the consumer to acquire an additional buffer if that buffer is not droppable and
     // will eventually be released or acquired by the consumer.
     bool mAllowExtraAcquire = false;
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    // Additional options to pass when allocating GraphicBuffers.
+    // GenerationID changes when the options change, indicating reallocation is required
+    uint32_t mAdditionalOptionsGenerationId = 0;
+    std::vector<gui::AdditionalOptions> mAdditionalOptions;
+#endif
+
 }; // class BufferQueueCore
 
 } // namespace android
diff --git a/libs/gui/include/gui/BufferQueueProducer.h b/libs/gui/include/gui/BufferQueueProducer.h
index de47483..37a9607 100644
--- a/libs/gui/include/gui/BufferQueueProducer.h
+++ b/libs/gui/include/gui/BufferQueueProducer.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_GUI_BUFFERQUEUEPRODUCER_H
 #define ANDROID_GUI_BUFFERQUEUEPRODUCER_H
 
+#include <gui/AdditionalOptions.h>
 #include <gui/BufferQueueDefs.h>
 
 #include <gui/IGraphicBufferProducer.h>
@@ -208,6 +209,10 @@
                           int8_t changeFrameRateStrategy) override;
 #endif
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    status_t setAdditionalOptions(const std::vector<gui::AdditionalOptions>& options) override;
+#endif
+
 protected:
     // see IGraphicsBufferProducer::setMaxDequeuedBufferCount, but with the ability to retrieve the
     // total maximum buffer count for the buffer queue (dequeued AND acquired)
diff --git a/libs/gui/include/gui/BufferSlot.h b/libs/gui/include/gui/BufferSlot.h
index 57704b1..5b32710 100644
--- a/libs/gui/include/gui/BufferSlot.h
+++ b/libs/gui/include/gui/BufferSlot.h
@@ -17,6 +17,8 @@
 #ifndef ANDROID_GUI_BUFFERSLOT_H
 #define ANDROID_GUI_BUFFERSLOT_H
 
+#include <com_android_graphics_libgui_flags.h>
+
 #include <ui/Fence.h>
 #include <ui/GraphicBuffer.h>
 
@@ -230,6 +232,11 @@
     // producer. If so, it needs to set the BUFFER_NEEDS_REALLOCATION flag when
     // dequeued to prevent the producer from using a stale cached buffer.
     bool mNeedsReallocation;
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    // The generation id of the additional options that mGraphicBuffer was allocated with
+    uint32_t mAdditionalOptionsGenerationId = 0;
+#endif
 };
 
 } // namespace android
diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h
index 8c1103b..4dbf9e1 100644
--- a/libs/gui/include/gui/DisplayEventReceiver.h
+++ b/libs/gui/include/gui/DisplayEventReceiver.h
@@ -119,6 +119,7 @@
             HdcpLevelsChange hdcpLevelsChange;
         };
     };
+    static_assert(sizeof(Event) == 216);
 
 public:
     /*
diff --git a/libs/gui/include/gui/IGraphicBufferProducer.h b/libs/gui/include/gui/IGraphicBufferProducer.h
index 7639e70..8fca946 100644
--- a/libs/gui/include/gui/IGraphicBufferProducer.h
+++ b/libs/gui/include/gui/IGraphicBufferProducer.h
@@ -31,6 +31,7 @@
 #include <ui/Rect.h>
 #include <ui/Region.h>
 
+#include <gui/AdditionalOptions.h>
 #include <gui/FrameTimestamps.h>
 #include <gui/HdrMetadata.h>
 
@@ -684,6 +685,10 @@
                                   int8_t changeFrameRateStrategy);
 #endif
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    virtual status_t setAdditionalOptions(const std::vector<gui::AdditionalOptions>& options);
+#endif
+
     struct RequestBufferOutput : public Flattenable<RequestBufferOutput> {
         RequestBufferOutput() = default;
 
diff --git a/libs/gui/include/gui/LayerStatePermissions.h b/libs/gui/include/gui/LayerStatePermissions.h
index a90f30c..b6588a2 100644
--- a/libs/gui/include/gui/LayerStatePermissions.h
+++ b/libs/gui/include/gui/LayerStatePermissions.h
@@ -15,15 +15,14 @@
  */
 
 #include <stdint.h>
-#include <string>
-#include <unordered_map>
-
+#include <utils/String16.h>
+#include <vector>
 namespace android {
 class LayerStatePermissions {
 public:
     static uint32_t getTransactionPermissions(int pid, int uid);
 
 private:
-    static std::unordered_map<std::string, int> mPermissionMap;
+    static std::vector<std::pair<String16, int>> mPermissionMap;
 };
 } // namespace android
\ No newline at end of file
diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h
index 39a59e4..bdcaaf2 100644
--- a/libs/gui/include/gui/Surface.h
+++ b/libs/gui/include/gui/Surface.h
@@ -215,6 +215,16 @@
                                   int8_t changeFrameRateStrategy);
     virtual status_t setFrameTimelineInfo(uint64_t frameNumber, const FrameTimelineInfo& info);
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    /**
+     * Set additional options to be passed when allocating a buffer. Only valid if IAllocator-V2
+     * or newer is available, otherwise will return INVALID_OPERATION. Only allowed to be called
+     * after connect and options are cleared when disconnect happens. Returns NO_INIT if not
+     * connected
+     */
+    status_t setAdditionalOptions(const std::vector<gui::AdditionalOptions>& options);
+#endif
+
 protected:
     virtual ~Surface();
 
@@ -302,6 +312,7 @@
     int dispatchGetLastQueuedBuffer(va_list args);
     int dispatchGetLastQueuedBuffer2(va_list args);
     int dispatchSetFrameTimelineInfo(va_list args);
+    int dispatchSetAdditionalOptions(va_list args);
 
     std::mutex mNameMutex;
     std::string mName;
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 79224e6..49b0a7d 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -431,6 +431,8 @@
         static std::mutex sApplyTokenMutex;
         void releaseBufferIfOverwriting(const layer_state_t& state);
         static void mergeFrameTimelineInfo(FrameTimelineInfo& t, const FrameTimelineInfo& other);
+        // Tracks registered callbacks
+        sp<TransactionCompletedListener> mTransactionCompletedListener = nullptr;
 
     protected:
         std::unordered_map<sp<IBinder>, ComposerState, IBinderHash> mComposerStates;
diff --git a/libs/gui/libgui_flags.aconfig b/libs/gui/libgui_flags.aconfig
index 3864699..a902a8c 100644
--- a/libs/gui/libgui_flags.aconfig
+++ b/libs/gui/libgui_flags.aconfig
@@ -16,3 +16,11 @@
   bug: "310927247"
   is_fixed_read_only: true
 }
+
+flag {
+  name: "bq_extendedallocate"
+  namespace: "core_graphics"
+  description: "Add BQ support for allocate with extended options"
+  bug: "268382490"
+  is_fixed_read_only: true
+}
diff --git a/libs/gui/rust/aidl_types/Android.bp b/libs/gui/rust/aidl_types/Android.bp
new file mode 100644
index 0000000..794f69e
--- /dev/null
+++ b/libs/gui/rust/aidl_types/Android.bp
@@ -0,0 +1,23 @@
+rust_defaults {
+    name: "libgui_aidl_types_defaults",
+    srcs: ["src/lib.rs"],
+    rustlibs: [
+        "libbinder_rs",
+    ],
+}
+
+rust_library {
+    name: "libgui_aidl_types_rs",
+    crate_name: "gui_aidl_types_rs",
+    defaults: ["libgui_aidl_types_defaults"],
+
+    // Currently necessary for host builds
+    // TODO(b/31559095): bionic on host should define this
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+    min_sdk_version: "VanillaIceCream",
+    vendor_available: true,
+}
diff --git a/libs/gui/rust/aidl_types/src/lib.rs b/libs/gui/rust/aidl_types/src/lib.rs
new file mode 100644
index 0000000..fead018
--- /dev/null
+++ b/libs/gui/rust/aidl_types/src/lib.rs
@@ -0,0 +1,55 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Rust wrapper for libgui AIDL types.
+
+use binder::{
+    binder_impl::{BorrowedParcel, UnstructuredParcelable},
+    impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
+    StatusCode,
+};
+
+macro_rules! stub_unstructured_parcelable {
+    ($name:ident) => {
+        /// Unimplemented stub parcelable.
+        #[derive(Debug, Default)]
+        pub struct $name(());
+
+        impl UnstructuredParcelable for $name {
+            fn write_to_parcel(&self, _parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
+                todo!()
+            }
+
+            fn from_parcel(_parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
+                todo!()
+            }
+        }
+
+        impl_deserialize_for_unstructured_parcelable!($name);
+        impl_serialize_for_unstructured_parcelable!($name);
+    };
+}
+
+stub_unstructured_parcelable!(BitTube);
+stub_unstructured_parcelable!(CaptureArgs);
+stub_unstructured_parcelable!(DisplayCaptureArgs);
+stub_unstructured_parcelable!(DisplayInfo);
+stub_unstructured_parcelable!(LayerCaptureArgs);
+stub_unstructured_parcelable!(LayerDebugInfo);
+stub_unstructured_parcelable!(LayerMetadata);
+stub_unstructured_parcelable!(ParcelableVsyncEventData);
+stub_unstructured_parcelable!(ScreenCaptureResults);
+stub_unstructured_parcelable!(VsyncEventData);
+stub_unstructured_parcelable!(WindowInfo);
+stub_unstructured_parcelable!(WindowInfosUpdate);
diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp
index 2faa330..ea8acbb 100644
--- a/libs/gui/tests/Android.bp
+++ b/libs/gui/tests/Android.bp
@@ -24,6 +24,7 @@
         "-Wextra",
         "-Wthread-safety",
         "-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_BQ_SETFRAMERATE=true",
+        "-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_BQ_EXTENDEDALLOCATE=true",
     ],
 
     srcs: [
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index 1ec6f91..272c5ed 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -28,6 +28,8 @@
 
 #include <ui/GraphicBuffer.h>
 
+#include <android-base/properties.h>
+
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/ProcessState.h>
@@ -47,6 +49,10 @@
 
 using namespace std::chrono_literals;
 
+static bool IsCuttlefish() {
+    return ::android::base::GetProperty("ro.product.board", "") == "cutf";
+}
+
 namespace android {
 using namespace com::android::graphics::libgui;
 
@@ -1439,4 +1445,55 @@
     EXPECT_EQ(nullptr, bufferConsumer.get());
 }
 
+TEST_F(BufferQueueTest, TestAdditionalOptions) {
+    sp<IGraphicBufferProducer> producer;
+    sp<IGraphicBufferConsumer> consumer;
+    BufferQueue::createBufferQueue(&producer, &consumer);
+
+    sp<BufferItemConsumer> bufferConsumer =
+            sp<BufferItemConsumer>::make(consumer, GRALLOC_USAGE_SW_READ_OFTEN, 2);
+    ASSERT_NE(nullptr, bufferConsumer.get());
+    sp<Surface> surface = sp<Surface>::make(producer);
+    native_window_set_buffers_format(surface.get(), PIXEL_FORMAT_RGBA_8888);
+    native_window_set_buffers_dimensions(surface.get(), 100, 100);
+
+    std::array<AHardwareBufferLongOptions, 1> extras = {{
+            {.name = "android.hardware.graphics.common.Dataspace", ADATASPACE_DISPLAY_P3},
+    }};
+
+    ASSERT_EQ(NO_INIT,
+              native_window_set_buffers_additional_options(surface.get(), extras.data(),
+                                                           extras.size()));
+
+    if (!IsCuttlefish()) {
+        GTEST_SKIP() << "Not cuttlefish";
+    }
+
+    ASSERT_EQ(OK, native_window_api_connect(surface.get(), NATIVE_WINDOW_API_CPU));
+    ASSERT_EQ(OK,
+              native_window_set_buffers_additional_options(surface.get(), extras.data(),
+                                                           extras.size()));
+
+    ANativeWindowBuffer* windowBuffer = nullptr;
+    int fence = -1;
+    ASSERT_EQ(OK, ANativeWindow_dequeueBuffer(surface.get(), &windowBuffer, &fence));
+
+    AHardwareBuffer* buffer = ANativeWindowBuffer_getHardwareBuffer(windowBuffer);
+    ASSERT_TRUE(buffer);
+    ADataSpace dataSpace = AHardwareBuffer_getDataSpace(buffer);
+    EXPECT_EQ(ADATASPACE_DISPLAY_P3, dataSpace);
+
+    ANativeWindow_cancelBuffer(surface.get(), windowBuffer, -1);
+
+    // Check that reconnecting properly clears the options
+    ASSERT_EQ(OK, native_window_api_disconnect(surface.get(), NATIVE_WINDOW_API_CPU));
+    ASSERT_EQ(OK, native_window_api_connect(surface.get(), NATIVE_WINDOW_API_CPU));
+
+    ASSERT_EQ(OK, ANativeWindow_dequeueBuffer(surface.get(), &windowBuffer, &fence));
+    buffer = ANativeWindowBuffer_getHardwareBuffer(windowBuffer);
+    ASSERT_TRUE(buffer);
+    dataSpace = AHardwareBuffer_getDataSpace(buffer);
+    EXPECT_EQ(ADATASPACE_UNKNOWN, dataSpace);
+}
+
 } // namespace android
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index fed590c..cc0649c 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -89,11 +89,6 @@
 
     bindgen_flags: [
         "--verbose",
-        "--allowlist-var=AMOTION_EVENT_FLAG_CANCELED",
-        "--allowlist-var=AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED",
-        "--allowlist-var=AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED",
-        "--allowlist-var=AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT",
-        "--allowlist-var=AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE",
         "--allowlist-var=AMOTION_EVENT_ACTION_CANCEL",
         "--allowlist-var=AMOTION_EVENT_ACTION_UP",
         "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_DOWN",
@@ -246,6 +241,7 @@
     shared_libs: [
         "libbase",
         "libbinder",
+        "libbinder_ndk",
         "libcutils",
         "liblog",
         "libPlatformProperties",
@@ -289,17 +285,6 @@
 
     target: {
         android: {
-            export_shared_lib_headers: ["libbinder"],
-
-            shared_libs: [
-                "libutils",
-                "libbinder",
-                // Stats logging library and its dependencies.
-                "libstatslog_libinput",
-                "libstatsbootstrap",
-                "android.os.statsbootstrap_aidl-cpp",
-            ],
-
             required: [
                 "motion_predictor_model_prebuilt",
                 "motion_predictor_model_config",
@@ -314,43 +299,6 @@
     },
 }
 
-// Use bootstrap version of stats logging library.
-// libinput is a bootstrap process (starts early in the boot process), and thus can't use the normal
-// `libstatslog` because that requires `libstatssocket`, which is only available later in the boot.
-cc_library {
-    name: "libstatslog_libinput",
-    generated_sources: ["statslog_libinput.cpp"],
-    generated_headers: ["statslog_libinput.h"],
-    export_generated_headers: ["statslog_libinput.h"],
-    shared_libs: [
-        "libbinder",
-        "libstatsbootstrap",
-        "libutils",
-        "android.os.statsbootstrap_aidl-cpp",
-    ],
-}
-
-genrule {
-    name: "statslog_libinput.h",
-    tools: ["stats-log-api-gen"],
-    cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_libinput.h --module libinput" +
-        " --namespace android,stats,libinput --bootstrap",
-    out: [
-        "statslog_libinput.h",
-    ],
-}
-
-genrule {
-    name: "statslog_libinput.cpp",
-    tools: ["stats-log-api-gen"],
-    cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_libinput.cpp --module libinput" +
-        " --namespace android,stats,libinput --importHeader statslog_libinput.h" +
-        " --bootstrap",
-    out: [
-        "statslog_libinput.cpp",
-    ],
-}
-
 cc_defaults {
     name: "libinput_fuzz_defaults",
     cpp_std: "c++20",
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index c348833..222647d 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -187,6 +187,7 @@
         mKeyCharacterMap(other.mKeyCharacterMap),
         mUsiVersion(other.mUsiVersion),
         mAssociatedDisplayId(other.mAssociatedDisplayId),
+        mEnabled(other.mEnabled),
         mHasVibrator(other.mHasVibrator),
         mHasBattery(other.mHasBattery),
         mHasButtonUnderPad(other.mHasButtonUnderPad),
@@ -202,7 +203,7 @@
 void InputDeviceInfo::initialize(int32_t id, int32_t generation, int32_t controllerNumber,
                                  const InputDeviceIdentifier& identifier, const std::string& alias,
                                  bool isExternal, bool hasMic, int32_t associatedDisplayId,
-                                 InputDeviceViewBehavior viewBehavior) {
+                                 InputDeviceViewBehavior viewBehavior, bool enabled) {
     mId = id;
     mGeneration = generation;
     mControllerNumber = controllerNumber;
@@ -213,6 +214,7 @@
     mSources = 0;
     mKeyboardType = AINPUT_KEYBOARD_TYPE_NONE;
     mAssociatedDisplayId = associatedDisplayId;
+    mEnabled = enabled;
     mHasVibrator = false;
     mHasBattery = false;
     mHasButtonUnderPad = false;
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 1869483..3ca6ccc 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -387,9 +387,9 @@
 
 status_t InputChannel::sendMessage(const InputMessage* msg) {
     ATRACE_NAME_IF(ATRACE_ENABLED(),
-                   StringPrintf("sendMessage(inputChannel=%s, seq=0x%" PRIx32 ", type=0x%" PRIx32
-                                ")",
-                                name.c_str(), msg->header.seq, msg->header.type));
+                   StringPrintf("sendMessage(inputChannel=%s, seq=0x%" PRIx32 ", type=%s)",
+                                name.c_str(), msg->header.seq,
+                                ftl::enum_string(msg->header.type).c_str()));
     const size_t msgLength = msg->size();
     InputMessage cleanMsg;
     msg->getSanitizedCopy(&cleanMsg);
@@ -458,9 +458,10 @@
              ftl::enum_string(msg->header.type).c_str());
     if (ATRACE_ENABLED()) {
         // Add an additional trace point to include data about the received message.
-        std::string message = StringPrintf("receiveMessage(inputChannel=%s, seq=0x%" PRIx32
-                                           ", type=0x%" PRIx32 ")",
-                                           name.c_str(), msg->header.seq, msg->header.type);
+        std::string message =
+                StringPrintf("receiveMessage(inputChannel=%s, seq=0x%" PRIx32 ", type=%s)",
+                             name.c_str(), msg->header.seq,
+                             ftl::enum_string(msg->header.type).c_str());
         ATRACE_NAME(message.c_str());
     }
     return OK;
diff --git a/libs/input/MotionPredictorMetricsManager.cpp b/libs/input/MotionPredictorMetricsManager.cpp
index 6872af2..cda39ce 100644
--- a/libs/input/MotionPredictorMetricsManager.cpp
+++ b/libs/input/MotionPredictorMetricsManager.cpp
@@ -25,10 +25,6 @@
 #include "Eigen/Core"
 #include "Eigen/Geometry"
 
-#ifdef __ANDROID__
-#include <statslog_libinput.h>
-#endif
-
 namespace android {
 namespace {
 
@@ -48,22 +44,9 @@
 
 void MotionPredictorMetricsManager::defaultReportAtomFunction(
         const MotionPredictorMetricsManager::AtomFields& atomFields) {
-    // Call stats_write logging function only on Android targets (not supported on host).
-#ifdef __ANDROID__
-    android::stats::libinput::
-            stats_write(android::stats::libinput::STYLUS_PREDICTION_METRICS_REPORTED,
-                            /*stylus_vendor_id=*/0,
-                            /*stylus_product_id=*/0,
-                            atomFields.deltaTimeBucketMilliseconds,
-                            atomFields.alongTrajectoryErrorMeanMillipixels,
-                            atomFields.alongTrajectoryErrorStdMillipixels,
-                            atomFields.offTrajectoryRmseMillipixels,
-                            atomFields.pressureRmseMilliunits,
-                            atomFields.highVelocityAlongTrajectoryRmse,
-                            atomFields.highVelocityOffTrajectoryRmse,
-                            atomFields.scaleInvariantAlongTrajectoryRmse,
-                            atomFields.scaleInvariantOffTrajectoryRmse);
-#endif
+    // TODO(b/338106546): Fix bootanimation build dependency issue, then re-add
+    // the stats_write function call here.
+    (void)atomFields;
 }
 
 MotionPredictorMetricsManager::MotionPredictorMetricsManager(
diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl
index 8f6f95b..90ed2b7 100644
--- a/libs/input/android/os/IInputConstants.aidl
+++ b/libs/input/android/os/IInputConstants.aidl
@@ -49,12 +49,105 @@
     const int POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY = 0x20000;
 
     /**
+     * This flag indicates that the window that received this motion event is partly
+     * or wholly obscured by another visible window above it and the event directly passed through
+     * the obscured area.
+     *
+     * A security sensitive application can check this flag to identify situations in which
+     * a malicious application may have covered up part of its content for the purpose
+     * of misleading the user or hijacking touches.  An appropriate response might be
+     * to drop the suspect touches or to take additional precautions to confirm the user's
+     * actual intent.
+     */
+    const int MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED = 0x1;
+
+    /**
+     * This flag indicates that the window that received this motion event is partly
+     * or wholly obscured by another visible window above it and the event did not directly pass
+     * through the obscured area.
+     *
+     * A security sensitive application can check this flag to identify situations in which
+     * a malicious application may have covered up part of its content for the purpose
+     * of misleading the user or hijacking touches.  An appropriate response might be
+     * to drop the suspect touches or to take additional precautions to confirm the user's
+     * actual intent.
+     *
+     * Unlike FLAG_WINDOW_IS_OBSCURED, this is only true if the window that received this event is
+     * obstructed in areas other than the touched location.
+     */
+    const int MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED = 0x2;
+
+    /**
+     * This private flag is only set on {@link #ACTION_HOVER_MOVE} events and indicates that
+     * this event will be immediately followed by a {@link #ACTION_HOVER_EXIT}. It is used to
+     * prevent generating redundant {@link #ACTION_HOVER_ENTER} events.
+     * @hide
+     */
+    const int MOTION_EVENT_FLAG_HOVER_EXIT_PENDING = 0x4;
+
+    /**
+     * This flag indicates that the event has been generated by a gesture generator. It
+     * provides a hint to the GestureDetector to not apply any touch slop.
+     *
+     * @hide
+     */
+    const int MOTION_EVENT_FLAG_IS_GENERATED_GESTURE = 0x8;
+
+    /**
+     * This flag is only set for events with {@link #ACTION_POINTER_UP} and {@link #ACTION_CANCEL}.
+     * It indicates that the pointer going up was an unintentional user touch. When FLAG_CANCELED
+     * is set, the typical actions that occur in response for a pointer going up (such as click
+     * handlers, end of drawing) should be aborted. This flag is typically set when the user was
+     * accidentally touching the screen, such as by gripping the device, or placing the palm on the
+     * screen.
+     *
+     * @see #ACTION_POINTER_UP
+     * @see #ACTION_CANCEL
+     */
+    const int INPUT_EVENT_FLAG_CANCELED = 0x20;
+
+    /**
+     * This flag indicates that the event will not cause a focus change if it is directed to an
+     * unfocused window, even if it an {@link #ACTION_DOWN}. This is typically used with pointer
+     * gestures to allow the user to direct gestures to an unfocused window without bringing the
+     * window into focus.
+     * @hide
+     */
+    const int MOTION_EVENT_FLAG_NO_FOCUS_CHANGE = 0x40;
+
+    /**
      * The input event was generated or modified by accessibility service.
      * Shared by both KeyEvent and MotionEvent flags, so this value should not overlap with either
      * set of flags, including in input/Input.h and in android/input.h.
      */
     const int INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT = 0x800;
 
+    /**
+     * Private flag that indicates when the system has detected that this motion event
+     * may be inconsistent with respect to the sequence of previously delivered motion events,
+     * such as when a pointer move event is sent but the pointer is not down.
+     *
+     * @hide
+     * @see #isTainted
+     * @see #setTainted
+     */
+    const int INPUT_EVENT_FLAG_TAINTED = 0x80000000;
+
+    /**
+     * Private flag indicating that this event was synthesized by the system and should be delivered
+     * to the accessibility focused view first. When being dispatched such an event is not handled
+     * by predecessors of the accessibility focused view and after the event reaches that view the
+     * flag is cleared and normal event dispatch is performed. This ensures that the platform can
+     * click on any view that has accessibility focus which is semantically equivalent to asking the
+     * view to perform a click accessibility action but more generic as views not implementing click
+     * action correctly can still be activated.
+     *
+     * @hide
+     * @see #isTargetAccessibilityFocus()
+     * @see #setTargetAccessibilityFocus(boolean)
+     */
+    const int MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS = 0x40000000;
+
     /* The default pointer acceleration value. */
     const int DEFAULT_POINTER_ACCELERATION = 3;
 
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index e161c2a..5c4b889 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -135,7 +135,13 @@
   description: "Enable prediction pruning based on jerk thresholds."
   bug: "266747654"
   is_fixed_read_only: true
+}
 
+flag {
+  name: "device_associations"
+  namespace: "input"
+  description: "Binds InputDevice name and InputDevice description against display unique id."
+  bug: "324075859"
 }
 
 flag {
@@ -144,3 +150,10 @@
   description: "Allow multiple input devices to be active in the same window simultaneously"
   bug: "330752824"
 }
+
+flag {
+  name: "hide_pointer_indicators_for_secure_windows"
+  namespace: "input"
+  description: "Hide touch and pointer indicators if a secure window is present on display"
+  bug: "325252005"
+}
diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs
index 705c959..d0dbd6f 100644
--- a/libs/input/rust/input.rs
+++ b/libs/input/rust/input.rs
@@ -17,6 +17,15 @@
 //! Common definitions of the Android Input Framework in rust.
 
 use bitflags::bitflags;
+use inputconstants::aidl::android::os::IInputConstants::INPUT_EVENT_FLAG_CANCELED;
+use inputconstants::aidl::android::os::IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT;
+use inputconstants::aidl::android::os::IInputConstants::INPUT_EVENT_FLAG_TAINTED;
+use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_HOVER_EXIT_PENDING;
+use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
+use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_NO_FOCUS_CHANGE;
+use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS;
+use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
 use std::fmt;
 
 /// The InputDevice ID.
@@ -182,18 +191,24 @@
     /// MotionEvent flags.
     #[derive(Debug)]
     pub struct MotionFlags: u32 {
-        /// FLAG_CANCELED
-        const CANCELED = input_bindgen::AMOTION_EVENT_FLAG_CANCELED as u32;
         /// FLAG_WINDOW_IS_OBSCURED
-        const WINDOW_IS_OBSCURED = input_bindgen::AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+        const WINDOW_IS_OBSCURED = MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED as u32;
         /// FLAG_WINDOW_IS_PARTIALLY_OBSCURED
-        const WINDOW_IS_PARTIALLY_OBSCURED =
-                input_bindgen::AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
-        /// FLAG_IS_ACCESSIBILITY_EVENT
-        const IS_ACCESSIBILITY_EVENT =
-                input_bindgen::AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT;
+        const WINDOW_IS_PARTIALLY_OBSCURED = MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED as u32;
+        /// FLAG_HOVER_EXIT_PENDING
+        const HOVER_EXIT_PENDING = MOTION_EVENT_FLAG_HOVER_EXIT_PENDING as u32;
+        /// FLAG_IS_GENERATED_GESTURE
+        const IS_GENERATED_GESTURE = MOTION_EVENT_FLAG_IS_GENERATED_GESTURE as u32;
+        /// FLAG_CANCELED
+        const CANCELED = INPUT_EVENT_FLAG_CANCELED as u32;
         /// FLAG_NO_FOCUS_CHANGE
-        const NO_FOCUS_CHANGE = input_bindgen::AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE;
+        const NO_FOCUS_CHANGE = MOTION_EVENT_FLAG_NO_FOCUS_CHANGE as u32;
+        /// FLAG_IS_ACCESSIBILITY_EVENT
+        const IS_ACCESSIBILITY_EVENT = INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT as u32;
+        /// FLAG_TAINTED
+        const TAINTED = INPUT_EVENT_FLAG_TAINTED as u32;
+        /// FLAG_TARGET_ACCESSIBILITY_FOCUS
+        const TARGET_ACCESSIBILITY_FOCUS = MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS as u32;
     }
 }
 
diff --git a/libs/input/rust/lib.rs b/libs/input/rust/lib.rs
index 01d9599..fb3f520 100644
--- a/libs/input/rust/lib.rs
+++ b/libs/input/rust/lib.rs
@@ -79,12 +79,20 @@
     pointer_properties: &[RustPointerProperties],
     flags: u32,
 ) -> String {
+    let motion_flags = MotionFlags::from_bits(flags);
+    if motion_flags.is_none() {
+        panic!(
+            "The conversion of flags 0x{:08x} failed, please check if some flags have not been \
+            added to MotionFlags.",
+            flags
+        );
+    }
     let result = verifier.process_movement(
         DeviceId(device_id),
         Source::from_bits(source).unwrap(),
         action,
         pointer_properties,
-        MotionFlags::from_bits(flags).unwrap(),
+        motion_flags.unwrap(),
     );
     match result {
         Ok(()) => "".to_string(),
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index ee140b7..6e724ac 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -65,6 +65,7 @@
         "libcutils",
         "liblog",
         "libPlatformProperties",
+        "libstatslog",
         "libtinyxml2",
         "libutils",
         "server_configurable_flags",
@@ -83,14 +84,6 @@
                 address: true,
             },
         },
-        android: {
-            static_libs: [
-                // Stats logging library and its dependencies.
-                "libstatslog_libinput",
-                "libstatsbootstrap",
-                "android.os.statsbootstrap_aidl-cpp",
-            ],
-        },
     },
 }
 
diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp
index dc38fef..b8f1caa 100644
--- a/libs/input/tests/MotionPredictor_test.cpp
+++ b/libs/input/tests/MotionPredictor_test.cpp
@@ -291,12 +291,12 @@
     MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
                               []() { return true /*enable prediction*/; });
 
-    // Jerk is medium (1.5 normalized, which is halfway between LOW_JANK and HIGH_JANK)
-    predictor.record(getMotionEvent(DOWN, 0, 4, 20ms));
-    predictor.record(getMotionEvent(MOVE, 0, 6.25, 30ms));
-    predictor.record(getMotionEvent(MOVE, 0, 9.4, 40ms));
-    predictor.record(getMotionEvent(MOVE, 0, 13.6, 50ms));
-    predictor.record(getMotionEvent(MOVE, 0, 19, 60ms));
+    // Jerk is medium (1.05 normalized, which is halfway between LOW_JANK and HIGH_JANK)
+    predictor.record(getMotionEvent(DOWN, 0, 5.2, 20ms));
+    predictor.record(getMotionEvent(MOVE, 0, 11.5, 30ms));
+    predictor.record(getMotionEvent(MOVE, 0, 22, 40ms));
+    predictor.record(getMotionEvent(MOVE, 0, 37.75, 50ms));
+    predictor.record(getMotionEvent(MOVE, 0, 59.8, 60ms));
     std::unique_ptr<MotionEvent> predicted = predictor.predict(82 * NSEC_PER_MSEC);
     EXPECT_NE(nullptr, predicted);
     // Halfway between LOW_JANK and HIGH_JANK means that half of the predictions
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index 969a5cf..33c303a 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -41,6 +41,8 @@
 #include <system/graphics.h>
 #include <unistd.h>
 
+#include <vndk/hardware_buffer.h>
+
 // system/window.h is a superset of the vndk and apex apis
 #include <apex/window.h>
 #include <vndk/window.h>
@@ -257,6 +259,7 @@
     NATIVE_WINDOW_SET_QUERY_INTERCEPTOR           = 47,    /* private */
     NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO         = 48,    /* private */
     NATIVE_WINDOW_GET_LAST_QUEUED_BUFFER2         = 49,    /* private */
+    NATIVE_WINDOW_SET_BUFFERS_ADDITIONAL_OPTIONS  = 50,
     // clang-format on
 };
 
@@ -1182,6 +1185,26 @@
     return window->perform(window, NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO, frameTimelineInfo);
 }
 
+/**
+ * native_window_set_buffers_additional_options(..., ExtendableType* additionalOptions, size_t size)
+ * All buffers dequeued after this call will have the additionalOptions specified.
+ *
+ * This must only be called after api_connect, otherwise NO_INIT is returned. The options are
+ * cleared in api_disconnect & api_connect
+ *
+ * If IAllocator is not v2 or newer this method returns INVALID_OPERATION
+ *
+ * \return NO_ERROR on success.
+ * \return NO_INIT if no api is connected
+ * \return INVALID_OPERATION if additional option support is not available
+ */
+static inline int native_window_set_buffers_additional_options(
+        struct ANativeWindow* window, const AHardwareBufferLongOptions* additionalOptions,
+        size_t additionalOptionsSize) {
+    return window->perform(window, NATIVE_WINDOW_SET_BUFFERS_ADDITIONAL_OPTIONS, additionalOptions,
+                           additionalOptionsSize);
+}
+
 // ------------------------------------------------------------------------------------------------
 // Candidates for APEX visibility
 // These functions are planned to be made stable for APEX modules, but have not
diff --git a/libs/nativewindow/rust/Android.bp b/libs/nativewindow/rust/Android.bp
index a3df482..97740db 100644
--- a/libs/nativewindow/rust/Android.bp
+++ b/libs/nativewindow/rust/Android.bp
@@ -54,6 +54,10 @@
     },
     min_sdk_version: "VanillaIceCream",
     vendor_available: true,
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.virt",
+    ],
 }
 
 rust_library {
@@ -78,6 +82,10 @@
     },
     min_sdk_version: "VanillaIceCream",
     vendor_available: true,
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.virt",
+    ],
 }
 
 rust_test {
@@ -116,6 +124,10 @@
     },
     min_sdk_version: "VanillaIceCream",
     vendor_available: true,
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.virt",
+    ],
 }
 
 rust_test {
diff --git a/libs/nativewindow/rust/src/lib.rs b/libs/nativewindow/rust/src/lib.rs
index 22ad834..dc3f51f 100644
--- a/libs/nativewindow/rust/src/lib.rs
+++ b/libs/nativewindow/rust/src/lib.rs
@@ -16,7 +16,8 @@
 
 extern crate nativewindow_bindgen as ffi;
 
-pub mod surface;
+mod surface;
+pub use surface::Surface;
 
 pub use ffi::{AHardwareBuffer_Format, AHardwareBuffer_UsageFlags};
 
diff --git a/libs/renderengine/benchmark/RenderEngineBench.cpp b/libs/renderengine/benchmark/RenderEngineBench.cpp
index 101f519..05a2063 100644
--- a/libs/renderengine/benchmark/RenderEngineBench.cpp
+++ b/libs/renderengine/benchmark/RenderEngineBench.cpp
@@ -71,7 +71,7 @@
                         .setImageCacheSize(1)
                         .setEnableProtectedContext(true)
                         .setPrecacheToneMapperShaderOnly(false)
-                        .setSupportsBackgroundBlur(true)
+                        .setBlurAlgorithm(renderengine::RenderEngine::BlurAlgorithm::KAWASE)
                         .setContextPriority(RenderEngine::ContextPriority::REALTIME)
                         .setThreaded(threaded)
                         .setGraphicsApi(graphicsApi)
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index 00a6213..980d913 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -50,6 +50,11 @@
 #define PROPERTY_DEBUG_RENDERENGINE_CAPTURE_FILENAME "debug.renderengine.capture_filename"
 
 /**
+ * Switches the cross-window background blur algorithm.
+ */
+#define PROPERTY_DEBUG_RENDERENGINE_BLUR_ALGORITHM "debug.renderengine.blur_algorithm"
+
+/**
  * Allows recording of Skia drawing commands with systrace.
  */
 #define PROPERTY_SKIA_ATRACE_ENABLED "debug.renderengine.skia_atrace_enabled"
@@ -107,9 +112,32 @@
         GRAPHITE,
     };
 
+    enum class BlurAlgorithm {
+        NONE,
+        GAUSSIAN,
+        KAWASE,
+    };
+
     static std::unique_ptr<RenderEngine> create(const RenderEngineCreationArgs& args);
 
-    static bool canSupport(GraphicsApi);
+    // Check if the device supports the given GraphicsApi.
+    //
+    // If called for GraphicsApi::VK then underlying (unprotected) VK resources will be preserved
+    // to optimize subsequent VK initialization, but teardown(GraphicsApi::VK) must be invoked if
+    // the caller subsequently decides to NOT use VK.
+    //
+    // The first call may require significant resource initialization, but subsequent checks are
+    // cached internally.
+    static bool canSupport(GraphicsApi graphicsApi);
+
+    // Teardown any GPU API resources that were previously initialized but are no longer needed.
+    //
+    // Must be called with GraphicsApi::VK if canSupport(GraphicsApi::VK) was previously invoked but
+    // the caller subsequently decided to not use VK.
+    //
+    // This is safe to call if there is nothing to teardown, but NOT safe to call if a RenderEngine
+    // instance exists. The RenderEngine destructor will handle its own teardown logic.
+    static void teardown(GraphicsApi graphicsApi);
 
     virtual ~RenderEngine() = 0;
 
@@ -258,7 +286,7 @@
     bool useColorManagement;
     bool enableProtectedContext;
     bool precacheToneMapperShaderOnly;
-    bool supportsBackgroundBlur;
+    RenderEngine::BlurAlgorithm blurAlgorithm;
     RenderEngine::ContextPriority contextPriority;
     RenderEngine::Threaded threaded;
     RenderEngine::GraphicsApi graphicsApi;
@@ -270,7 +298,7 @@
     // must be created by Builder via constructor with full argument list
     RenderEngineCreationArgs(int _pixelFormat, uint32_t _imageCacheSize,
                              bool _enableProtectedContext, bool _precacheToneMapperShaderOnly,
-                             bool _supportsBackgroundBlur,
+                             RenderEngine::BlurAlgorithm _blurAlgorithm,
                              RenderEngine::ContextPriority _contextPriority,
                              RenderEngine::Threaded _threaded,
                              RenderEngine::GraphicsApi _graphicsApi,
@@ -279,7 +307,7 @@
             imageCacheSize(_imageCacheSize),
             enableProtectedContext(_enableProtectedContext),
             precacheToneMapperShaderOnly(_precacheToneMapperShaderOnly),
-            supportsBackgroundBlur(_supportsBackgroundBlur),
+            blurAlgorithm(_blurAlgorithm),
             contextPriority(_contextPriority),
             threaded(_threaded),
             graphicsApi(_graphicsApi),
@@ -306,8 +334,8 @@
         this->precacheToneMapperShaderOnly = precacheToneMapperShaderOnly;
         return *this;
     }
-    Builder& setSupportsBackgroundBlur(bool supportsBackgroundBlur) {
-        this->supportsBackgroundBlur = supportsBackgroundBlur;
+    Builder& setBlurAlgorithm(RenderEngine::BlurAlgorithm blurAlgorithm) {
+        this->blurAlgorithm = blurAlgorithm;
         return *this;
     }
     Builder& setContextPriority(RenderEngine::ContextPriority contextPriority) {
@@ -328,7 +356,7 @@
     }
     RenderEngineCreationArgs build() const {
         return RenderEngineCreationArgs(pixelFormat, imageCacheSize, enableProtectedContext,
-                                        precacheToneMapperShaderOnly, supportsBackgroundBlur,
+                                        precacheToneMapperShaderOnly, blurAlgorithm,
                                         contextPriority, threaded, graphicsApi, skiaBackend);
     }
 
@@ -338,7 +366,7 @@
     uint32_t imageCacheSize = 0;
     bool enableProtectedContext = false;
     bool precacheToneMapperShaderOnly = false;
-    bool supportsBackgroundBlur = false;
+    RenderEngine::BlurAlgorithm blurAlgorithm = RenderEngine::BlurAlgorithm::NONE;
     RenderEngine::ContextPriority contextPriority = RenderEngine::ContextPriority::MEDIUM;
     RenderEngine::Threaded threaded = RenderEngine::Threaded::YES;
     RenderEngine::GraphicsApi graphicsApi = RenderEngine::GraphicsApi::GL;
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index 61369ae..48270e1 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -271,7 +271,7 @@
                                        EGLContext ctxt, EGLSurface placeholder,
                                        EGLContext protectedContext, EGLSurface protectedPlaceholder)
       : SkiaRenderEngine(args.threaded, static_cast<PixelFormat>(args.pixelFormat),
-                         args.supportsBackgroundBlur),
+                         args.blurAlgorithm),
         mEGLDisplay(display),
         mEGLContext(ctxt),
         mPlaceholderSurface(placeholder),
@@ -279,7 +279,7 @@
         mProtectedPlaceholderSurface(protectedPlaceholder) {}
 
 SkiaGLRenderEngine::~SkiaGLRenderEngine() {
-    finishRenderingAndAbandonContext();
+    finishRenderingAndAbandonContexts();
     if (mPlaceholderSurface != EGL_NO_SURFACE) {
         eglDestroySurface(mEGLDisplay, mPlaceholderSurface);
     }
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index 2484650..325a911 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -273,32 +273,47 @@
 }
 
 SkiaRenderEngine::SkiaRenderEngine(Threaded threaded, PixelFormat pixelFormat,
-                                   bool supportsBackgroundBlur)
+                                   BlurAlgorithm blurAlgorithm)
       : RenderEngine(threaded), mDefaultPixelFormat(pixelFormat) {
-    if (supportsBackgroundBlur) {
-        ALOGD("Background Blurs Enabled");
-        mBlurFilter = new KawaseBlurFilter();
+    switch (blurAlgorithm) {
+        case BlurAlgorithm::GAUSSIAN: {
+            ALOGD("Background Blurs Enabled (Gaussian algorithm)");
+            mBlurFilter = new GaussianBlurFilter();
+            break;
+        }
+        case BlurAlgorithm::KAWASE: {
+            ALOGD("Background Blurs Enabled (Kawase algorithm)");
+            mBlurFilter = new KawaseBlurFilter();
+            break;
+        }
+        default: {
+            mBlurFilter = nullptr;
+            break;
+        }
     }
+
     mCapture = std::make_unique<SkiaCapture>();
 }
 
 SkiaRenderEngine::~SkiaRenderEngine() { }
 
-// To be called from backend dtors.
-void SkiaRenderEngine::finishRenderingAndAbandonContext() {
+// To be called from backend dtors. Used to clean up Skia objects before GPU API contexts are
+// destroyed by subclasses.
+void SkiaRenderEngine::finishRenderingAndAbandonContexts() {
     std::lock_guard<std::mutex> lock(mRenderingMutex);
 
     if (mBlurFilter) {
         delete mBlurFilter;
     }
 
-    if (mContext) {
-        mContext->finishRenderingAndAbandonContext();
-    }
+    // Leftover textures may hold refs to backend-specific Skia contexts, which must be released
+    // before ~SkiaGpuContext is called.
+    mTextureCleanupMgr.setDeferredStatus(false);
+    mTextureCleanupMgr.cleanup();
 
-    if (mProtectedContext) {
-        mProtectedContext->finishRenderingAndAbandonContext();
-    }
+    // ~SkiaGpuContext must be called before GPU API contexts are torn down.
+    mContext.reset();
+    mProtectedContext.reset();
 }
 
 void SkiaRenderEngine::useProtectedContext(bool useProtectedContext) {
@@ -405,9 +420,7 @@
 
     // If we were to support caching protected buffers then we will need to switch the
     // currently bound context if we are not already using the protected context (and subsequently
-    // switch back after the buffer is cached).  However, for non-protected content we can bind
-    // the texture in either GL context because they are initialized with the same share_context
-    // which allows the texture state to be shared between them.
+    // switch back after the buffer is cached).
     auto context = getActiveContext();
     auto& cache = mTextureCache;
 
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index d7b4910..38db810 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -59,7 +59,7 @@
 class SkiaRenderEngine : public RenderEngine {
 public:
     static std::unique_ptr<SkiaRenderEngine> create(const RenderEngineCreationArgs& args);
-    SkiaRenderEngine(Threaded, PixelFormat pixelFormat, bool supportsBackgroundBlur);
+    SkiaRenderEngine(Threaded, PixelFormat pixelFormat, BlurAlgorithm);
     ~SkiaRenderEngine() override;
 
     std::future<void> primeCache(bool shouldPrimeUltraHDR) override final;
@@ -79,9 +79,9 @@
     void ensureContextsCreated();
 
 protected:
-    // This is so backends can stop the generic rendering state first before
-    // cleaning up backend-specific state
-    void finishRenderingAndAbandonContext();
+    // This is so backends can stop the generic rendering state first before cleaning up
+    // backend-specific state. SkiaGpuContexts are invalid after invocation.
+    void finishRenderingAndAbandonContexts();
 
     // Functions that a given backend (GLES, Vulkan) must implement
     using Contexts = std::pair<unique_ptr<SkiaGpuContext>, unique_ptr<SkiaGpuContext>>;
@@ -168,9 +168,6 @@
     // Number of external holders of ExternalTexture references, per GraphicBuffer ID.
     std::unordered_map<GraphicBufferId, int32_t> mGraphicBufferExternalRefs
             GUARDED_BY(mRenderingMutex);
-    // For GL, this cache is shared between protected and unprotected contexts. For Vulkan, it is
-    // only used for the unprotected context, because Vulkan does not allow sharing between
-    // contexts, and protected is less common.
     std::unordered_map<GraphicBufferId, std::shared_ptr<AutoBackendTexture::LocalRef>> mTextureCache
             GUARDED_BY(mRenderingMutex);
     std::unordered_map<shaders::LinearEffect, sk_sp<SkRuntimeEffect>, shaders::LinearEffectHasher>
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
index fd71332..bd50107 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -69,12 +69,38 @@
         case GraphicsApi::GL:
             return true;
         case GraphicsApi::VK: {
-            if (!sVulkanInterface.isInitialized()) {
-                sVulkanInterface.init(false /* no protected content */);
-                ALOGD("%s: initialized == %s.", __func__,
-                      sVulkanInterface.isInitialized() ? "true" : "false");
+            // Static local variables are initialized once, on first invocation of the function.
+            static const bool canSupportVulkan = []() {
+                if (!sVulkanInterface.isInitialized()) {
+                    sVulkanInterface.init(false /* no protected content */);
+                    ALOGD("%s: initialized == %s.", __func__,
+                          sVulkanInterface.isInitialized() ? "true" : "false");
+                    if (!sVulkanInterface.isInitialized()) {
+                        sVulkanInterface.teardown();
+                        return false;
+                    }
+                }
+                return true;
+            }();
+            return canSupportVulkan;
+        }
+    }
+}
+
+void RenderEngine::teardown(GraphicsApi graphicsApi) {
+    switch (graphicsApi) {
+        case GraphicsApi::GL:
+            break;
+        case GraphicsApi::VK: {
+            if (sVulkanInterface.isInitialized()) {
+                sVulkanInterface.teardown();
+                ALOGD("Tearing down the unprotected VulkanInterface.");
             }
-            return sVulkanInterface.isInitialized();
+            if (sProtectedContentVulkanInterface.isInitialized()) {
+                sProtectedContentVulkanInterface.teardown();
+                ALOGD("Tearing down the protected VulkanInterface.");
+            }
+            break;
         }
     }
 }
@@ -85,14 +111,30 @@
 
 SkiaVkRenderEngine::SkiaVkRenderEngine(const RenderEngineCreationArgs& args)
       : SkiaRenderEngine(args.threaded, static_cast<PixelFormat>(args.pixelFormat),
-                         args.supportsBackgroundBlur) {}
+                         args.blurAlgorithm) {}
 
 SkiaVkRenderEngine::~SkiaVkRenderEngine() {
-    finishRenderingAndAbandonContext();
+    finishRenderingAndAbandonContexts();
+    // Teardown VulkanInterfaces after Skia contexts have been abandoned
+    teardown(GraphicsApi::VK);
 }
 
 SkiaRenderEngine::Contexts SkiaVkRenderEngine::createContexts() {
     sSetupVulkanInterface();
+    // More work would need to be done in order to have multiple RenderEngine instances. In
+    // particular, they would not be able to share the same VulkanInterface(s).
+    LOG_ALWAYS_FATAL_IF(!sVulkanInterface.takeOwnership(),
+                        "SkiaVkRenderEngine couldn't take ownership of existing unprotected "
+                        "VulkanInterface! Only one SkiaVkRenderEngine instance may exist at a "
+                        "time.");
+    if (sProtectedContentVulkanInterface.isInitialized()) {
+        // takeOwnership fails on an uninitialized VulkanInterface, but protected content support is
+        // optional.
+        LOG_ALWAYS_FATAL_IF(!sProtectedContentVulkanInterface.takeOwnership(),
+                            "SkiaVkRenderEngine couldn't take ownership of existing protected "
+                            "VulkanInterface! Only one SkiaVkRenderEngine instance may exist at a "
+                            "time.");
+    }
 
     SkiaRenderEngine::Contexts contexts;
     contexts.first = createContext(sVulkanInterface);
diff --git a/libs/renderengine/skia/VulkanInterface.cpp b/libs/renderengine/skia/VulkanInterface.cpp
index 49b9f1e..fc16c56 100644
--- a/libs/renderengine/skia/VulkanInterface.cpp
+++ b/libs/renderengine/skia/VulkanInterface.cpp
@@ -244,7 +244,9 @@
     VK_CHECK(vkEnumerateInstanceVersion(&instanceVersion));
 
     if (instanceVersion < VK_MAKE_VERSION(1, 1, 0)) {
-        return;
+        BAIL("Vulkan instance API version %" PRIu32 ".%" PRIu32 ".%" PRIu32 " < 1.1.0",
+             VK_VERSION_MAJOR(instanceVersion), VK_VERSION_MINOR(instanceVersion),
+             VK_VERSION_PATCH(instanceVersion));
     }
 
     const VkApplicationInfo appInfo = {
@@ -323,13 +325,11 @@
     }
 
     vkGetPhysicalDeviceProperties2(physicalDevice, &physDevProps);
-    if (physDevProps.properties.apiVersion < VK_MAKE_VERSION(1, 1, 0)) {
-        BAIL("Could not find a Vulkan 1.1+ physical device");
-    }
-
-    if (physDevProps.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU) {
-        // TODO: b/326633110 - SkiaVK is not working correctly on swiftshader path.
-        BAIL("CPU implementations of Vulkan is not supported");
+    const uint32_t physicalDeviceApiVersion = physDevProps.properties.apiVersion;
+    if (physicalDeviceApiVersion < VK_MAKE_VERSION(1, 1, 0)) {
+        BAIL("Vulkan physical device API version %" PRIu32 ".%" PRIu32 ".%" PRIu32 " < 1.1.0",
+             VK_VERSION_MAJOR(physicalDeviceApiVersion), VK_VERSION_MINOR(physicalDeviceApiVersion),
+             VK_VERSION_PATCH(physicalDeviceApiVersion));
     }
 
     // Check for syncfd support. Bail if we cannot both import and export them.
@@ -544,7 +544,7 @@
     mDevice = device;
     mQueue = graphicsQueue;
     mQueueIndex = graphicsQueueIndex;
-    mApiVersion = physDevProps.properties.apiVersion;
+    mApiVersion = physicalDeviceApiVersion;
     // grExtensions already constructed
     // feature pointers already constructed
     mGrGetProc = sGetProc;
@@ -557,13 +557,16 @@
     ALOGD("%s: Success init Vulkan interface in %f ms", __func__, initTimeMs);
 }
 
-// TODO: b/293371537 - Iterate on this.
-// Currently unused, but copied over from its original location for potential future use. This
-// should likely be improved to walk the pNext chain of mPhysicalDeviceFeatures2 and free everything
-// like HWUI's VulkanManager. Also, not all fields are being reset.
-void VulkanInterface::teardown() {
-    mInitialized = false;
+bool VulkanInterface::takeOwnership() {
+    if (!isInitialized() || mIsOwned) {
+        return false;
+    }
+    mIsOwned = true;
+    return true;
+}
 
+void VulkanInterface::teardown() {
+    // Core resources that must be destroyed using Vulkan functions.
     if (mDevice != VK_NULL_HANDLE) {
         mFuncs.vkDeviceWaitIdle(mDevice);
         mFuncs.vkDestroyDevice(mDevice, nullptr);
@@ -574,26 +577,42 @@
         mInstance = VK_NULL_HANDLE;
     }
 
+    // Optional features that can be deleted directly.
+    // TODO: b/293371537 - This section should likely be improved to walk the pNext chain of
+    // mPhysicalDeviceFeatures2 and free everything like HWUI's VulkanManager.
     if (mProtectedMemoryFeatures) {
         delete mProtectedMemoryFeatures;
+        mProtectedMemoryFeatures = nullptr;
     }
-
     if (mSamplerYcbcrConversionFeatures) {
         delete mSamplerYcbcrConversionFeatures;
+        mSamplerYcbcrConversionFeatures = nullptr;
     }
-
     if (mPhysicalDeviceFeatures2) {
         delete mPhysicalDeviceFeatures2;
+        mPhysicalDeviceFeatures2 = nullptr;
     }
-
     if (mDeviceFaultFeatures) {
         delete mDeviceFaultFeatures;
+        mDeviceFaultFeatures = nullptr;
     }
 
-    mSamplerYcbcrConversionFeatures = nullptr;
-    mPhysicalDeviceFeatures2 = nullptr;
-    mProtectedMemoryFeatures = nullptr;
-    mDeviceFaultFeatures = nullptr;
+    // Misc. fields that can be trivially reset without special deletion:
+    mInitialized = false;
+    mIsOwned = false;
+    mPhysicalDevice = VK_NULL_HANDLE; // Implicitly destroyed by destroying mInstance.
+    mQueue = VK_NULL_HANDLE;          // Implicitly destroyed by destroying mDevice.
+    mQueueIndex = 0;
+    mApiVersion = 0;
+    mGrExtensions = GrVkExtensions();
+    mGrGetProc = nullptr;
+    mIsProtected = false;
+    mIsRealtimePriority = false;
+
+    mFuncs = VulkanFuncs();
+
+    mInstanceExtensionNames.clear();
+    mDeviceExtensionNames.clear();
 }
 
 } // namespace skia
diff --git a/libs/renderengine/skia/VulkanInterface.h b/libs/renderengine/skia/VulkanInterface.h
index 2824fcb..af0489a 100644
--- a/libs/renderengine/skia/VulkanInterface.h
+++ b/libs/renderengine/skia/VulkanInterface.h
@@ -41,6 +41,10 @@
     VulkanInterface& operator=(VulkanInterface&&) = delete;
 
     void init(bool protectedContent = false);
+    // Returns true and marks this VulkanInterface as "owned" if it is initialized but unused by any
+    // RenderEngine instances. Returns false if already owned, indicating that it must not be used
+    // by a new RE instance.
+    bool takeOwnership();
     void teardown();
 
     GrVkBackendContext getGaneshBackendContext();
@@ -72,7 +76,9 @@
                                 const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
                                 const std::vector<std::byte>& vendorBinaryData);
 
+    // Note: keep all field defaults in sync with teardown()
     bool mInitialized = false;
+    bool mIsOwned = false;
     VkInstance mInstance = VK_NULL_HANDLE;
     VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE;
     VkDevice mDevice = VK_NULL_HANDLE;
diff --git a/libs/renderengine/skia/compat/GaneshGpuContext.cpp b/libs/renderengine/skia/compat/GaneshGpuContext.cpp
index e3fec19..b2eae00 100644
--- a/libs/renderengine/skia/compat/GaneshGpuContext.cpp
+++ b/libs/renderengine/skia/compat/GaneshGpuContext.cpp
@@ -38,7 +38,6 @@
 namespace android::renderengine::skia {
 
 namespace {
-// TODO: b/293371537 - Graphite variant.
 static GrContextOptions ganeshOptions(GrContextOptions::PersistentCache& skSLCacheMonitor) {
     GrContextOptions options;
     options.fDisableDriverCorrectnessWorkarounds = true;
@@ -67,6 +66,11 @@
     LOG_ALWAYS_FATAL_IF(mGrContext.get() == nullptr, "GrDirectContext creation failed");
 }
 
+GaneshGpuContext::~GaneshGpuContext() {
+    mGrContext->flushAndSubmit(GrSyncCpu::kYes);
+    mGrContext->abandonContext();
+};
+
 sk_sp<GrDirectContext> GaneshGpuContext::grDirectContext() {
     return mGrContext;
 }
@@ -101,11 +105,6 @@
     mGrContext->setResourceCacheLimit(maxResourceBytes);
 }
 
-void GaneshGpuContext::finishRenderingAndAbandonContext() {
-    mGrContext->flushAndSubmit(GrSyncCpu::kYes);
-    mGrContext->abandonContext();
-};
-
 void GaneshGpuContext::purgeUnlockedScratchResources() {
     mGrContext->purgeUnlockedResources(GrPurgeResourceOptions::kScratchResourcesOnly);
 }
diff --git a/libs/renderengine/skia/compat/GaneshGpuContext.h b/libs/renderengine/skia/compat/GaneshGpuContext.h
index d815d15..aeb1a82 100644
--- a/libs/renderengine/skia/compat/GaneshGpuContext.h
+++ b/libs/renderengine/skia/compat/GaneshGpuContext.h
@@ -25,7 +25,7 @@
 class GaneshGpuContext : public SkiaGpuContext {
 public:
     GaneshGpuContext(sk_sp<GrDirectContext> grContext);
-    ~GaneshGpuContext() override = default;
+    ~GaneshGpuContext() override;
 
     sk_sp<GrDirectContext> grDirectContext() override;
 
@@ -39,7 +39,6 @@
     bool isAbandonedOrDeviceLost() override;
     void setResourceCacheLimit(size_t maxResourceBytes) override;
 
-    void finishRenderingAndAbandonContext() override;
     void purgeUnlockedScratchResources() override;
     void resetContextIfApplicable() override;
 
diff --git a/libs/renderengine/skia/compat/GraphiteGpuContext.cpp b/libs/renderengine/skia/compat/GraphiteGpuContext.cpp
index e19d66f..69f5832 100644
--- a/libs/renderengine/skia/compat/GraphiteGpuContext.cpp
+++ b/libs/renderengine/skia/compat/GraphiteGpuContext.cpp
@@ -62,6 +62,22 @@
     LOG_ALWAYS_FATAL_IF(mRecorder.get() == nullptr, "graphite::Recorder creation failed");
 }
 
+GraphiteGpuContext::~GraphiteGpuContext() {
+    // The equivalent operation would occur when destroying the graphite::Context, but calling this
+    // explicitly allows any outstanding GraphiteBackendTextures to be released, thus allowing us to
+    // assert that this GraphiteGpuContext holds the last ref to the underlying graphite::Recorder.
+    mContext->submit(skgpu::graphite::SyncToCpu::kYes);
+    // We must call the Context's and Recorder's dtors before exiting this function, so all other
+    // refs must be released by now. Note: these assertions may be unreliable in a hypothetical
+    // future world where we take advantage of Graphite's multi-threading capabilities!
+    LOG_ALWAYS_FATAL_IF(mRecorder.use_count() > 1,
+                        "Something other than GraphiteGpuContext holds a ref to the underlying "
+                        "graphite::Recorder");
+    LOG_ALWAYS_FATAL_IF(mContext.use_count() > 1,
+                        "Something other than GraphiteGpuContext holds a ref to the underlying "
+                        "graphite::Context");
+};
+
 std::shared_ptr<skgpu::graphite::Context> GraphiteGpuContext::graphiteContext() {
     return mContext;
 }
@@ -94,11 +110,6 @@
     return mContext->isDeviceLost();
 }
 
-void GraphiteGpuContext::finishRenderingAndAbandonContext() {
-    // TODO: b/293371537 - Validate that nothing else needs to be explicitly abandoned.
-    mContext->submit(skgpu::graphite::SyncToCpu::kYes);
-};
-
 void GraphiteGpuContext::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const {
     mContext->dumpMemoryStatistics(traceMemoryDump);
 }
diff --git a/libs/renderengine/skia/compat/GraphiteGpuContext.h b/libs/renderengine/skia/compat/GraphiteGpuContext.h
index 685f899..413817f 100644
--- a/libs/renderengine/skia/compat/GraphiteGpuContext.h
+++ b/libs/renderengine/skia/compat/GraphiteGpuContext.h
@@ -26,7 +26,7 @@
 class GraphiteGpuContext : public SkiaGpuContext {
 public:
     GraphiteGpuContext(std::unique_ptr<skgpu::graphite::Context> context);
-    ~GraphiteGpuContext() override = default;
+    ~GraphiteGpuContext() override;
 
     std::shared_ptr<skgpu::graphite::Context> graphiteContext() override;
     std::shared_ptr<skgpu::graphite::Recorder> graphiteRecorder() override;
@@ -45,7 +45,6 @@
     // functions yet, as its design may evolve.)
     void setResourceCacheLimit(size_t maxResourceBytes) override{};
 
-    void finishRenderingAndAbandonContext() override;
     // TODO: b/293371537 - Triple-check and validate that no cleanup is necessary when switching
     // contexts.
     // No-op (unnecessary during context switch for Graphite's client-budgeted memory model).
@@ -58,7 +57,7 @@
 private:
     DISALLOW_COPY_AND_ASSIGN(GraphiteGpuContext);
 
-    const std::shared_ptr<skgpu::graphite::Context> mContext;
+    std::shared_ptr<skgpu::graphite::Context> mContext;
     std::shared_ptr<skgpu::graphite::Recorder> mRecorder;
 };
 
diff --git a/libs/renderengine/skia/compat/SkiaGpuContext.h b/libs/renderengine/skia/compat/SkiaGpuContext.h
index a2457e5..282dfe7 100644
--- a/libs/renderengine/skia/compat/SkiaGpuContext.h
+++ b/libs/renderengine/skia/compat/SkiaGpuContext.h
@@ -36,18 +36,32 @@
 
 /**
  * Abstraction over Ganesh and Graphite's underlying context-like objects.
+ *
+ * On destruction, subclasses will submit any pending work before destroying their internal Skia
+ * context(s). Any unused cached SkiaBackendTextures created from a SkiaGpuContext that are awaiting
+ * cleanup must be deleted before destroying that SkiaGpuContext, and any textures that are released
+ * during ~SkiaGpuContext must be configured to be deleted immediately.
  */
 class SkiaGpuContext {
 public:
+    /**
+     * glInterface must remain valid until after SkiaGpuContext is destroyed.
+     */
     static std::unique_ptr<SkiaGpuContext> MakeGL_Ganesh(
             sk_sp<const GrGLInterface> glInterface,
             GrContextOptions::PersistentCache& skSLCacheMonitor);
 
+    /**
+     * grVkBackendContext must remain valid until after SkiaGpuContext is destroyed.
+     */
     static std::unique_ptr<SkiaGpuContext> MakeVulkan_Ganesh(
             const GrVkBackendContext& grVkBackendContext,
             GrContextOptions::PersistentCache& skSLCacheMonitor);
 
     // TODO: b/293371537 - Need shader / pipeline monitoring support in Graphite.
+    /**
+     * vulkanBackendContext must remain valid until after SkiaGpuContext is destroyed.
+     */
     static std::unique_ptr<SkiaGpuContext> MakeVulkan_Graphite(
             const skgpu::VulkanBackendContext& vulkanBackendContext);
 
@@ -91,7 +105,6 @@
     virtual size_t getMaxTextureSize() const = 0;
     virtual void setResourceCacheLimit(size_t maxResourceBytes) = 0;
 
-    virtual void finishRenderingAndAbandonContext() = 0;
     virtual void purgeUnlockedScratchResources() = 0;
     virtual void resetContextIfApplicable() = 0; // No-op outside of GL (&& Ganesh at this point.)
 
diff --git a/libs/renderengine/skia/debug/SkiaCapture.cpp b/libs/renderengine/skia/debug/SkiaCapture.cpp
index 48dc77e..e778884 100644
--- a/libs/renderengine/skia/debug/SkiaCapture.cpp
+++ b/libs/renderengine/skia/debug/SkiaCapture.cpp
@@ -30,7 +30,7 @@
 #include "SkCanvas.h"
 #include "SkRect.h"
 #include "SkTypeface.h"
-#include "src/utils/SkMultiPictureDocument.h"
+#include "include/docs/SkMultiPictureDocument.h"
 #include <sys/stat.h>
 
 namespace android {
@@ -196,7 +196,7 @@
         // procs doesn't need to outlive this Make call
         // The last argument is a callback for the endPage behavior.
         // See SkSharingProc.h for more explanation of this callback.
-        mMultiPic = SkMakeMultiPictureDocument(
+        mMultiPic = SkMultiPictureDocument::Make(
                 mOpenMultiPicStream.get(), &procs,
                 [sharingCtx = mSerialContext.get()](const SkPicture* pic) {
                     SkSharingSerialContext::collectNonTextureImagesFromPicture(pic, sharingCtx);
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 4bd0852..cb8b016 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -116,7 +116,7 @@
                         .setImageCacheSize(1)
                         .setEnableProtectedContext(false)
                         .setPrecacheToneMapperShaderOnly(false)
-                        .setSupportsBackgroundBlur(true)
+                        .setBlurAlgorithm(renderengine::RenderEngine::BlurAlgorithm::KAWASE)
                         .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
                         .setThreaded(renderengine::RenderEngine::Threaded::NO)
                         .setGraphicsApi(graphicsApi())
@@ -242,7 +242,7 @@
     RenderEngineTest() {
         const ::testing::TestInfo* const test_info =
                 ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+        ALOGI("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
     }
 
     ~RenderEngineTest() {
@@ -251,7 +251,7 @@
         }
         const ::testing::TestInfo* const test_info =
                 ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
+        ALOGI("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
     }
 
     void writeBufferToFile(const char* basename) {
@@ -1265,7 +1265,12 @@
 
 void RenderEngineTest::fillBufferWithPremultiplyAlpha() {
     fillRedBufferWithPremultiplyAlpha();
-    expectBufferColor(fullscreenRect(), 128, 0, 0, 128);
+    // Different backends and GPUs may round 255 * 0.5 = 127.5 differently, but
+    // either 127 or 128 are acceptable. Checking both 127 and 128 with a
+    // tolerance of 1 allows either 127 or 128 to pass, while preventing 126 or
+    // 129 from erroneously passing.
+    expectBufferColor(fullscreenRect(), 127, 0, 0, 127, 1);
+    expectBufferColor(fullscreenRect(), 128, 0, 0, 128, 1);
 }
 
 void RenderEngineTest::fillRedBufferWithoutPremultiplyAlpha() {
@@ -1687,11 +1692,6 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_outputDataspace) {
-    // TODO: b/331445583 - Fix in Graphite and re-enable.
-    if (GetParam()->skiaBackend() == renderengine::RenderEngine::SkiaBackend::GRAPHITE) {
-        GTEST_SKIP();
-    }
-
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
     if (!renderEngineFactory->apiSupported()) {
@@ -1842,11 +1842,6 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_opaqueBufferSource) {
-    // TODO: b/331447131 - Fix in Graphite and re-enable.
-    if (GetParam()->skiaBackend() == renderengine::RenderEngine::SkiaBackend::GRAPHITE) {
-        GTEST_SKIP();
-    }
-
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
     if (!renderEngineFactory->apiSupported()) {
@@ -1997,11 +1992,6 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_bufferSource) {
-    // TODO: b/331446495 - Fix in Graphite and re-enable.
-    if (GetParam()->skiaBackend() == renderengine::RenderEngine::SkiaBackend::GRAPHITE) {
-        GTEST_SKIP();
-    }
-
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
     if (!renderEngineFactory->apiSupported()) {
@@ -2061,11 +2051,6 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBuffer_premultipliesAlpha) {
-    // TODO: b/331446496 - Fix in Graphite and re-enable.
-    if (GetParam()->skiaBackend() == renderengine::RenderEngine::SkiaBackend::GRAPHITE) {
-        GTEST_SKIP();
-    }
-
     if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp
index 9cb298a..12230f9 100644
--- a/libs/ui/Android.bp
+++ b/libs/ui/Android.bp
@@ -105,9 +105,6 @@
 cc_library_shared {
     name: "libui",
     vendor_available: true,
-    vndk: {
-        enabled: true,
-    },
     double_loadable: true,
 
     cflags: [
@@ -280,13 +277,3 @@
     "tests",
     "tools",
 ]
-
-filegroup {
-    name: "libui_host_common",
-    srcs: [
-        "Rect.cpp",
-        "Region.cpp",
-        "PixelFormat.cpp",
-        "Transform.cpp",
-    ],
-}
diff --git a/libs/ui/DebugUtils.cpp b/libs/ui/DebugUtils.cpp
index 8675f14..bee58e5 100644
--- a/libs/ui/DebugUtils.cpp
+++ b/libs/ui/DebugUtils.cpp
@@ -22,14 +22,12 @@
 #include <android-base/stringprintf.h>
 #include <string>
 
-using android::base::StringAppendF;
 using android::base::StringPrintf;
 using android::ui::ColorMode;
 using android::ui::RenderIntent;
 
-std::string decodeStandard(android_dataspace dataspace) {
-    const uint32_t dataspaceSelect = (dataspace & HAL_DATASPACE_STANDARD_MASK);
-    switch (dataspaceSelect) {
+std::string decodeStandardOnly(uint32_t dataspaceStandard) {
+    switch (dataspaceStandard) {
         case HAL_DATASPACE_STANDARD_BT709:
             return std::string("BT709");
 
@@ -62,63 +60,44 @@
 
         case HAL_DATASPACE_STANDARD_ADOBE_RGB:
             return std::string("AdobeRGB");
-
-        case 0:
-            switch (dataspace & 0xffff) {
-                case HAL_DATASPACE_JFIF:
-                    return std::string("(deprecated) JFIF (BT601_625)");
-
-                case HAL_DATASPACE_BT601_625:
-                    return std::string("(deprecated) BT601_625");
-
-                case HAL_DATASPACE_BT601_525:
-                    return std::string("(deprecated) BT601_525");
-
-                case HAL_DATASPACE_SRGB_LINEAR:
-                case HAL_DATASPACE_SRGB:
-                    return std::string("(deprecated) sRGB");
-
-                case HAL_DATASPACE_BT709:
-                    return std::string("(deprecated) BT709");
-
-                case HAL_DATASPACE_ARBITRARY:
-                    return std::string("ARBITRARY");
-
-                case HAL_DATASPACE_UNKNOWN:
-                // Fallthrough
-                default:
-                    return StringPrintf("Unknown deprecated dataspace code %d", dataspace);
-            }
     }
 
-    return StringPrintf("Unknown dataspace code %d", dataspaceSelect);
+    return StringPrintf("Unknown dataspace code %d", dataspaceStandard);
 }
 
-std::string decodeTransfer(android_dataspace dataspace) {
-    const uint32_t dataspaceSelect = (dataspace & HAL_DATASPACE_STANDARD_MASK);
-    if (dataspaceSelect == 0) {
+std::string decodeStandard(android_dataspace dataspace) {
+    const uint32_t dataspaceStandard = (dataspace & HAL_DATASPACE_STANDARD_MASK);
+    if (dataspaceStandard == 0) {
         switch (dataspace & 0xffff) {
             case HAL_DATASPACE_JFIF:
+                return std::string("(deprecated) JFIF (BT601_625)");
+
             case HAL_DATASPACE_BT601_625:
+                return std::string("(deprecated) BT601_625");
+
             case HAL_DATASPACE_BT601_525:
-            case HAL_DATASPACE_BT709:
-                return std::string("SMPTE_170M");
+                return std::string("(deprecated) BT601_525");
 
             case HAL_DATASPACE_SRGB_LINEAR:
-            case HAL_DATASPACE_ARBITRARY:
-                return std::string("Linear");
-
             case HAL_DATASPACE_SRGB:
-                return std::string("sRGB");
+                return std::string("(deprecated) sRGB");
+
+            case HAL_DATASPACE_BT709:
+                return std::string("(deprecated) BT709");
+
+            case HAL_DATASPACE_ARBITRARY:
+                return std::string("ARBITRARY");
 
             case HAL_DATASPACE_UNKNOWN:
             // Fallthrough
             default:
-                return std::string("");
+                return StringPrintf("Unknown deprecated dataspace code %d", dataspace);
         }
     }
+    return decodeStandardOnly(dataspaceStandard);
+}
 
-    const uint32_t dataspaceTransfer = (dataspace & HAL_DATASPACE_TRANSFER_MASK);
+std::string decodeTransferOnly(uint32_t dataspaceTransfer) {
     switch (dataspaceTransfer) {
         case HAL_DATASPACE_TRANSFER_UNSPECIFIED:
             return std::string("Unspecified");
@@ -151,6 +130,52 @@
     return StringPrintf("Unknown dataspace transfer %d", dataspaceTransfer);
 }
 
+std::string decodeTransfer(android_dataspace dataspace) {
+    const uint32_t dataspaceSelect = (dataspace & HAL_DATASPACE_STANDARD_MASK);
+    if (dataspaceSelect == 0) {
+        switch (dataspace & 0xffff) {
+            case HAL_DATASPACE_JFIF:
+            case HAL_DATASPACE_BT601_625:
+            case HAL_DATASPACE_BT601_525:
+            case HAL_DATASPACE_BT709:
+                return std::string("SMPTE_170M");
+
+            case HAL_DATASPACE_SRGB_LINEAR:
+            case HAL_DATASPACE_ARBITRARY:
+                return std::string("Linear");
+
+            case HAL_DATASPACE_SRGB:
+                return std::string("sRGB");
+
+            case HAL_DATASPACE_UNKNOWN:
+            // Fallthrough
+            default:
+                return std::string("");
+        }
+    }
+
+    const uint32_t dataspaceTransfer = (dataspace & HAL_DATASPACE_TRANSFER_MASK);
+    return decodeTransferOnly(dataspaceTransfer);
+}
+
+std::string decodeRangeOnly(uint32_t dataspaceRange) {
+    switch (dataspaceRange) {
+        case HAL_DATASPACE_RANGE_UNSPECIFIED:
+            return std::string("Range Unspecified");
+
+        case HAL_DATASPACE_RANGE_FULL:
+            return std::string("Full range");
+
+        case HAL_DATASPACE_RANGE_LIMITED:
+            return std::string("Limited range");
+
+        case HAL_DATASPACE_RANGE_EXTENDED:
+            return std::string("Extended range");
+    }
+
+    return StringPrintf("Unknown dataspace range %d", dataspaceRange);
+}
+
 std::string decodeRange(android_dataspace dataspace) {
     const uint32_t dataspaceSelect = (dataspace & HAL_DATASPACE_STANDARD_MASK);
     if (dataspaceSelect == 0) {
@@ -174,21 +199,7 @@
     }
 
     const uint32_t dataspaceRange = (dataspace & HAL_DATASPACE_RANGE_MASK);
-    switch (dataspaceRange) {
-        case HAL_DATASPACE_RANGE_UNSPECIFIED:
-            return std::string("Range Unspecified");
-
-        case HAL_DATASPACE_RANGE_FULL:
-            return std::string("Full range");
-
-        case HAL_DATASPACE_RANGE_LIMITED:
-            return std::string("Limited range");
-
-        case HAL_DATASPACE_RANGE_EXTENDED:
-            return std::string("Extended range");
-    }
-
-    return StringPrintf("Unknown dataspace range %d", dataspaceRange);
+    return decodeRangeOnly(dataspaceRange);
 }
 
 std::string dataspaceDetails(android_dataspace dataspace) {
diff --git a/libs/ui/GraphicBufferAllocator.cpp b/libs/ui/GraphicBufferAllocator.cpp
index 98082fb..1ebe597 100644
--- a/libs/ui/GraphicBufferAllocator.cpp
+++ b/libs/ui/GraphicBufferAllocator.cpp
@@ -291,5 +291,9 @@
     return NO_ERROR;
 }
 
+bool GraphicBufferAllocator::supportsAdditionalOptions() const {
+    return mAllocator->supportsAdditionalOptions();
+}
+
 // ---------------------------------------------------------------------------
 }; // namespace android
diff --git a/libs/ui/include/ui/DebugUtils.h b/libs/ui/include/ui/DebugUtils.h
index 18cd487..7c4ac42 100644
--- a/libs/ui/include/ui/DebugUtils.h
+++ b/libs/ui/include/ui/DebugUtils.h
@@ -27,8 +27,11 @@
 }
 
 std::string decodeStandard(android_dataspace dataspace);
+std::string decodeStandardOnly(uint32_t dataspaceStandard);
 std::string decodeTransfer(android_dataspace dataspace);
+std::string decodeTransferOnly(uint32_t dataspaceTransfer);
 std::string decodeRange(android_dataspace dataspace);
+std::string decodeRangeOnly(uint32_t dataspaceRange);
 std::string dataspaceDetails(android_dataspace dataspace);
 std::string decodeColorMode(android::ui::ColorMode colormode);
 std::string decodeColorTransform(android_color_transform colorTransform);
diff --git a/libs/ui/include/ui/Gralloc.h b/libs/ui/include/ui/Gralloc.h
index e6015e0..4167dcb 100644
--- a/libs/ui/include/ui/Gralloc.h
+++ b/libs/ui/include/ui/Gralloc.h
@@ -226,6 +226,8 @@
             const GraphicBufferAllocator::AllocationRequest&) const {
         return GraphicBufferAllocator::AllocationResult(UNKNOWN_TRANSACTION);
     }
+
+    virtual bool supportsAdditionalOptions() const { return false; }
 };
 
 } // namespace android
diff --git a/libs/ui/include/ui/Gralloc5.h b/libs/ui/include/ui/Gralloc5.h
index f9e8f5e..5aa5019 100644
--- a/libs/ui/include/ui/Gralloc5.h
+++ b/libs/ui/include/ui/Gralloc5.h
@@ -178,6 +178,8 @@
     [[nodiscard]] GraphicBufferAllocator::AllocationResult allocate(
             const GraphicBufferAllocator::AllocationRequest&) const override;
 
+    bool supportsAdditionalOptions() const override { return true; }
+
 private:
     const Gralloc5Mapper &mMapper;
     std::shared_ptr<aidl::android::hardware::graphics::allocator::IAllocator> mAllocator;
diff --git a/libs/ui/include/ui/GraphicBufferAllocator.h b/libs/ui/include/ui/GraphicBufferAllocator.h
index 8f461e1..bbb2d77 100644
--- a/libs/ui/include/ui/GraphicBufferAllocator.h
+++ b/libs/ui/include/ui/GraphicBufferAllocator.h
@@ -107,6 +107,8 @@
     void dump(std::string& res, bool less = true) const;
     static void dumpToSystemLog(bool less = true);
 
+    bool supportsAdditionalOptions() const;
+
 protected:
     struct alloc_rec_t {
         uint32_t width;
diff --git a/libs/ultrahdr/OWNERS b/libs/ultrahdr/OWNERS
deleted file mode 100644
index 6ace354..0000000
--- a/libs/ultrahdr/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-arifdikici@google.com
-dichenzhang@google.com
-kyslov@google.com
\ No newline at end of file
diff --git a/libs/ultrahdr/adobe-hdr-gain-map-license/Android.bp b/libs/ultrahdr/adobe-hdr-gain-map-license/Android.bp
deleted file mode 100644
index 2fa361f..0000000
--- a/libs/ultrahdr/adobe-hdr-gain-map-license/Android.bp
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2023 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-license {
-    name: "adobe_hdr_gain_map_license-deprecated",
-    license_kinds: ["legacy_by_exception_only"],
-    license_text: ["NOTICE"],
-}
diff --git a/libs/ultrahdr/adobe-hdr-gain-map-license/NOTICE b/libs/ultrahdr/adobe-hdr-gain-map-license/NOTICE
deleted file mode 100644
index 3f6c594..0000000
--- a/libs/ultrahdr/adobe-hdr-gain-map-license/NOTICE
+++ /dev/null
@@ -1 +0,0 @@
-This product includes Gain Map technology under license by Adobe.
diff --git a/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp b/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp
deleted file mode 100644
index f1f4035..0000000
--- a/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// System include files
-#include <fuzzer/FuzzedDataProvider.h>
-#include <iostream>
-#include <vector>
-
-// User include files
-#include "ultrahdr/jpegr.h"
-
-using namespace android::ultrahdr;
-
-// Transfer functions for image data, sync with ultrahdr.h
-const int kOfMin = ULTRAHDR_OUTPUT_UNSPECIFIED + 1;
-const int kOfMax = ULTRAHDR_OUTPUT_MAX;
-
-class UltraHdrDecFuzzer {
-public:
-    UltraHdrDecFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
-    void process();
-
-private:
-    FuzzedDataProvider mFdp;
-};
-
-void UltraHdrDecFuzzer::process() {
-    // hdr_of
-    auto of = static_cast<ultrahdr_output_format>(mFdp.ConsumeIntegralInRange<int>(kOfMin, kOfMax));
-    auto buffer = mFdp.ConsumeRemainingBytes<uint8_t>();
-    jpegr_compressed_struct jpegImgR{buffer.data(), (int)buffer.size(), (int)buffer.size(),
-                                     ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-
-    std::vector<uint8_t> iccData(0);
-    std::vector<uint8_t> exifData(0);
-    jpegr_info_struct info{0, 0, &iccData, &exifData};
-    JpegR jpegHdr;
-    (void)jpegHdr.getJPEGRInfo(&jpegImgR, &info);
-//#define DUMP_PARAM
-#ifdef DUMP_PARAM
-    std::cout << "input buffer size " << jpegImgR.length << std::endl;
-    std::cout << "image dimensions " << info.width << " x " << info.width << std::endl;
-#endif
-    size_t outSize = info.width * info.height * ((of == ULTRAHDR_OUTPUT_HDR_LINEAR) ? 8 : 4);
-    jpegr_uncompressed_struct decodedJpegR;
-    auto decodedRaw = std::make_unique<uint8_t[]>(outSize);
-    decodedJpegR.data = decodedRaw.get();
-    ultrahdr_metadata_struct metadata;
-    jpegr_uncompressed_struct decodedGainMap{};
-    (void)jpegHdr.decodeJPEGR(&jpegImgR, &decodedJpegR,
-                              mFdp.ConsumeFloatingPointInRange<float>(1.0, FLT_MAX), nullptr, of,
-                              &decodedGainMap, &metadata);
-    if (decodedGainMap.data) free(decodedGainMap.data);
-}
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    UltraHdrDecFuzzer fuzzHandle(data, size);
-    fuzzHandle.process();
-    return 0;
-}
diff --git a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp
deleted file mode 100644
index 2d59e8b..0000000
--- a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// System include files
-#include <fuzzer/FuzzedDataProvider.h>
-#include <algorithm>
-#include <iostream>
-#include <random>
-#include <vector>
-
-// User include files
-#include "ultrahdr/gainmapmath.h"
-#include "ultrahdr/jpegdecoderhelper.h"
-#include "ultrahdr/jpegencoderhelper.h"
-#include "utils/Log.h"
-
-using namespace android::ultrahdr;
-
-// Color gamuts for image data, sync with ultrahdr.h
-const int kCgMin = ULTRAHDR_COLORGAMUT_UNSPECIFIED + 1;
-const int kCgMax = ULTRAHDR_COLORGAMUT_MAX;
-
-// Transfer functions for image data, sync with ultrahdr.h
-const int kTfMin = ULTRAHDR_TF_UNSPECIFIED + 1;
-const int kTfMax = ULTRAHDR_TF_PQ;
-
-// Transfer functions for image data, sync with ultrahdr.h
-const int kOfMin = ULTRAHDR_OUTPUT_UNSPECIFIED + 1;
-const int kOfMax = ULTRAHDR_OUTPUT_MAX;
-
-// quality factor
-const int kQfMin = 0;
-const int kQfMax = 100;
-
-class UltraHdrEncFuzzer {
-public:
-    UltraHdrEncFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
-    void process();
-    void fillP010Buffer(uint16_t* data, int width, int height, int stride);
-    void fill420Buffer(uint8_t* data, int width, int height, int stride);
-
-private:
-    FuzzedDataProvider mFdp;
-};
-
-void UltraHdrEncFuzzer::fillP010Buffer(uint16_t* data, int width, int height, int stride) {
-    uint16_t* tmp = data;
-    std::vector<uint16_t> buffer(16);
-    for (int i = 0; i < buffer.size(); i++) {
-        buffer[i] = (mFdp.ConsumeIntegralInRange<int>(0, (1 << 10) - 1)) << 6;
-    }
-    for (int j = 0; j < height; j++) {
-        for (int i = 0; i < width; i += buffer.size()) {
-            memcpy(tmp + i, buffer.data(),
-                   std::min((int)buffer.size(), (width - i)) * sizeof(*data));
-            std::shuffle(buffer.begin(), buffer.end(),
-                         std::default_random_engine(std::random_device{}()));
-        }
-        tmp += stride;
-    }
-}
-
-void UltraHdrEncFuzzer::fill420Buffer(uint8_t* data, int width, int height, int stride) {
-    uint8_t* tmp = data;
-    std::vector<uint8_t> buffer(16);
-    mFdp.ConsumeData(buffer.data(), buffer.size());
-    for (int j = 0; j < height; j++) {
-        for (int i = 0; i < width; i += buffer.size()) {
-            memcpy(tmp + i, buffer.data(),
-                   std::min((int)buffer.size(), (width - i)) * sizeof(*data));
-            std::shuffle(buffer.begin(), buffer.end(),
-                         std::default_random_engine(std::random_device{}()));
-        }
-        tmp += stride;
-    }
-}
-
-void UltraHdrEncFuzzer::process() {
-    while (mFdp.remaining_bytes()) {
-        struct jpegr_uncompressed_struct p010Img {};
-        struct jpegr_uncompressed_struct yuv420Img {};
-        struct jpegr_uncompressed_struct grayImg {};
-        struct jpegr_compressed_struct jpegImgR {};
-        struct jpegr_compressed_struct jpegImg {};
-        struct jpegr_compressed_struct jpegGainMap {};
-
-        // which encode api to select
-        int muxSwitch = mFdp.ConsumeIntegralInRange<int>(0, 4);
-
-        // quality factor
-        int quality = mFdp.ConsumeIntegralInRange<int>(kQfMin, kQfMax);
-
-        // hdr_tf
-        auto tf = static_cast<ultrahdr_transfer_function>(
-                mFdp.ConsumeIntegralInRange<int>(kTfMin, kTfMax));
-
-        // p010 Cg
-        auto p010Cg =
-                static_cast<ultrahdr_color_gamut>(mFdp.ConsumeIntegralInRange<int>(kCgMin, kCgMax));
-
-        // 420 Cg
-        auto yuv420Cg =
-                static_cast<ultrahdr_color_gamut>(mFdp.ConsumeIntegralInRange<int>(kCgMin, kCgMax));
-
-        // hdr_of
-        auto of = static_cast<ultrahdr_output_format>(
-                mFdp.ConsumeIntegralInRange<int>(kOfMin, kOfMax));
-
-        int width = mFdp.ConsumeIntegralInRange<int>(kMinWidth, kMaxWidth);
-        width = (width >> 1) << 1;
-
-        int height = mFdp.ConsumeIntegralInRange<int>(kMinHeight, kMaxHeight);
-        height = (height >> 1) << 1;
-
-        std::unique_ptr<uint16_t[]> bufferYHdr = nullptr;
-        std::unique_ptr<uint16_t[]> bufferUVHdr = nullptr;
-        std::unique_ptr<uint8_t[]> bufferYSdr = nullptr;
-        std::unique_ptr<uint8_t[]> bufferUVSdr = nullptr;
-        std::unique_ptr<uint8_t[]> grayImgRaw = nullptr;
-        if (muxSwitch != 4) {
-            // init p010 image
-            bool isUVContiguous = mFdp.ConsumeBool();
-            bool hasYStride = mFdp.ConsumeBool();
-            int yStride = hasYStride ? mFdp.ConsumeIntegralInRange<int>(width, width + 128) : width;
-            p010Img.width = width;
-            p010Img.height = height;
-            p010Img.colorGamut = p010Cg;
-            p010Img.luma_stride = hasYStride ? yStride : 0;
-            int bppP010 = 2;
-            if (isUVContiguous) {
-                size_t p010Size = yStride * height * 3 / 2;
-                bufferYHdr = std::make_unique<uint16_t[]>(p010Size);
-                p010Img.data = bufferYHdr.get();
-                p010Img.chroma_data = nullptr;
-                p010Img.chroma_stride = 0;
-                fillP010Buffer(bufferYHdr.get(), width, height, yStride);
-                fillP010Buffer(bufferYHdr.get() + yStride * height, width, height / 2, yStride);
-            } else {
-                int uvStride = mFdp.ConsumeIntegralInRange<int>(width, width + 128);
-                size_t p010YSize = yStride * height;
-                bufferYHdr = std::make_unique<uint16_t[]>(p010YSize);
-                p010Img.data = bufferYHdr.get();
-                fillP010Buffer(bufferYHdr.get(), width, height, yStride);
-                size_t p010UVSize = uvStride * p010Img.height / 2;
-                bufferUVHdr = std::make_unique<uint16_t[]>(p010UVSize);
-                p010Img.chroma_data = bufferUVHdr.get();
-                p010Img.chroma_stride = uvStride;
-                fillP010Buffer(bufferUVHdr.get(), width, height / 2, uvStride);
-            }
-        } else {
-            size_t map_width = width / kMapDimensionScaleFactor;
-            size_t map_height = height / kMapDimensionScaleFactor;
-            // init 400 image
-            grayImg.width = map_width;
-            grayImg.height = map_height;
-            grayImg.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-
-            const size_t graySize = map_width * map_height;
-            grayImgRaw = std::make_unique<uint8_t[]>(graySize);
-            grayImg.data = grayImgRaw.get();
-            fill420Buffer(grayImgRaw.get(), map_width, map_height, map_width);
-            grayImg.chroma_data = nullptr;
-            grayImg.luma_stride = 0;
-            grayImg.chroma_stride = 0;
-        }
-
-        if (muxSwitch > 0) {
-            // init 420 image
-            bool isUVContiguous = mFdp.ConsumeBool();
-            bool hasYStride = mFdp.ConsumeBool();
-            int yStride = hasYStride ? mFdp.ConsumeIntegralInRange<int>(width, width + 128) : width;
-            yuv420Img.width = width;
-            yuv420Img.height = height;
-            yuv420Img.colorGamut = yuv420Cg;
-            yuv420Img.luma_stride = hasYStride ? yStride : 0;
-            if (isUVContiguous) {
-                size_t yuv420Size = yStride * height * 3 / 2;
-                bufferYSdr = std::make_unique<uint8_t[]>(yuv420Size);
-                yuv420Img.data = bufferYSdr.get();
-                yuv420Img.chroma_data = nullptr;
-                yuv420Img.chroma_stride = 0;
-                fill420Buffer(bufferYSdr.get(), width, height, yStride);
-                fill420Buffer(bufferYSdr.get() + yStride * height, width / 2, height / 2,
-                              yStride / 2);
-                fill420Buffer(bufferYSdr.get() + yStride * height * 5 / 4, width / 2, height / 2,
-                              yStride / 2);
-            } else {
-                int uvStride = mFdp.ConsumeIntegralInRange<int>(width / 2, width / 2 + 128);
-                size_t yuv420YSize = yStride * height;
-                bufferYSdr = std::make_unique<uint8_t[]>(yuv420YSize);
-                yuv420Img.data = bufferYSdr.get();
-                fill420Buffer(bufferYSdr.get(), width, height, yStride);
-                size_t yuv420UVSize = uvStride * yuv420Img.height / 2 * 2;
-                bufferUVSdr = std::make_unique<uint8_t[]>(yuv420UVSize);
-                yuv420Img.chroma_data = bufferUVSdr.get();
-                yuv420Img.chroma_stride = uvStride;
-                fill420Buffer(bufferUVSdr.get(), width / 2, height / 2, uvStride);
-                fill420Buffer(bufferUVSdr.get() + uvStride * height / 2, width / 2, height / 2,
-                              uvStride);
-            }
-        }
-
-        // dest
-        // 2 * p010 size as input data is random, DCT compression might not behave as expected
-        jpegImgR.maxLength = std::max(8 * 1024 /* min size 8kb */, width * height * 3 * 2);
-        auto jpegImgRaw = std::make_unique<uint8_t[]>(jpegImgR.maxLength);
-        jpegImgR.data = jpegImgRaw.get();
-
-//#define DUMP_PARAM
-#ifdef DUMP_PARAM
-        std::cout << "Api Select " << muxSwitch << std::endl;
-        std::cout << "image dimensions " << width << " x " << height << std::endl;
-        std::cout << "p010 color gamut " << p010Img.colorGamut << std::endl;
-        std::cout << "p010 luma stride " << p010Img.luma_stride << std::endl;
-        std::cout << "p010 chroma stride " << p010Img.chroma_stride << std::endl;
-        std::cout << "420 color gamut " << yuv420Img.colorGamut << std::endl;
-        std::cout << "420 luma stride " << yuv420Img.luma_stride << std::endl;
-        std::cout << "420 chroma stride " << yuv420Img.chroma_stride << std::endl;
-        std::cout << "quality factor " << quality << std::endl;
-#endif
-
-        JpegR jpegHdr;
-        android::status_t status = android::UNKNOWN_ERROR;
-        if (muxSwitch == 0) { // api 0
-            jpegImgR.length = 0;
-            status = jpegHdr.encodeJPEGR(&p010Img, tf, &jpegImgR, quality, nullptr);
-        } else if (muxSwitch == 1) { // api 1
-            jpegImgR.length = 0;
-            status = jpegHdr.encodeJPEGR(&p010Img, &yuv420Img, tf, &jpegImgR, quality, nullptr);
-        } else {
-            // compressed img
-            JpegEncoderHelper encoder;
-            struct jpegr_uncompressed_struct yuv420ImgCopy = yuv420Img;
-            if (yuv420ImgCopy.luma_stride == 0) yuv420ImgCopy.luma_stride = yuv420Img.width;
-            if (!yuv420ImgCopy.chroma_data) {
-                uint8_t* data = reinterpret_cast<uint8_t*>(yuv420Img.data);
-                yuv420ImgCopy.chroma_data = data + yuv420Img.luma_stride * yuv420Img.height;
-                yuv420ImgCopy.chroma_stride = yuv420Img.luma_stride >> 1;
-            }
-
-            if (encoder.compressImage(reinterpret_cast<uint8_t*>(yuv420ImgCopy.data),
-                                      reinterpret_cast<uint8_t*>(yuv420ImgCopy.chroma_data),
-                                      yuv420ImgCopy.width, yuv420ImgCopy.height,
-                                      yuv420ImgCopy.luma_stride, yuv420ImgCopy.chroma_stride,
-                                      quality, nullptr, 0)) {
-                jpegImg.length = encoder.getCompressedImageSize();
-                jpegImg.maxLength = jpegImg.length;
-                jpegImg.data = encoder.getCompressedImagePtr();
-                jpegImg.colorGamut = yuv420Cg;
-
-                if (muxSwitch == 2) { // api 2
-                    jpegImgR.length = 0;
-                    status = jpegHdr.encodeJPEGR(&p010Img, &yuv420Img, &jpegImg, tf, &jpegImgR);
-                } else if (muxSwitch == 3) { // api 3
-                    jpegImgR.length = 0;
-                    status = jpegHdr.encodeJPEGR(&p010Img, &jpegImg, tf, &jpegImgR);
-                } else if (muxSwitch == 4) { // api 4
-                    jpegImgR.length = 0;
-                    JpegEncoderHelper gainMapEncoder;
-                    if (gainMapEncoder.compressImage(reinterpret_cast<uint8_t*>(grayImg.data),
-                                                     nullptr, grayImg.width, grayImg.height,
-                                                     grayImg.width, 0, quality, nullptr, 0)) {
-                        jpegGainMap.length = gainMapEncoder.getCompressedImageSize();
-                        jpegGainMap.maxLength = jpegImg.length;
-                        jpegGainMap.data = gainMapEncoder.getCompressedImagePtr();
-                        jpegGainMap.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-                        ultrahdr_metadata_struct metadata;
-                        metadata.version = kJpegrVersion;
-                        if (tf == ULTRAHDR_TF_HLG) {
-                            metadata.maxContentBoost = kHlgMaxNits / kSdrWhiteNits;
-                        } else if (tf == ULTRAHDR_TF_PQ) {
-                            metadata.maxContentBoost = kPqMaxNits / kSdrWhiteNits;
-                        } else {
-                            metadata.maxContentBoost = 1.0f;
-                        }
-                        metadata.minContentBoost = 1.0f;
-                        metadata.gamma = 1.0f;
-                        metadata.offsetSdr = 0.0f;
-                        metadata.offsetHdr = 0.0f;
-                        metadata.hdrCapacityMin = 1.0f;
-                        metadata.hdrCapacityMax = metadata.maxContentBoost;
-                        status = jpegHdr.encodeJPEGR(&jpegImg, &jpegGainMap, &metadata, &jpegImgR);
-                    }
-                }
-            }
-        }
-        if (status == android::OK) {
-            std::vector<uint8_t> iccData(0);
-            std::vector<uint8_t> exifData(0);
-            jpegr_info_struct info{0, 0, &iccData, &exifData};
-            status = jpegHdr.getJPEGRInfo(&jpegImgR, &info);
-            if (status == android::OK) {
-                size_t outSize =
-                        info.width * info.height * ((of == ULTRAHDR_OUTPUT_HDR_LINEAR) ? 8 : 4);
-                jpegr_uncompressed_struct decodedJpegR;
-                auto decodedRaw = std::make_unique<uint8_t[]>(outSize);
-                decodedJpegR.data = decodedRaw.get();
-                ultrahdr_metadata_struct metadata;
-                jpegr_uncompressed_struct decodedGainMap{};
-                status = jpegHdr.decodeJPEGR(&jpegImgR, &decodedJpegR,
-                                             mFdp.ConsumeFloatingPointInRange<float>(1.0, FLT_MAX),
-                                             nullptr, of, &decodedGainMap, &metadata);
-                if (status != android::OK) {
-                    ALOGE("encountered error during decoding %d", status);
-                }
-                if (decodedGainMap.data) free(decodedGainMap.data);
-            } else {
-                ALOGE("encountered error during get jpeg info %d", status);
-            }
-        } else {
-            ALOGE("encountered error during encoding %d", status);
-        }
-    }
-}
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    UltraHdrEncFuzzer fuzzHandle(data, size);
-    fuzzHandle.process();
-    return 0;
-}
diff --git a/libs/ultrahdr/gainmapmath.cpp b/libs/ultrahdr/gainmapmath.cpp
deleted file mode 100644
index ae9c4ca..0000000
--- a/libs/ultrahdr/gainmapmath.cpp
+++ /dev/null
@@ -1,775 +0,0 @@
-/*
- * 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 <cmath>
-#include <vector>
-#include <ultrahdr/gainmapmath.h>
-
-namespace android::ultrahdr {
-
-static const std::vector<float> kPqOETF = [] {
-    std::vector<float> result;
-    for (int idx = 0; idx < kPqOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kPqOETFNumEntries - 1);
-      result.push_back(pqOetf(value));
-    }
-    return result;
-}();
-
-static const std::vector<float> kPqInvOETF = [] {
-    std::vector<float> result;
-    for (int idx = 0; idx < kPqInvOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kPqInvOETFNumEntries - 1);
-      result.push_back(pqInvOetf(value));
-    }
-    return result;
-}();
-
-static const std::vector<float> kHlgOETF = [] {
-    std::vector<float> result;
-    for (int idx = 0; idx < kHlgOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kHlgOETFNumEntries - 1);
-      result.push_back(hlgOetf(value));
-    }
-    return result;
-}();
-
-static const std::vector<float> kHlgInvOETF = [] {
-    std::vector<float> result;
-    for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kHlgInvOETFNumEntries - 1);
-      result.push_back(hlgInvOetf(value));
-    }
-    return result;
-}();
-
-static const std::vector<float> kSrgbInvOETF = [] {
-    std::vector<float> result;
-    for (int idx = 0; idx < kSrgbInvOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kSrgbInvOETFNumEntries - 1);
-      result.push_back(srgbInvOetf(value));
-    }
-    return result;
-}();
-
-// Use Shepard's method for inverse distance weighting. For more information:
-// en.wikipedia.org/wiki/Inverse_distance_weighting#Shepard's_method
-
-float ShepardsIDW::euclideanDistance(float x1, float x2, float y1, float y2) {
-  return sqrt(((y2 - y1) * (y2 - y1)) + (x2 - x1) * (x2 - x1));
-}
-
-void ShepardsIDW::fillShepardsIDW(float *weights, int incR, int incB) {
-  for (int y = 0; y < mMapScaleFactor; y++) {
-    for (int x = 0; x < mMapScaleFactor; x++) {
-      float pos_x = ((float)x) / mMapScaleFactor;
-      float pos_y = ((float)y) / mMapScaleFactor;
-      int curr_x = floor(pos_x);
-      int curr_y = floor(pos_y);
-      int next_x = curr_x + incR;
-      int next_y = curr_y + incB;
-      float e1_distance = euclideanDistance(pos_x, curr_x, pos_y, curr_y);
-      int index = y * mMapScaleFactor * 4 + x * 4;
-      if (e1_distance == 0) {
-        weights[index++] = 1.f;
-        weights[index++] = 0.f;
-        weights[index++] = 0.f;
-        weights[index++] = 0.f;
-      } else {
-        float e1_weight = 1.f / e1_distance;
-
-        float e2_distance = euclideanDistance(pos_x, curr_x, pos_y, next_y);
-        float e2_weight = 1.f / e2_distance;
-
-        float e3_distance = euclideanDistance(pos_x, next_x, pos_y, curr_y);
-        float e3_weight = 1.f / e3_distance;
-
-        float e4_distance = euclideanDistance(pos_x, next_x, pos_y, next_y);
-        float e4_weight = 1.f / e4_distance;
-
-        float total_weight = e1_weight + e2_weight + e3_weight + e4_weight;
-
-        weights[index++] = e1_weight / total_weight;
-        weights[index++] = e2_weight / total_weight;
-        weights[index++] = e3_weight / total_weight;
-        weights[index++] = e4_weight / total_weight;
-      }
-    }
-  }
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// sRGB transformations
-
-static const float kMaxPixelFloat = 1.0f;
-static float clampPixelFloat(float value) {
-    return (value < 0.0f) ? 0.0f : (value > kMaxPixelFloat) ? kMaxPixelFloat : value;
-}
-
-// See IEC 61966-2-1/Amd 1:2003, Equation F.7.
-static const float kSrgbR = 0.2126f, kSrgbG = 0.7152f, kSrgbB = 0.0722f;
-
-float srgbLuminance(Color e) {
-  return kSrgbR * e.r + kSrgbG * e.g + kSrgbB * e.b;
-}
-
-// See ITU-R BT.709-6, Section 3.
-// Uses the same coefficients for deriving luma signal as
-// IEC 61966-2-1/Amd 1:2003 states for luminance, so we reuse the luminance
-// function above.
-static const float kSrgbCb = 1.8556f, kSrgbCr = 1.5748f;
-
-Color srgbRgbToYuv(Color e_gamma) {
-  float y_gamma = srgbLuminance(e_gamma);
-  return {{{ y_gamma,
-             (e_gamma.b - y_gamma) / kSrgbCb,
-             (e_gamma.r - y_gamma) / kSrgbCr }}};
-}
-
-// See ITU-R BT.709-6, Section 3.
-// Same derivation to BT.2100's YUV->RGB, below. Similar to srgbRgbToYuv, we
-// can reuse the luminance coefficients since they are the same.
-static const float kSrgbGCb = kSrgbB * kSrgbCb / kSrgbG;
-static const float kSrgbGCr = kSrgbR * kSrgbCr / kSrgbG;
-
-Color srgbYuvToRgb(Color e_gamma) {
-  return {{{ clampPixelFloat(e_gamma.y + kSrgbCr * e_gamma.v),
-             clampPixelFloat(e_gamma.y - kSrgbGCb * e_gamma.u - kSrgbGCr * e_gamma.v),
-             clampPixelFloat(e_gamma.y + kSrgbCb * e_gamma.u) }}};
-}
-
-// See IEC 61966-2-1/Amd 1:2003, Equations F.5 and F.6.
-float srgbInvOetf(float e_gamma) {
-  if (e_gamma <= 0.04045f) {
-    return e_gamma / 12.92f;
-  } else {
-    return pow((e_gamma + 0.055f) / 1.055f, 2.4);
-  }
-}
-
-Color srgbInvOetf(Color e_gamma) {
-  return {{{ srgbInvOetf(e_gamma.r),
-             srgbInvOetf(e_gamma.g),
-             srgbInvOetf(e_gamma.b) }}};
-}
-
-// See IEC 61966-2-1, Equations F.5 and F.6.
-float srgbInvOetfLUT(float e_gamma) {
-  uint32_t value = static_cast<uint32_t>(e_gamma * (kSrgbInvOETFNumEntries - 1) + 0.5);
-  //TODO() : Remove once conversion modules have appropriate clamping in place
-  value = CLIP3(value, 0, kSrgbInvOETFNumEntries - 1);
-  return kSrgbInvOETF[value];
-}
-
-Color srgbInvOetfLUT(Color e_gamma) {
-  return {{{ srgbInvOetfLUT(e_gamma.r),
-             srgbInvOetfLUT(e_gamma.g),
-             srgbInvOetfLUT(e_gamma.b) }}};
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Display-P3 transformations
-
-// See SMPTE EG 432-1, Equation 7-8.
-static const float kP3R = 0.20949f, kP3G = 0.72160f, kP3B = 0.06891f;
-
-float p3Luminance(Color e) {
-  return kP3R * e.r + kP3G * e.g + kP3B * e.b;
-}
-
-// See ITU-R BT.601-7, Sections 2.5.1 and 2.5.2.
-// Unfortunately, calculation of luma signal differs from calculation of
-// luminance for Display-P3, so we can't reuse p3Luminance here.
-static const float kP3YR = 0.299f, kP3YG = 0.587f, kP3YB = 0.114f;
-static const float kP3Cb = 1.772f, kP3Cr = 1.402f;
-
-Color p3RgbToYuv(Color e_gamma) {
-  float y_gamma = kP3YR * e_gamma.r + kP3YG * e_gamma.g + kP3YB * e_gamma.b;
-  return {{{ y_gamma,
-             (e_gamma.b - y_gamma) / kP3Cb,
-             (e_gamma.r - y_gamma) / kP3Cr }}};
-}
-
-// See ITU-R BT.601-7, Sections 2.5.1 and 2.5.2.
-// Same derivation to BT.2100's YUV->RGB, below. Similar to p3RgbToYuv, we must
-// use luma signal coefficients rather than the luminance coefficients.
-static const float kP3GCb = kP3YB * kP3Cb / kP3YG;
-static const float kP3GCr = kP3YR * kP3Cr / kP3YG;
-
-Color p3YuvToRgb(Color e_gamma) {
-  return {{{ clampPixelFloat(e_gamma.y + kP3Cr * e_gamma.v),
-             clampPixelFloat(e_gamma.y - kP3GCb * e_gamma.u - kP3GCr * e_gamma.v),
-             clampPixelFloat(e_gamma.y + kP3Cb * e_gamma.u) }}};
-}
-
-
-////////////////////////////////////////////////////////////////////////////////
-// BT.2100 transformations - according to ITU-R BT.2100-2
-
-// See ITU-R BT.2100-2, Table 5, HLG Reference OOTF
-static const float kBt2100R = 0.2627f, kBt2100G = 0.6780f, kBt2100B = 0.0593f;
-
-float bt2100Luminance(Color e) {
-  return kBt2100R * e.r + kBt2100G * e.g + kBt2100B * e.b;
-}
-
-// See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals.
-// BT.2100 uses the same coefficients for calculating luma signal and luminance,
-// so we reuse the luminance function here.
-static const float kBt2100Cb = 1.8814f, kBt2100Cr = 1.4746f;
-
-Color bt2100RgbToYuv(Color e_gamma) {
-  float y_gamma = bt2100Luminance(e_gamma);
-  return {{{ y_gamma,
-             (e_gamma.b - y_gamma) / kBt2100Cb,
-             (e_gamma.r - y_gamma) / kBt2100Cr }}};
-}
-
-// See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals.
-//
-// Similar to bt2100RgbToYuv above, we can reuse the luminance coefficients.
-//
-// Derived by inversing bt2100RgbToYuv. The derivation for R and B are  pretty
-// straight forward; we just invert the formulas for U and V above. But deriving
-// the formula for G is a bit more complicated:
-//
-// Start with equation for luminance:
-//   Y = kBt2100R * R + kBt2100G * G + kBt2100B * B
-// Solve for G:
-//   G = (Y - kBt2100R * R - kBt2100B * B) / kBt2100B
-// Substitute equations for R and B in terms YUV:
-//   G = (Y - kBt2100R * (Y + kBt2100Cr * V) - kBt2100B * (Y + kBt2100Cb * U)) / kBt2100B
-// Simplify:
-//   G = Y * ((1 - kBt2100R - kBt2100B) / kBt2100G)
-//     + U * (kBt2100B * kBt2100Cb / kBt2100G)
-//     + V * (kBt2100R * kBt2100Cr / kBt2100G)
-//
-// We then get the following coeficients for calculating G from YUV:
-//
-// Coef for Y = (1 - kBt2100R - kBt2100B) / kBt2100G = 1
-// Coef for U = kBt2100B * kBt2100Cb / kBt2100G = kBt2100GCb = ~0.1645
-// Coef for V = kBt2100R * kBt2100Cr / kBt2100G = kBt2100GCr = ~0.5713
-
-static const float kBt2100GCb = kBt2100B * kBt2100Cb / kBt2100G;
-static const float kBt2100GCr = kBt2100R * kBt2100Cr / kBt2100G;
-
-Color bt2100YuvToRgb(Color e_gamma) {
-  return {{{ clampPixelFloat(e_gamma.y + kBt2100Cr * e_gamma.v),
-             clampPixelFloat(e_gamma.y - kBt2100GCb * e_gamma.u - kBt2100GCr * e_gamma.v),
-             clampPixelFloat(e_gamma.y + kBt2100Cb * e_gamma.u) }}};
-}
-
-// See ITU-R BT.2100-2, Table 5, HLG Reference OETF.
-static const float kHlgA = 0.17883277f, kHlgB = 0.28466892f, kHlgC = 0.55991073;
-
-float hlgOetf(float e) {
-  if (e <= 1.0f/12.0f) {
-    return sqrt(3.0f * e);
-  } else {
-    return kHlgA * log(12.0f * e - kHlgB) + kHlgC;
-  }
-}
-
-Color hlgOetf(Color e) {
-  return {{{ hlgOetf(e.r), hlgOetf(e.g), hlgOetf(e.b) }}};
-}
-
-float hlgOetfLUT(float e) {
-  uint32_t value = static_cast<uint32_t>(e * (kHlgOETFNumEntries - 1) + 0.5);
-  //TODO() : Remove once conversion modules have appropriate clamping in place
-  value = CLIP3(value, 0, kHlgOETFNumEntries - 1);
-
-  return kHlgOETF[value];
-}
-
-Color hlgOetfLUT(Color e) {
-  return {{{ hlgOetfLUT(e.r), hlgOetfLUT(e.g), hlgOetfLUT(e.b) }}};
-}
-
-// See ITU-R BT.2100-2, Table 5, HLG Reference EOTF.
-float hlgInvOetf(float e_gamma) {
-  if (e_gamma <= 0.5f) {
-    return pow(e_gamma, 2.0f) / 3.0f;
-  } else {
-    return (exp((e_gamma - kHlgC) / kHlgA) + kHlgB) / 12.0f;
-  }
-}
-
-Color hlgInvOetf(Color e_gamma) {
-  return {{{ hlgInvOetf(e_gamma.r),
-             hlgInvOetf(e_gamma.g),
-             hlgInvOetf(e_gamma.b) }}};
-}
-
-float hlgInvOetfLUT(float e_gamma) {
-  uint32_t value = static_cast<uint32_t>(e_gamma * (kHlgInvOETFNumEntries - 1) + 0.5);
-  //TODO() : Remove once conversion modules have appropriate clamping in place
-  value = CLIP3(value, 0, kHlgInvOETFNumEntries - 1);
-
-  return kHlgInvOETF[value];
-}
-
-Color hlgInvOetfLUT(Color e_gamma) {
-  return {{{ hlgInvOetfLUT(e_gamma.r),
-             hlgInvOetfLUT(e_gamma.g),
-             hlgInvOetfLUT(e_gamma.b) }}};
-}
-
-// See ITU-R BT.2100-2, Table 4, Reference PQ OETF.
-static const float kPqM1 = 2610.0f / 16384.0f, kPqM2 = 2523.0f / 4096.0f * 128.0f;
-static const float kPqC1 = 3424.0f / 4096.0f, kPqC2 = 2413.0f / 4096.0f * 32.0f,
-                   kPqC3 = 2392.0f / 4096.0f * 32.0f;
-
-float pqOetf(float e) {
-  if (e <= 0.0f) return 0.0f;
-  return pow((kPqC1 + kPqC2 * pow(e, kPqM1)) / (1 + kPqC3 * pow(e, kPqM1)),
-             kPqM2);
-}
-
-Color pqOetf(Color e) {
-  return {{{ pqOetf(e.r), pqOetf(e.g), pqOetf(e.b) }}};
-}
-
-float pqOetfLUT(float e) {
-  uint32_t value = static_cast<uint32_t>(e * (kPqOETFNumEntries - 1) + 0.5);
-  //TODO() : Remove once conversion modules have appropriate clamping in place
-  value = CLIP3(value, 0, kPqOETFNumEntries - 1);
-
-  return kPqOETF[value];
-}
-
-Color pqOetfLUT(Color e) {
-  return {{{ pqOetfLUT(e.r), pqOetfLUT(e.g), pqOetfLUT(e.b) }}};
-}
-
-// Derived from the inverse of the Reference PQ OETF.
-static const float kPqInvA = 128.0f, kPqInvB = 107.0f, kPqInvC = 2413.0f, kPqInvD = 2392.0f,
-                   kPqInvE = 6.2773946361f, kPqInvF = 0.0126833f;
-
-float pqInvOetf(float e_gamma) {
-  // This equation blows up if e_gamma is 0.0, and checking on <= 0.0 doesn't
-  // always catch 0.0. So, check on 0.0001, since anything this small will
-  // effectively be crushed to zero anyways.
-  if (e_gamma <= 0.0001f) return 0.0f;
-  return pow((kPqInvA * pow(e_gamma, kPqInvF) - kPqInvB)
-           / (kPqInvC - kPqInvD * pow(e_gamma, kPqInvF)),
-             kPqInvE);
-}
-
-Color pqInvOetf(Color e_gamma) {
-  return {{{ pqInvOetf(e_gamma.r),
-             pqInvOetf(e_gamma.g),
-             pqInvOetf(e_gamma.b) }}};
-}
-
-float pqInvOetfLUT(float e_gamma) {
-  uint32_t value = static_cast<uint32_t>(e_gamma * (kPqInvOETFNumEntries - 1) + 0.5);
-  //TODO() : Remove once conversion modules have appropriate clamping in place
-  value = CLIP3(value, 0, kPqInvOETFNumEntries - 1);
-
-  return kPqInvOETF[value];
-}
-
-Color pqInvOetfLUT(Color e_gamma) {
-  return {{{ pqInvOetfLUT(e_gamma.r),
-             pqInvOetfLUT(e_gamma.g),
-             pqInvOetfLUT(e_gamma.b) }}};
-}
-
-
-////////////////////////////////////////////////////////////////////////////////
-// Color conversions
-
-Color bt709ToP3(Color e) {
- return {{{ 0.82254f * e.r + 0.17755f * e.g + 0.00006f * e.b,
-            0.03312f * e.r + 0.96684f * e.g + -0.00001f * e.b,
-            0.01706f * e.r + 0.07240f * e.g + 0.91049f * e.b }}};
-}
-
-Color bt709ToBt2100(Color e) {
- return {{{ 0.62740f * e.r + 0.32930f * e.g + 0.04332f * e.b,
-            0.06904f * e.r + 0.91958f * e.g + 0.01138f * e.b,
-            0.01636f * e.r + 0.08799f * e.g + 0.89555f * e.b }}};
-}
-
-Color p3ToBt709(Color e) {
- return {{{ 1.22482f * e.r + -0.22490f * e.g + -0.00007f * e.b,
-            -0.04196f * e.r + 1.04199f * e.g + 0.00001f * e.b,
-            -0.01961f * e.r + -0.07865f * e.g + 1.09831f * e.b }}};
-}
-
-Color p3ToBt2100(Color e) {
- return {{{ 0.75378f * e.r + 0.19862f * e.g + 0.04754f * e.b,
-            0.04576f * e.r + 0.94177f * e.g + 0.01250f * e.b,
-            -0.00121f * e.r + 0.01757f * e.g + 0.98359f * e.b }}};
-}
-
-Color bt2100ToBt709(Color e) {
- return {{{ 1.66045f * e.r + -0.58764f * e.g + -0.07286f * e.b,
-            -0.12445f * e.r + 1.13282f * e.g + -0.00837f * e.b,
-            -0.01811f * e.r + -0.10057f * e.g + 1.11878f * e.b }}};
-}
-
-Color bt2100ToP3(Color e) {
- return {{{ 1.34369f * e.r + -0.28223f * e.g + -0.06135f * e.b,
-            -0.06533f * e.r + 1.07580f * e.g + -0.01051f * e.b,
-            0.00283f * e.r + -0.01957f * e.g + 1.01679f * e.b
- }}};
-}
-
-// TODO: confirm we always want to convert like this before calculating
-// luminance.
-ColorTransformFn getHdrConversionFn(ultrahdr_color_gamut sdr_gamut,
-                                    ultrahdr_color_gamut hdr_gamut) {
-  switch (sdr_gamut) {
-    case ULTRAHDR_COLORGAMUT_BT709:
-      switch (hdr_gamut) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-          return identityConversion;
-        case ULTRAHDR_COLORGAMUT_P3:
-          return p3ToBt709;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-          return bt2100ToBt709;
-        case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
-          return nullptr;
-      }
-      break;
-    case ULTRAHDR_COLORGAMUT_P3:
-      switch (hdr_gamut) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-          return bt709ToP3;
-        case ULTRAHDR_COLORGAMUT_P3:
-          return identityConversion;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-          return bt2100ToP3;
-        case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
-          return nullptr;
-      }
-      break;
-    case ULTRAHDR_COLORGAMUT_BT2100:
-      switch (hdr_gamut) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-          return bt709ToBt2100;
-        case ULTRAHDR_COLORGAMUT_P3:
-          return p3ToBt2100;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-          return identityConversion;
-        case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
-          return nullptr;
-      }
-      break;
-    case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
-      return nullptr;
-  }
-}
-
-// All of these conversions are derived from the respective input YUV->RGB conversion followed by
-// the RGB->YUV for the receiving encoding. They are consistent with the RGB<->YUV functions in this
-// file, given that we uses BT.709 encoding for sRGB and BT.601 encoding for Display-P3, to match
-// DataSpace.
-
-Color yuv709To601(Color e_gamma) {
-  return {{{ 1.0f * e_gamma.y +  0.101579f * e_gamma.u +  0.196076f * e_gamma.v,
-             0.0f * e_gamma.y +  0.989854f * e_gamma.u + -0.110653f * e_gamma.v,
-             0.0f * e_gamma.y + -0.072453f * e_gamma.u +  0.983398f * e_gamma.v }}};
-}
-
-Color yuv709To2100(Color e_gamma) {
-  return {{{ 1.0f * e_gamma.y + -0.016969f * e_gamma.u +  0.096312f * e_gamma.v,
-             0.0f * e_gamma.y +  0.995306f * e_gamma.u + -0.051192f * e_gamma.v,
-             0.0f * e_gamma.y +  0.011507f * e_gamma.u +  1.002637f * e_gamma.v }}};
-}
-
-Color yuv601To709(Color e_gamma) {
-  return {{{ 1.0f * e_gamma.y + -0.118188f * e_gamma.u + -0.212685f * e_gamma.v,
-             0.0f * e_gamma.y +  1.018640f * e_gamma.u +  0.114618f * e_gamma.v,
-             0.0f * e_gamma.y +  0.075049f * e_gamma.u +  1.025327f * e_gamma.v }}};
-}
-
-Color yuv601To2100(Color e_gamma) {
-  return {{{ 1.0f * e_gamma.y + -0.128245f * e_gamma.u + -0.115879f * e_gamma.v,
-             0.0f * e_gamma.y +  1.010016f * e_gamma.u +  0.061592f * e_gamma.v,
-             0.0f * e_gamma.y +  0.086969f * e_gamma.u +  1.029350f * e_gamma.v }}};
-}
-
-Color yuv2100To709(Color e_gamma) {
-  return {{{ 1.0f * e_gamma.y +  0.018149f * e_gamma.u + -0.095132f * e_gamma.v,
-             0.0f * e_gamma.y +  1.004123f * e_gamma.u +  0.051267f * e_gamma.v,
-             0.0f * e_gamma.y + -0.011524f * e_gamma.u +  0.996782f * e_gamma.v }}};
-}
-
-Color yuv2100To601(Color e_gamma) {
-  return {{{ 1.0f * e_gamma.y +  0.117887f * e_gamma.u +  0.105521f * e_gamma.v,
-             0.0f * e_gamma.y +  0.995211f * e_gamma.u + -0.059549f * e_gamma.v,
-             0.0f * e_gamma.y + -0.084085f * e_gamma.u +  0.976518f * e_gamma.v }}};
-}
-
-void transformYuv420(jr_uncompressed_ptr image, size_t x_chroma, size_t y_chroma,
-                     ColorTransformFn fn) {
-  Color yuv1 = getYuv420Pixel(image, x_chroma * 2,     y_chroma * 2    );
-  Color yuv2 = getYuv420Pixel(image, x_chroma * 2 + 1, y_chroma * 2    );
-  Color yuv3 = getYuv420Pixel(image, x_chroma * 2,     y_chroma * 2 + 1);
-  Color yuv4 = getYuv420Pixel(image, x_chroma * 2 + 1, y_chroma * 2 + 1);
-
-  yuv1 = fn(yuv1);
-  yuv2 = fn(yuv2);
-  yuv3 = fn(yuv3);
-  yuv4 = fn(yuv4);
-
-  Color new_uv = (yuv1 + yuv2 + yuv3 + yuv4) / 4.0f;
-
-  size_t pixel_y1_idx =  x_chroma * 2      +  y_chroma * 2      * image->luma_stride;
-  size_t pixel_y2_idx = (x_chroma * 2 + 1) +  y_chroma * 2      * image->luma_stride;
-  size_t pixel_y3_idx =  x_chroma * 2      + (y_chroma * 2 + 1) * image->luma_stride;
-  size_t pixel_y4_idx = (x_chroma * 2 + 1) + (y_chroma * 2 + 1) * image->luma_stride;
-
-  uint8_t& y1_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y1_idx];
-  uint8_t& y2_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y2_idx];
-  uint8_t& y3_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y3_idx];
-  uint8_t& y4_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y4_idx];
-
-  size_t pixel_count = image->chroma_stride * image->height / 2;
-  size_t pixel_uv_idx = x_chroma + y_chroma * (image->chroma_stride);
-
-  uint8_t& u_uint = reinterpret_cast<uint8_t*>(image->chroma_data)[pixel_uv_idx];
-  uint8_t& v_uint = reinterpret_cast<uint8_t*>(image->chroma_data)[pixel_count + pixel_uv_idx];
-
-  y1_uint = static_cast<uint8_t>(CLIP3((yuv1.y * 255.0f + 0.5f), 0, 255));
-  y2_uint = static_cast<uint8_t>(CLIP3((yuv2.y * 255.0f + 0.5f), 0, 255));
-  y3_uint = static_cast<uint8_t>(CLIP3((yuv3.y * 255.0f + 0.5f), 0, 255));
-  y4_uint = static_cast<uint8_t>(CLIP3((yuv4.y * 255.0f + 0.5f), 0, 255));
-
-  u_uint = static_cast<uint8_t>(CLIP3((new_uv.u * 255.0f + 128.0f + 0.5f), 0, 255));
-  v_uint = static_cast<uint8_t>(CLIP3((new_uv.v * 255.0f + 128.0f + 0.5f), 0, 255));
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Gain map calculations
-uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata) {
-  return encodeGain(y_sdr, y_hdr, metadata,
-                    log2(metadata->minContentBoost), log2(metadata->maxContentBoost));
-}
-
-uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata,
-                   float log2MinContentBoost, float log2MaxContentBoost) {
-  float gain = 1.0f;
-  if (y_sdr > 0.0f) {
-    gain = y_hdr / y_sdr;
-  }
-
-  if (gain < metadata->minContentBoost) gain = metadata->minContentBoost;
-  if (gain > metadata->maxContentBoost) gain = metadata->maxContentBoost;
-
-  return static_cast<uint8_t>((log2(gain) - log2MinContentBoost)
-                            / (log2MaxContentBoost - log2MinContentBoost)
-                            * 255.0f);
-}
-
-Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata) {
-  float logBoost = log2(metadata->minContentBoost) * (1.0f - gain)
-                 + log2(metadata->maxContentBoost) * gain;
-  float gainFactor = exp2(logBoost);
-  return e * gainFactor;
-}
-
-Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata, float displayBoost) {
-  float logBoost = log2(metadata->minContentBoost) * (1.0f - gain)
-                 + log2(metadata->maxContentBoost) * gain;
-  float gainFactor = exp2(logBoost * displayBoost / metadata->maxContentBoost);
-  return e * gainFactor;
-}
-
-Color applyGainLUT(Color e, float gain, GainLUT& gainLUT) {
-  float gainFactor = gainLUT.getGainFactor(gain);
-  return e * gainFactor;
-}
-
-Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
-  uint8_t* luma_data = reinterpret_cast<uint8_t*>(image->data);
-  size_t luma_stride = image->luma_stride;
-  uint8_t* chroma_data = reinterpret_cast<uint8_t*>(image->chroma_data);
-  size_t chroma_stride = image->chroma_stride;
-
-  size_t offset_cr = chroma_stride * (image->height / 2);
-  size_t pixel_y_idx = x + y * luma_stride;
-  size_t pixel_chroma_idx = x / 2 + (y / 2) * chroma_stride;
-
-  uint8_t y_uint = luma_data[pixel_y_idx];
-  uint8_t u_uint = chroma_data[pixel_chroma_idx];
-  uint8_t v_uint = chroma_data[offset_cr + pixel_chroma_idx];
-
-  // 128 bias for UV given we are using jpeglib; see:
-  // https://github.com/kornelski/libjpeg/blob/master/structure.doc
-  return {{{ static_cast<float>(y_uint) / 255.0f,
-             (static_cast<float>(u_uint) - 128.0f) / 255.0f,
-             (static_cast<float>(v_uint) - 128.0f) / 255.0f }}};
-}
-
-Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
-  uint16_t* luma_data = reinterpret_cast<uint16_t*>(image->data);
-  size_t luma_stride = image->luma_stride == 0 ? image->width : image->luma_stride;
-  uint16_t* chroma_data = reinterpret_cast<uint16_t*>(image->chroma_data);
-  size_t chroma_stride = image->chroma_stride;
-
-  size_t pixel_y_idx = y * luma_stride + x;
-  size_t pixel_u_idx = (y >> 1) * chroma_stride + (x & ~0x1);
-  size_t pixel_v_idx = pixel_u_idx + 1;
-
-  uint16_t y_uint = luma_data[pixel_y_idx] >> 6;
-  uint16_t u_uint = chroma_data[pixel_u_idx] >> 6;
-  uint16_t v_uint = chroma_data[pixel_v_idx] >> 6;
-
-  // Conversions include taking narrow-range into account.
-  return {{{ (static_cast<float>(y_uint) - 64.0f) / 876.0f,
-             (static_cast<float>(u_uint) - 64.0f) / 896.0f - 0.5f,
-             (static_cast<float>(v_uint) - 64.0f) / 896.0f - 0.5f }}};
-}
-
-typedef Color (*getPixelFn)(jr_uncompressed_ptr, size_t, size_t);
-
-static Color samplePixels(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y,
-                          getPixelFn get_pixel_fn) {
-  Color e = {{{ 0.0f, 0.0f, 0.0f }}};
-  for (size_t dy = 0; dy < map_scale_factor; ++dy) {
-    for (size_t dx = 0; dx < map_scale_factor; ++dx) {
-      e += get_pixel_fn(image, x * map_scale_factor + dx, y * map_scale_factor + dy);
-    }
-  }
-
-  return e / static_cast<float>(map_scale_factor * map_scale_factor);
-}
-
-Color sampleYuv420(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y) {
-  return samplePixels(image, map_scale_factor, x, y, getYuv420Pixel);
-}
-
-Color sampleP010(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y) {
-  return samplePixels(image, map_scale_factor, x, y, getP010Pixel);
-}
-
-// TODO: do we need something more clever for filtering either the map or images
-// to generate the map?
-
-static size_t clamp(const size_t& val, const size_t& low, const size_t& high) {
-  return val < low ? low : (high < val ? high : val);
-}
-
-static float mapUintToFloat(uint8_t map_uint) {
-  return static_cast<float>(map_uint) / 255.0f;
-}
-
-static float pythDistance(float x_diff, float y_diff) {
-  return sqrt(pow(x_diff, 2.0f) + pow(y_diff, 2.0f));
-}
-
-// TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
-float sampleMap(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_t y) {
-  float x_map = static_cast<float>(x) / map_scale_factor;
-  float y_map = static_cast<float>(y) / map_scale_factor;
-
-  size_t x_lower = static_cast<size_t>(floor(x_map));
-  size_t x_upper = x_lower + 1;
-  size_t y_lower = static_cast<size_t>(floor(y_map));
-  size_t y_upper = y_lower + 1;
-
-  x_lower = clamp(x_lower, 0, map->width - 1);
-  x_upper = clamp(x_upper, 0, map->width - 1);
-  y_lower = clamp(y_lower, 0, map->height - 1);
-  y_upper = clamp(y_upper, 0, map->height - 1);
-
-  // Use Shepard's method for inverse distance weighting. For more information:
-  // en.wikipedia.org/wiki/Inverse_distance_weighting#Shepard's_method
-
-  float e1 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_lower * map->width]);
-  float e1_dist = pythDistance(x_map - static_cast<float>(x_lower),
-                               y_map - static_cast<float>(y_lower));
-  if (e1_dist == 0.0f) return e1;
-
-  float e2 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_upper * map->width]);
-  float e2_dist = pythDistance(x_map - static_cast<float>(x_lower),
-                               y_map - static_cast<float>(y_upper));
-  if (e2_dist == 0.0f) return e2;
-
-  float e3 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_lower * map->width]);
-  float e3_dist = pythDistance(x_map - static_cast<float>(x_upper),
-                               y_map - static_cast<float>(y_lower));
-  if (e3_dist == 0.0f) return e3;
-
-  float e4 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_upper * map->width]);
-  float e4_dist = pythDistance(x_map - static_cast<float>(x_upper),
-                               y_map - static_cast<float>(y_upper));
-  if (e4_dist == 0.0f) return e2;
-
-  float e1_weight = 1.0f / e1_dist;
-  float e2_weight = 1.0f / e2_dist;
-  float e3_weight = 1.0f / e3_dist;
-  float e4_weight = 1.0f / e4_dist;
-  float total_weight = e1_weight + e2_weight + e3_weight + e4_weight;
-
-  return e1 * (e1_weight / total_weight)
-       + e2 * (e2_weight / total_weight)
-       + e3 * (e3_weight / total_weight)
-       + e4 * (e4_weight / total_weight);
-}
-
-float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y,
-                ShepardsIDW& weightTables) {
-  // TODO: If map_scale_factor is guaranteed to be an integer power of 2, then optimize the
-  // following by computing log2(map_scale_factor) once and then using >> log2(map_scale_factor)
-  int x_lower = x / map_scale_factor;
-  int x_upper = x_lower + 1;
-  int y_lower = y / map_scale_factor;
-  int y_upper = y_lower + 1;
-
-  x_lower = std::min(x_lower, map->width - 1);
-  x_upper = std::min(x_upper, map->width - 1);
-  y_lower = std::min(y_lower, map->height - 1);
-  y_upper = std::min(y_upper, map->height - 1);
-
-  float e1 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_lower * map->width]);
-  float e2 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_upper * map->width]);
-  float e3 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_lower * map->width]);
-  float e4 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_upper * map->width]);
-
-  // TODO: If map_scale_factor is guaranteed to be an integer power of 2, then optimize the
-  // following by using & (map_scale_factor - 1)
-  int offset_x = x % map_scale_factor;
-  int offset_y = y % map_scale_factor;
-
-  float* weights = weightTables.mWeights;
-  if (x_lower == x_upper && y_lower == y_upper) weights = weightTables.mWeightsC;
-  else if (x_lower == x_upper) weights = weightTables.mWeightsNR;
-  else if (y_lower == y_upper) weights = weightTables.mWeightsNB;
-  weights += offset_y * map_scale_factor * 4 + offset_x * 4;
-
-  return e1 * weights[0] + e2 * weights[1] + e3 * weights[2] + e4 * weights[3];
-}
-
-uint32_t colorToRgba1010102(Color e_gamma) {
-  return (0x3ff & static_cast<uint32_t>(e_gamma.r * 1023.0f))
-       | ((0x3ff & static_cast<uint32_t>(e_gamma.g * 1023.0f)) << 10)
-       | ((0x3ff & static_cast<uint32_t>(e_gamma.b * 1023.0f)) << 20)
-       | (0x3 << 30);  // Set alpha to 1.0
-}
-
-uint64_t colorToRgbaF16(Color e_gamma) {
-  return (uint64_t) floatToHalf(e_gamma.r)
-       | (((uint64_t) floatToHalf(e_gamma.g)) << 16)
-       | (((uint64_t) floatToHalf(e_gamma.b)) << 32)
-       | (((uint64_t) floatToHalf(1.0f)) << 48);
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/icc.cpp b/libs/ultrahdr/icc.cpp
deleted file mode 100644
index e41b645..0000000
--- a/libs/ultrahdr/icc.cpp
+++ /dev/null
@@ -1,692 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef USE_BIG_ENDIAN
-#define USE_BIG_ENDIAN true
-#endif
-
-#include <ultrahdr/icc.h>
-#include <vector>
-#include <utils/Log.h>
-
-#ifndef FLT_MAX
-#define FLT_MAX 0x1.fffffep127f
-#endif
-
-namespace android::ultrahdr {
-static void Matrix3x3_apply(const Matrix3x3* m, float* x) {
-    float y0 = x[0] * m->vals[0][0] + x[1] * m->vals[0][1] + x[2] * m->vals[0][2];
-    float y1 = x[0] * m->vals[1][0] + x[1] * m->vals[1][1] + x[2] * m->vals[1][2];
-    float y2 = x[0] * m->vals[2][0] + x[1] * m->vals[2][1] + x[2] * m->vals[2][2];
-    x[0] = y0;
-    x[1] = y1;
-    x[2] = y2;
-}
-
-bool Matrix3x3_invert(const Matrix3x3* src, Matrix3x3* dst) {
-    double a00 = src->vals[0][0],
-           a01 = src->vals[1][0],
-           a02 = src->vals[2][0],
-           a10 = src->vals[0][1],
-           a11 = src->vals[1][1],
-           a12 = src->vals[2][1],
-           a20 = src->vals[0][2],
-           a21 = src->vals[1][2],
-           a22 = src->vals[2][2];
-
-    double b0 = a00*a11 - a01*a10,
-           b1 = a00*a12 - a02*a10,
-           b2 = a01*a12 - a02*a11,
-           b3 = a20,
-           b4 = a21,
-           b5 = a22;
-
-    double determinant = b0*b5
-                       - b1*b4
-                       + b2*b3;
-
-    if (determinant == 0) {
-        return false;
-    }
-
-    double invdet = 1.0 / determinant;
-    if (invdet > +FLT_MAX || invdet < -FLT_MAX || !isfinitef_((float)invdet)) {
-        return false;
-    }
-
-    b0 *= invdet;
-    b1 *= invdet;
-    b2 *= invdet;
-    b3 *= invdet;
-    b4 *= invdet;
-    b5 *= invdet;
-
-    dst->vals[0][0] = (float)( a11*b5 - a12*b4 );
-    dst->vals[1][0] = (float)( a02*b4 - a01*b5 );
-    dst->vals[2][0] = (float)(        +     b2 );
-    dst->vals[0][1] = (float)( a12*b3 - a10*b5 );
-    dst->vals[1][1] = (float)( a00*b5 - a02*b3 );
-    dst->vals[2][1] = (float)(        -     b1 );
-    dst->vals[0][2] = (float)( a10*b4 - a11*b3 );
-    dst->vals[1][2] = (float)( a01*b3 - a00*b4 );
-    dst->vals[2][2] = (float)(        +     b0 );
-
-    for (int r = 0; r < 3; ++r)
-    for (int c = 0; c < 3; ++c) {
-        if (!isfinitef_(dst->vals[r][c])) {
-            return false;
-        }
-    }
-    return true;
-}
-
-static Matrix3x3 Matrix3x3_concat(const Matrix3x3* A, const Matrix3x3* B) {
-    Matrix3x3 m = { { { 0,0,0 },{ 0,0,0 },{ 0,0,0 } } };
-    for (int r = 0; r < 3; r++)
-        for (int c = 0; c < 3; c++) {
-            m.vals[r][c] = A->vals[r][0] * B->vals[0][c]
-                         + A->vals[r][1] * B->vals[1][c]
-                         + A->vals[r][2] * B->vals[2][c];
-        }
-    return m;
-}
-
-static void float_XYZD50_to_grid16_lab(const float* xyz_float, uint8_t* grid16_lab) {
-    float v[3] = {
-            xyz_float[0] / kD50_x,
-            xyz_float[1] / kD50_y,
-            xyz_float[2] / kD50_z,
-    };
-    for (size_t i = 0; i < 3; ++i) {
-        v[i] = v[i] > 0.008856f ? cbrtf(v[i]) : v[i] * 7.787f + (16 / 116.0f);
-    }
-    const float L = v[1] * 116.0f - 16.0f;
-    const float a = (v[0] - v[1]) * 500.0f;
-    const float b = (v[1] - v[2]) * 200.0f;
-    const float Lab_unorm[3] = {
-            L * (1 / 100.f),
-            (a + 128.0f) * (1 / 255.0f),
-            (b + 128.0f) * (1 / 255.0f),
-    };
-    // This will encode L=1 as 0xFFFF. This matches how skcms will interpret the
-    // table, but the spec appears to indicate that the value should be 0xFF00.
-    // https://crbug.com/skia/13807
-    for (size_t i = 0; i < 3; ++i) {
-        reinterpret_cast<uint16_t*>(grid16_lab)[i] =
-                Endian_SwapBE16(float_round_to_unorm16(Lab_unorm[i]));
-    }
-}
-
-std::string IccHelper::get_desc_string(const ultrahdr_transfer_function tf,
-                                       const ultrahdr_color_gamut gamut) {
-    std::string result;
-    switch (gamut) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-            result += "sRGB";
-            break;
-        case ULTRAHDR_COLORGAMUT_P3:
-            result += "Display P3";
-            break;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-            result += "Rec2020";
-            break;
-        default:
-            result += "Unknown";
-            break;
-    }
-    result += " Gamut with ";
-    switch (tf) {
-        case ULTRAHDR_TF_SRGB:
-            result += "sRGB";
-            break;
-        case ULTRAHDR_TF_LINEAR:
-            result += "Linear";
-            break;
-        case ULTRAHDR_TF_PQ:
-            result += "PQ";
-            break;
-        case ULTRAHDR_TF_HLG:
-            result += "HLG";
-            break;
-        default:
-            result += "Unknown";
-            break;
-    }
-    result += " Transfer";
-    return result;
-}
-
-sp<DataStruct> IccHelper::write_text_tag(const char* text) {
-    uint32_t text_length = strlen(text);
-    uint32_t header[] = {
-            Endian_SwapBE32(kTAG_TextType),                         // Type signature
-            0,                                                      // Reserved
-            Endian_SwapBE32(1),                                     // Number of records
-            Endian_SwapBE32(12),                                    // Record size (must be 12)
-            Endian_SwapBE32(SetFourByteTag('e', 'n', 'U', 'S')),    // English USA
-            Endian_SwapBE32(2 * text_length),                       // Length of string in bytes
-            Endian_SwapBE32(28),                                    // Offset of string
-    };
-
-    uint32_t total_length = text_length * 2 + sizeof(header);
-    total_length = (((total_length + 2) >> 2) << 2);  // 4 aligned
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
-
-    if (!dataStruct->write(header, sizeof(header))) {
-        ALOGE("write_text_tag(): error in writing data");
-        return dataStruct;
-    }
-
-    for (size_t i = 0; i < text_length; i++) {
-        // Convert ASCII to big-endian UTF-16.
-        dataStruct->write8(0);
-        dataStruct->write8(text[i]);
-    }
-
-    return dataStruct;
-}
-
-sp<DataStruct> IccHelper::write_xyz_tag(float x, float y, float z) {
-    uint32_t data[] = {
-            Endian_SwapBE32(kXYZ_PCSSpace),
-            0,
-            static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(x))),
-            static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(y))),
-            static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(z))),
-    };
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(sizeof(data));
-    dataStruct->write(&data, sizeof(data));
-    return dataStruct;
-}
-
-sp<DataStruct> IccHelper::write_trc_tag(const int table_entries, const void* table_16) {
-    int total_length = 4 + 4 + 4 + table_entries * 2;
-    total_length = (((total_length + 2) >> 2) << 2);  // 4 aligned
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
-    dataStruct->write32(Endian_SwapBE32(kTAG_CurveType));     // Type
-    dataStruct->write32(0);                                   // Reserved
-    dataStruct->write32(Endian_SwapBE32(table_entries));      // Value count
-    for (size_t i = 0; i < table_entries; ++i) {
-        uint16_t value = reinterpret_cast<const uint16_t*>(table_16)[i];
-        dataStruct->write16(value);
-    }
-    return dataStruct;
-}
-
-sp<DataStruct> IccHelper::write_trc_tag(const TransferFunction& fn) {
-    if (fn.a == 1.f && fn.b == 0.f && fn.c == 0.f
-            && fn.d == 0.f && fn.e == 0.f && fn.f == 0.f) {
-        int total_length = 16;
-        sp<DataStruct> dataStruct = new DataStruct(total_length);
-        dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType));  // Type
-        dataStruct->write32(0);                                    // Reserved
-        dataStruct->write32(Endian_SwapBE16(kExponential_ParaCurveType));
-        dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.g)));
-        return dataStruct;
-    }
-
-    int total_length = 40;
-    sp<DataStruct> dataStruct = new DataStruct(total_length);
-    dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType));  // Type
-    dataStruct->write32(0);                                    // Reserved
-    dataStruct->write32(Endian_SwapBE16(kGABCDEF_ParaCurveType));
-    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.g)));
-    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.a)));
-    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.b)));
-    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.c)));
-    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.d)));
-    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.e)));
-    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.f)));
-    return dataStruct;
-}
-
-float IccHelper::compute_tone_map_gain(const ultrahdr_transfer_function tf, float L) {
-    if (L <= 0.f) {
-        return 1.f;
-    }
-    if (tf == ULTRAHDR_TF_PQ) {
-        // The PQ transfer function will map to the range [0, 1]. Linearly scale
-        // it up to the range [0, 10,000/203]. We will then tone map that back
-        // down to [0, 1].
-        constexpr float kInputMaxLuminance = 10000 / 203.f;
-        constexpr float kOutputMaxLuminance = 1.0;
-        L *= kInputMaxLuminance;
-
-        // Compute the tone map gain which will tone map from 10,000/203 to 1.0.
-        constexpr float kToneMapA = kOutputMaxLuminance / (kInputMaxLuminance * kInputMaxLuminance);
-        constexpr float kToneMapB = 1.f / kOutputMaxLuminance;
-        return kInputMaxLuminance * (1.f + kToneMapA * L) / (1.f + kToneMapB * L);
-    }
-    if (tf == ULTRAHDR_TF_HLG) {
-        // Let Lw be the brightness of the display in nits.
-        constexpr float Lw = 203.f;
-        const float gamma = 1.2f + 0.42f * std::log(Lw / 1000.f) / std::log(10.f);
-        return std::pow(L, gamma - 1.f);
-    }
-    return 1.f;
-}
-
-sp<DataStruct> IccHelper::write_cicp_tag(uint32_t color_primaries,
-                                         uint32_t transfer_characteristics) {
-    int total_length = 12;  // 4 + 4 + 1 + 1 + 1 + 1
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
-    dataStruct->write32(Endian_SwapBE32(kTAG_cicp));    // Type signature
-    dataStruct->write32(0);                             // Reserved
-    dataStruct->write8(color_primaries);                // Color primaries
-    dataStruct->write8(transfer_characteristics);       // Transfer characteristics
-    dataStruct->write8(0);                              // RGB matrix
-    dataStruct->write8(1);                              // Full range
-    return dataStruct;
-}
-
-void IccHelper::compute_lut_entry(const Matrix3x3& src_to_XYZD50, float rgb[3]) {
-    // Compute the matrices to convert from source to Rec2020, and from Rec2020 to XYZD50.
-    Matrix3x3 src_to_rec2020;
-    const Matrix3x3 rec2020_to_XYZD50 = kRec2020;
-    {
-        Matrix3x3 XYZD50_to_rec2020;
-        Matrix3x3_invert(&rec2020_to_XYZD50, &XYZD50_to_rec2020);
-        src_to_rec2020 = Matrix3x3_concat(&XYZD50_to_rec2020, &src_to_XYZD50);
-    }
-
-    // Convert the source signal to linear.
-    for (size_t i = 0; i < kNumChannels; ++i) {
-        rgb[i] = pqOetf(rgb[i]);
-    }
-
-    // Convert source gamut to Rec2020.
-    Matrix3x3_apply(&src_to_rec2020, rgb);
-
-    // Compute the luminance of the signal.
-    float L = bt2100Luminance({{{rgb[0], rgb[1], rgb[2]}}});
-
-    // Compute the tone map gain based on the luminance.
-    float tone_map_gain = compute_tone_map_gain(ULTRAHDR_TF_PQ, L);
-
-    // Apply the tone map gain.
-    for (size_t i = 0; i < kNumChannels; ++i) {
-        rgb[i] *= tone_map_gain;
-    }
-
-    // Convert from Rec2020-linear to XYZD50.
-    Matrix3x3_apply(&rec2020_to_XYZD50, rgb);
-}
-
-sp<DataStruct> IccHelper::write_clut(const uint8_t* grid_points, const uint8_t* grid_16) {
-    uint32_t value_count = kNumChannels;
-    for (uint32_t i = 0; i < kNumChannels; ++i) {
-        value_count *= grid_points[i];
-    }
-
-    int total_length = 20 + 2 * value_count;
-    total_length = (((total_length + 2) >> 2) << 2);  // 4 aligned
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
-
-    for (size_t i = 0; i < 16; ++i) {
-        dataStruct->write8(i < kNumChannels ? grid_points[i] : 0);  // Grid size
-    }
-    dataStruct->write8(2);  // Grid byte width (always 16-bit)
-    dataStruct->write8(0);  // Reserved
-    dataStruct->write8(0);  // Reserved
-    dataStruct->write8(0);  // Reserved
-
-    for (uint32_t i = 0; i < value_count; ++i) {
-        uint16_t value = reinterpret_cast<const uint16_t*>(grid_16)[i];
-        dataStruct->write16(value);
-    }
-
-    return dataStruct;
-}
-
-sp<DataStruct> IccHelper::write_mAB_or_mBA_tag(uint32_t type,
-                                               bool has_a_curves,
-                                               const uint8_t* grid_points,
-                                               const uint8_t* grid_16) {
-    const size_t b_curves_offset = 32;
-    sp<DataStruct> b_curves_data[kNumChannels];
-    sp<DataStruct> a_curves_data[kNumChannels];
-    size_t clut_offset = 0;
-    sp<DataStruct> clut;
-    size_t a_curves_offset = 0;
-
-    // The "B" curve is required.
-    for (size_t i = 0; i < kNumChannels; ++i) {
-        b_curves_data[i] = write_trc_tag(kLinear_TransFun);
-    }
-
-    // The "A" curve and CLUT are optional.
-    if (has_a_curves) {
-        clut_offset = b_curves_offset;
-        for (size_t i = 0; i < kNumChannels; ++i) {
-            clut_offset += b_curves_data[i]->getLength();
-        }
-        clut = write_clut(grid_points, grid_16);
-
-        a_curves_offset = clut_offset + clut->getLength();
-        for (size_t i = 0; i < kNumChannels; ++i) {
-            a_curves_data[i] = write_trc_tag(kLinear_TransFun);
-        }
-    }
-
-    int total_length = b_curves_offset;
-    for (size_t i = 0; i < kNumChannels; ++i) {
-        total_length += b_curves_data[i]->getLength();
-    }
-    if (has_a_curves) {
-        total_length += clut->getLength();
-        for (size_t i = 0; i < kNumChannels; ++i) {
-            total_length += a_curves_data[i]->getLength();
-        }
-    }
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
-    dataStruct->write32(Endian_SwapBE32(type));             // Type signature
-    dataStruct->write32(0);                                 // Reserved
-    dataStruct->write8(kNumChannels);                       // Input channels
-    dataStruct->write8(kNumChannels);                       // Output channels
-    dataStruct->write16(0);                                 // Reserved
-    dataStruct->write32(Endian_SwapBE32(b_curves_offset));  // B curve offset
-    dataStruct->write32(Endian_SwapBE32(0));                // Matrix offset (ignored)
-    dataStruct->write32(Endian_SwapBE32(0));                // M curve offset (ignored)
-    dataStruct->write32(Endian_SwapBE32(clut_offset));      // CLUT offset
-    dataStruct->write32(Endian_SwapBE32(a_curves_offset));  // A curve offset
-    for (size_t i = 0; i < kNumChannels; ++i) {
-        if (dataStruct->write(b_curves_data[i]->getData(), b_curves_data[i]->getLength())) {
-            return dataStruct;
-        }
-    }
-    if (has_a_curves) {
-        dataStruct->write(clut->getData(), clut->getLength());
-        for (size_t i = 0; i < kNumChannels; ++i) {
-            dataStruct->write(a_curves_data[i]->getData(), a_curves_data[i]->getLength());
-        }
-    }
-    return dataStruct;
-}
-
-sp<DataStruct> IccHelper::writeIccProfile(ultrahdr_transfer_function tf,
-                                          ultrahdr_color_gamut gamut) {
-    ICCHeader header;
-
-    std::vector<std::pair<uint32_t, sp<DataStruct>>> tags;
-
-    // Compute profile description tag
-    std::string desc = get_desc_string(tf, gamut);
-
-    tags.emplace_back(kTAG_desc, write_text_tag(desc.c_str()));
-
-    Matrix3x3 toXYZD50;
-    switch (gamut) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-            toXYZD50 = kSRGB;
-            break;
-        case ULTRAHDR_COLORGAMUT_P3:
-            toXYZD50 = kDisplayP3;
-            break;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-            toXYZD50 = kRec2020;
-            break;
-        default:
-            // Should not fall here.
-            return nullptr;
-    }
-
-    // Compute primaries.
-    {
-        tags.emplace_back(kTAG_rXYZ,
-                write_xyz_tag(toXYZD50.vals[0][0], toXYZD50.vals[1][0], toXYZD50.vals[2][0]));
-        tags.emplace_back(kTAG_gXYZ,
-                write_xyz_tag(toXYZD50.vals[0][1], toXYZD50.vals[1][1], toXYZD50.vals[2][1]));
-        tags.emplace_back(kTAG_bXYZ,
-                write_xyz_tag(toXYZD50.vals[0][2], toXYZD50.vals[1][2], toXYZD50.vals[2][2]));
-    }
-
-    // Compute white point tag (must be D50)
-    tags.emplace_back(kTAG_wtpt, write_xyz_tag(kD50_x, kD50_y, kD50_z));
-
-    // Compute transfer curves.
-    if (tf != ULTRAHDR_TF_PQ) {
-        if (tf == ULTRAHDR_TF_HLG) {
-            std::vector<uint8_t> trc_table;
-            trc_table.resize(kTrcTableSize * 2);
-            for (uint32_t i = 0; i < kTrcTableSize; ++i) {
-                float x = i / (kTrcTableSize - 1.f);
-                float y = hlgOetf(x);
-                y *= compute_tone_map_gain(tf, y);
-                float_to_table16(y, &trc_table[2 * i]);
-            }
-
-            tags.emplace_back(kTAG_rTRC,
-                    write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data())));
-            tags.emplace_back(kTAG_gTRC,
-                    write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data())));
-            tags.emplace_back(kTAG_bTRC,
-                    write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data())));
-        } else {
-            tags.emplace_back(kTAG_rTRC, write_trc_tag(kSRGB_TransFun));
-            tags.emplace_back(kTAG_gTRC, write_trc_tag(kSRGB_TransFun));
-            tags.emplace_back(kTAG_bTRC, write_trc_tag(kSRGB_TransFun));
-        }
-    }
-
-    // Compute CICP.
-    if (tf == ULTRAHDR_TF_HLG || tf == ULTRAHDR_TF_PQ) {
-        // The CICP tag is present in ICC 4.4, so update the header's version.
-        header.version = Endian_SwapBE32(0x04400000);
-
-        uint32_t color_primaries = 0;
-        if (gamut == ULTRAHDR_COLORGAMUT_BT709) {
-            color_primaries = kCICPPrimariesSRGB;
-        } else if (gamut == ULTRAHDR_COLORGAMUT_P3) {
-            color_primaries = kCICPPrimariesP3;
-        }
-
-        uint32_t transfer_characteristics = 0;
-        if (tf == ULTRAHDR_TF_SRGB) {
-            transfer_characteristics = kCICPTrfnSRGB;
-        } else if (tf == ULTRAHDR_TF_LINEAR) {
-            transfer_characteristics = kCICPTrfnLinear;
-        } else if (tf == ULTRAHDR_TF_PQ) {
-            transfer_characteristics = kCICPTrfnPQ;
-        } else if (tf == ULTRAHDR_TF_HLG) {
-            transfer_characteristics = kCICPTrfnHLG;
-        }
-        tags.emplace_back(kTAG_cicp, write_cicp_tag(color_primaries, transfer_characteristics));
-    }
-
-    // Compute A2B0.
-    if (tf == ULTRAHDR_TF_PQ) {
-        std::vector<uint8_t> a2b_grid;
-        a2b_grid.resize(kGridSize * kGridSize * kGridSize * kNumChannels * 2);
-        size_t a2b_grid_index = 0;
-        for (uint32_t r_index = 0; r_index < kGridSize; ++r_index) {
-            for (uint32_t g_index = 0; g_index < kGridSize; ++g_index) {
-                for (uint32_t b_index = 0; b_index < kGridSize; ++b_index) {
-                    float rgb[3] = {
-                            r_index / (kGridSize - 1.f),
-                            g_index / (kGridSize - 1.f),
-                            b_index / (kGridSize - 1.f),
-                    };
-                    compute_lut_entry(toXYZD50, rgb);
-                    float_XYZD50_to_grid16_lab(rgb, &a2b_grid[a2b_grid_index]);
-                    a2b_grid_index += 6;
-                }
-            }
-        }
-        const uint8_t* grid_16 = reinterpret_cast<const uint8_t*>(a2b_grid.data());
-
-        uint8_t grid_points[kNumChannels];
-        for (size_t i = 0; i < kNumChannels; ++i) {
-            grid_points[i] = kGridSize;
-        }
-
-        auto a2b_data = write_mAB_or_mBA_tag(kTAG_mABType,
-                                             /* has_a_curves */ true,
-                                             grid_points,
-                                             grid_16);
-        tags.emplace_back(kTAG_A2B0, std::move(a2b_data));
-    }
-
-    // Compute B2A0.
-    if (tf == ULTRAHDR_TF_PQ) {
-        auto b2a_data = write_mAB_or_mBA_tag(kTAG_mBAType,
-                                             /* has_a_curves */ false,
-                                             /* grid_points */ nullptr,
-                                             /* grid_16 */ nullptr);
-        tags.emplace_back(kTAG_B2A0, std::move(b2a_data));
-    }
-
-    // Compute copyright tag
-    tags.emplace_back(kTAG_cprt, write_text_tag("Google Inc. 2022"));
-
-    // Compute the size of the profile.
-    size_t tag_data_size = 0;
-    for (const auto& tag : tags) {
-        tag_data_size += tag.second->getLength();
-    }
-    size_t tag_table_size = kICCTagTableEntrySize * tags.size();
-    size_t profile_size = kICCHeaderSize + tag_table_size + tag_data_size;
-
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(profile_size + kICCIdentifierSize);
-
-    // Write identifier, chunk count, and chunk ID
-    if (!dataStruct->write(kICCIdentifier, sizeof(kICCIdentifier)) ||
-        !dataStruct->write8(1) || !dataStruct->write8(1)) {
-        ALOGE("writeIccProfile(): error in identifier");
-        return dataStruct;
-    }
-
-    // Write the header.
-    header.data_color_space = Endian_SwapBE32(Signature_RGB);
-    header.pcs = Endian_SwapBE32(tf == ULTRAHDR_TF_PQ ? Signature_Lab : Signature_XYZ);
-    header.size = Endian_SwapBE32(profile_size);
-    header.tag_count = Endian_SwapBE32(tags.size());
-
-    if (!dataStruct->write(&header, sizeof(header))) {
-        ALOGE("writeIccProfile(): error in header");
-        return dataStruct;
-    }
-
-    // Write the tag table. Track the offset and size of the previous tag to
-    // compute each tag's offset. An empty SkData indicates that the previous
-    // tag is to be reused.
-    uint32_t last_tag_offset = sizeof(header) + tag_table_size;
-    uint32_t last_tag_size = 0;
-    for (const auto& tag : tags) {
-        last_tag_offset = last_tag_offset + last_tag_size;
-        last_tag_size = tag.second->getLength();
-        uint32_t tag_table_entry[3] = {
-                Endian_SwapBE32(tag.first),
-                Endian_SwapBE32(last_tag_offset),
-                Endian_SwapBE32(last_tag_size),
-        };
-        if (!dataStruct->write(tag_table_entry, sizeof(tag_table_entry))) {
-            ALOGE("writeIccProfile(): error in writing tag table");
-            return dataStruct;
-        }
-    }
-
-    // Write the tags.
-    for (const auto& tag : tags) {
-        if (!dataStruct->write(tag.second->getData(), tag.second->getLength())) {
-            ALOGE("writeIccProfile(): error in writing tags");
-            return dataStruct;
-        }
-    }
-
-    return dataStruct;
-}
-
-bool IccHelper::tagsEqualToMatrix(const Matrix3x3& matrix,
-                                  const uint8_t* red_tag,
-                                  const uint8_t* green_tag,
-                                  const uint8_t* blue_tag) {
-    sp<DataStruct> red_tag_test = write_xyz_tag(matrix.vals[0][0], matrix.vals[1][0],
-                                                matrix.vals[2][0]);
-    sp<DataStruct> green_tag_test = write_xyz_tag(matrix.vals[0][1], matrix.vals[1][1],
-                                                  matrix.vals[2][1]);
-    sp<DataStruct> blue_tag_test = write_xyz_tag(matrix.vals[0][2], matrix.vals[1][2],
-                                                 matrix.vals[2][2]);
-    return memcmp(red_tag, red_tag_test->getData(), kColorantTagSize) == 0 &&
-           memcmp(green_tag, green_tag_test->getData(), kColorantTagSize) == 0 &&
-           memcmp(blue_tag, blue_tag_test->getData(), kColorantTagSize) == 0;
-}
-
-ultrahdr_color_gamut IccHelper::readIccColorGamut(void* icc_data, size_t icc_size) {
-    // Each tag table entry consists of 3 fields of 4 bytes each.
-    static const size_t kTagTableEntrySize = 12;
-
-    if (icc_data == nullptr || icc_size < sizeof(ICCHeader) + kICCIdentifierSize) {
-        return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    }
-
-    if (memcmp(icc_data, kICCIdentifier, sizeof(kICCIdentifier)) != 0) {
-        return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    }
-
-    uint8_t* icc_bytes = reinterpret_cast<uint8_t*>(icc_data) + kICCIdentifierSize;
-
-    ICCHeader* header = reinterpret_cast<ICCHeader*>(icc_bytes);
-
-    // Use 0 to indicate not found, since offsets are always relative to start
-    // of ICC data and therefore a tag offset of zero would never be valid.
-    size_t red_primary_offset = 0, green_primary_offset = 0, blue_primary_offset = 0;
-    size_t red_primary_size = 0, green_primary_size = 0, blue_primary_size = 0;
-    for (size_t tag_idx = 0; tag_idx < Endian_SwapBE32(header->tag_count); ++tag_idx) {
-        uint32_t* tag_entry_start = reinterpret_cast<uint32_t*>(
-            icc_bytes + sizeof(ICCHeader) + tag_idx * kTagTableEntrySize);
-        // first 4 bytes are the tag signature, next 4 bytes are the tag offset,
-        // last 4 bytes are the tag length in bytes.
-        if (red_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_rXYZ)) {
-            red_primary_offset = Endian_SwapBE32(*(tag_entry_start+1));
-            red_primary_size = Endian_SwapBE32(*(tag_entry_start+2));
-        } else if (green_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_gXYZ)) {
-            green_primary_offset = Endian_SwapBE32(*(tag_entry_start+1));
-            green_primary_size = Endian_SwapBE32(*(tag_entry_start+2));
-        } else if (blue_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_bXYZ)) {
-            blue_primary_offset = Endian_SwapBE32(*(tag_entry_start+1));
-            blue_primary_size = Endian_SwapBE32(*(tag_entry_start+2));
-        }
-    }
-
-    if (red_primary_offset == 0 || red_primary_size != kColorantTagSize ||
-        kICCIdentifierSize + red_primary_offset + red_primary_size > icc_size ||
-        green_primary_offset == 0 || green_primary_size != kColorantTagSize ||
-        kICCIdentifierSize + green_primary_offset + green_primary_size > icc_size ||
-        blue_primary_offset == 0 || blue_primary_size != kColorantTagSize ||
-        kICCIdentifierSize + blue_primary_offset + blue_primary_size > icc_size) {
-        return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    }
-
-    uint8_t* red_tag = icc_bytes + red_primary_offset;
-    uint8_t* green_tag = icc_bytes + green_primary_offset;
-    uint8_t* blue_tag = icc_bytes + blue_primary_offset;
-
-    // Serialize tags as we do on encode and compare what we find to that to
-    // determine the gamut (since we don't have a need yet for full deserialize).
-    if (tagsEqualToMatrix(kSRGB, red_tag, green_tag, blue_tag)) {
-        return ULTRAHDR_COLORGAMUT_BT709;
-    } else if (tagsEqualToMatrix(kDisplayP3, red_tag, green_tag, blue_tag)) {
-        return ULTRAHDR_COLORGAMUT_P3;
-    } else if (tagsEqualToMatrix(kRec2020, red_tag, green_tag, blue_tag)) {
-        return ULTRAHDR_COLORGAMUT_BT2100;
-    }
-
-    // Didn't find a match to one of the profiles we write; indicate the gamut
-    // is unspecified since we don't understand it.
-    return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/include/ultrahdr/gainmapmath.h b/libs/ultrahdr/include/ultrahdr/gainmapmath.h
deleted file mode 100644
index 9f1238f..0000000
--- a/libs/ultrahdr/include/ultrahdr/gainmapmath.h
+++ /dev/null
@@ -1,505 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef ANDROID_ULTRAHDR_RECOVERYMAPMATH_H
-#define ANDROID_ULTRAHDR_RECOVERYMAPMATH_H
-
-#include <cmath>
-#include <stdint.h>
-
-#include <ultrahdr/jpegr.h>
-
-namespace android::ultrahdr {
-
-#define CLIP3(x, min, max) ((x) < (min)) ? (min) : ((x) > (max)) ? (max) : (x)
-
-////////////////////////////////////////////////////////////////////////////////
-// Framework
-
-const float kSdrWhiteNits = 100.0f;
-const float kHlgMaxNits = 1000.0f;
-const float kPqMaxNits = 10000.0f;
-
-struct Color {
-  union {
-    struct {
-      float r;
-      float g;
-      float b;
-    };
-    struct {
-      float y;
-      float u;
-      float v;
-    };
-  };
-};
-
-typedef Color (*ColorTransformFn)(Color);
-typedef float (*ColorCalculationFn)(Color);
-
-// A transfer function mapping encoded values to linear values,
-// represented by this 7-parameter piecewise function:
-//
-//   linear = sign(encoded) *  (c*|encoded| + f)       , 0 <= |encoded| < d
-//          = sign(encoded) * ((a*|encoded| + b)^g + e), d <= |encoded|
-//
-// (A simple gamma transfer function sets g to gamma and a to 1.)
-typedef struct TransferFunction {
-    float g, a,b,c,d,e,f;
-} TransferFunction;
-
-static constexpr TransferFunction kSRGB_TransFun =
-    { 2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0.0f, 0.0f };
-
-static constexpr TransferFunction kLinear_TransFun =
-    { 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
-
-inline Color operator+=(Color& lhs, const Color& rhs) {
-  lhs.r += rhs.r;
-  lhs.g += rhs.g;
-  lhs.b += rhs.b;
-  return lhs;
-}
-inline Color operator-=(Color& lhs, const Color& rhs) {
-  lhs.r -= rhs.r;
-  lhs.g -= rhs.g;
-  lhs.b -= rhs.b;
-  return lhs;
-}
-
-inline Color operator+(const Color& lhs, const Color& rhs) {
-  Color temp = lhs;
-  return temp += rhs;
-}
-inline Color operator-(const Color& lhs, const Color& rhs) {
-  Color temp = lhs;
-  return temp -= rhs;
-}
-
-inline Color operator+=(Color& lhs, const float rhs) {
-  lhs.r += rhs;
-  lhs.g += rhs;
-  lhs.b += rhs;
-  return lhs;
-}
-inline Color operator-=(Color& lhs, const float rhs) {
-  lhs.r -= rhs;
-  lhs.g -= rhs;
-  lhs.b -= rhs;
-  return lhs;
-}
-inline Color operator*=(Color& lhs, const float rhs) {
-  lhs.r *= rhs;
-  lhs.g *= rhs;
-  lhs.b *= rhs;
-  return lhs;
-}
-inline Color operator/=(Color& lhs, const float rhs) {
-  lhs.r /= rhs;
-  lhs.g /= rhs;
-  lhs.b /= rhs;
-  return lhs;
-}
-
-inline Color operator+(const Color& lhs, const float rhs) {
-  Color temp = lhs;
-  return temp += rhs;
-}
-inline Color operator-(const Color& lhs, const float rhs) {
-  Color temp = lhs;
-  return temp -= rhs;
-}
-inline Color operator*(const Color& lhs, const float rhs) {
-  Color temp = lhs;
-  return temp *= rhs;
-}
-inline Color operator/(const Color& lhs, const float rhs) {
-  Color temp = lhs;
-  return temp /= rhs;
-}
-
-inline uint16_t floatToHalf(float f) {
-  // round-to-nearest-even: add last bit after truncated mantissa
-  const uint32_t b = *((uint32_t*)&f) + 0x00001000;
-
-  const uint32_t e = (b & 0x7F800000) >> 23; // exponent
-  const uint32_t m = b & 0x007FFFFF; // mantissa
-
-  // sign : normalized : denormalized : saturate
-  return (b & 0x80000000) >> 16
-            | (e > 112) * ((((e - 112) << 10) & 0x7C00) | m >> 13)
-            | ((e < 113) & (e > 101)) * ((((0x007FF000 + m) >> (125 - e)) + 1) >> 1)
-            | (e > 143) * 0x7FFF;
-}
-
-constexpr size_t kGainFactorPrecision = 10;
-constexpr size_t kGainFactorNumEntries = 1 << kGainFactorPrecision;
-struct GainLUT {
-  GainLUT(ultrahdr_metadata_ptr metadata) {
-    for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
-      float logBoost = log2(metadata->minContentBoost) * (1.0f - value)
-                     + log2(metadata->maxContentBoost) * value;
-      mGainTable[idx] = exp2(logBoost);
-    }
-  }
-
-  GainLUT(ultrahdr_metadata_ptr metadata, float displayBoost) {
-    float boostFactor = displayBoost > 0 ? displayBoost / metadata->maxContentBoost : 1.0f;
-    for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
-      float logBoost = log2(metadata->minContentBoost) * (1.0f - value)
-                     + log2(metadata->maxContentBoost) * value;
-      mGainTable[idx] = exp2(logBoost * boostFactor);
-    }
-  }
-
-  ~GainLUT() {
-  }
-
-  float getGainFactor(float gain) {
-    uint32_t idx = static_cast<uint32_t>(gain * (kGainFactorNumEntries - 1) + 0.5);
-    //TODO() : Remove once conversion modules have appropriate clamping in place
-    idx = CLIP3(idx, 0, kGainFactorNumEntries - 1);
-    return mGainTable[idx];
-  }
-
-private:
-  float mGainTable[kGainFactorNumEntries];
-};
-
-struct ShepardsIDW {
-  ShepardsIDW(int mapScaleFactor) : mMapScaleFactor{mapScaleFactor} {
-    const int size = mMapScaleFactor * mMapScaleFactor * 4;
-    mWeights = new float[size];
-    mWeightsNR = new float[size];
-    mWeightsNB = new float[size];
-    mWeightsC = new float[size];
-    fillShepardsIDW(mWeights, 1, 1);
-    fillShepardsIDW(mWeightsNR, 0, 1);
-    fillShepardsIDW(mWeightsNB, 1, 0);
-    fillShepardsIDW(mWeightsC, 0, 0);
-  }
-  ~ShepardsIDW() {
-    delete[] mWeights;
-    delete[] mWeightsNR;
-    delete[] mWeightsNB;
-    delete[] mWeightsC;
-  }
-
-  int mMapScaleFactor;
-  // Image :-
-  // p00 p01 p02 p03 p04 p05 p06 p07
-  // p10 p11 p12 p13 p14 p15 p16 p17
-  // p20 p21 p22 p23 p24 p25 p26 p27
-  // p30 p31 p32 p33 p34 p35 p36 p37
-  // p40 p41 p42 p43 p44 p45 p46 p47
-  // p50 p51 p52 p53 p54 p55 p56 p57
-  // p60 p61 p62 p63 p64 p65 p66 p67
-  // p70 p71 p72 p73 p74 p75 p76 p77
-
-  // Gain Map (for 4 scale factor) :-
-  // m00 p01
-  // m10 m11
-
-  // Gain sample of curr 4x4, right 4x4, bottom 4x4, bottom right 4x4 are used during
-  // reconstruction. hence table weight size is 4.
-  float* mWeights;
-  // TODO: check if its ok to mWeights at places
-  float* mWeightsNR;  // no right
-  float* mWeightsNB;  // no bottom
-  float* mWeightsC;  // no right & bottom
-
-  float euclideanDistance(float x1, float x2, float y1, float y2);
-  void fillShepardsIDW(float *weights, int incR, int incB);
-};
-
-////////////////////////////////////////////////////////////////////////////////
-// sRGB transformations
-// NOTE: sRGB has the same color primaries as BT.709, but different transfer
-// function. For this reason, all sRGB transformations here apply to BT.709,
-// except for those concerning transfer functions.
-
-/*
- * Calculate the luminance of a linear RGB sRGB pixel, according to
- * IEC 61966-2-1/Amd 1:2003.
- *
- * [0.0, 1.0] range in and out.
- */
-float srgbLuminance(Color e);
-
-/*
- * Convert from OETF'd srgb RGB to YUV, according to ITU-R BT.709-6.
- *
- * BT.709 YUV<->RGB matrix is used to match expectations for DataSpace.
- */
-Color srgbRgbToYuv(Color e_gamma);
-
-
-/*
- * Convert from OETF'd srgb YUV to RGB, according to ITU-R BT.709-6.
- *
- * BT.709 YUV<->RGB matrix is used to match expectations for DataSpace.
- */
-Color srgbYuvToRgb(Color e_gamma);
-
-/*
- * Convert from srgb to linear, according to IEC 61966-2-1/Amd 1:2003.
- *
- * [0.0, 1.0] range in and out.
- */
-float srgbInvOetf(float e_gamma);
-Color srgbInvOetf(Color e_gamma);
-float srgbInvOetfLUT(float e_gamma);
-Color srgbInvOetfLUT(Color e_gamma);
-
-constexpr size_t kSrgbInvOETFPrecision = 10;
-constexpr size_t kSrgbInvOETFNumEntries = 1 << kSrgbInvOETFPrecision;
-
-////////////////////////////////////////////////////////////////////////////////
-// Display-P3 transformations
-
-/*
- * Calculated the luminance of a linear RGB P3 pixel, according to SMPTE EG 432-1.
- *
- * [0.0, 1.0] range in and out.
- */
-float p3Luminance(Color e);
-
-/*
- * Convert from OETF'd P3 RGB to YUV, according to ITU-R BT.601-7.
- *
- * BT.601 YUV<->RGB matrix is used to match expectations for DataSpace.
- */
-Color p3RgbToYuv(Color e_gamma);
-
-/*
- * Convert from OETF'd P3 YUV to RGB, according to ITU-R BT.601-7.
- *
- * BT.601 YUV<->RGB matrix is used to match expectations for DataSpace.
- */
-Color p3YuvToRgb(Color e_gamma);
-
-
-////////////////////////////////////////////////////////////////////////////////
-// BT.2100 transformations - according to ITU-R BT.2100-2
-
-/*
- * Calculate the luminance of a linear RGB BT.2100 pixel.
- *
- * [0.0, 1.0] range in and out.
- */
-float bt2100Luminance(Color e);
-
-/*
- * Convert from OETF'd BT.2100 RGB to YUV, according to ITU-R BT.2100-2.
- *
- * BT.2100 YUV<->RGB matrix is used to match expectations for DataSpace.
- */
-Color bt2100RgbToYuv(Color e_gamma);
-
-/*
- * Convert from OETF'd BT.2100 YUV to RGB, according to ITU-R BT.2100-2.
- *
- * BT.2100 YUV<->RGB matrix is used to match expectations for DataSpace.
- */
-Color bt2100YuvToRgb(Color e_gamma);
-
-/*
- * Convert from scene luminance to HLG.
- *
- * [0.0, 1.0] range in and out.
- */
-float hlgOetf(float e);
-Color hlgOetf(Color e);
-float hlgOetfLUT(float e);
-Color hlgOetfLUT(Color e);
-
-constexpr size_t kHlgOETFPrecision = 16;
-constexpr size_t kHlgOETFNumEntries = 1 << kHlgOETFPrecision;
-
-/*
- * Convert from HLG to scene luminance.
- *
- * [0.0, 1.0] range in and out.
- */
-float hlgInvOetf(float e_gamma);
-Color hlgInvOetf(Color e_gamma);
-float hlgInvOetfLUT(float e_gamma);
-Color hlgInvOetfLUT(Color e_gamma);
-
-constexpr size_t kHlgInvOETFPrecision = 12;
-constexpr size_t kHlgInvOETFNumEntries = 1 << kHlgInvOETFPrecision;
-
-/*
- * Convert from scene luminance to PQ.
- *
- * [0.0, 1.0] range in and out.
- */
-float pqOetf(float e);
-Color pqOetf(Color e);
-float pqOetfLUT(float e);
-Color pqOetfLUT(Color e);
-
-constexpr size_t kPqOETFPrecision = 16;
-constexpr size_t kPqOETFNumEntries = 1 << kPqOETFPrecision;
-
-/*
- * Convert from PQ to scene luminance in nits.
- *
- * [0.0, 1.0] range in and out.
- */
-float pqInvOetf(float e_gamma);
-Color pqInvOetf(Color e_gamma);
-float pqInvOetfLUT(float e_gamma);
-Color pqInvOetfLUT(Color e_gamma);
-
-constexpr size_t kPqInvOETFPrecision = 12;
-constexpr size_t kPqInvOETFNumEntries = 1 << kPqInvOETFPrecision;
-
-
-////////////////////////////////////////////////////////////////////////////////
-// Color space conversions
-
-/*
- * Convert between color spaces with linear RGB data, according to ITU-R BT.2407 and EG 432-1.
- *
- * All conversions are derived from multiplying the matrix for XYZ to output RGB color gamut by the
- * matrix for input RGB color gamut to XYZ. The matrix for converting from XYZ to an RGB gamut is
- * always the inverse of the RGB gamut to XYZ matrix.
- */
-Color bt709ToP3(Color e);
-Color bt709ToBt2100(Color e);
-Color p3ToBt709(Color e);
-Color p3ToBt2100(Color e);
-Color bt2100ToBt709(Color e);
-Color bt2100ToP3(Color e);
-
-/*
- * Identity conversion.
- */
-inline Color identityConversion(Color e) { return e; }
-
-/*
- * Get the conversion to apply to the HDR image for gain map generation
- */
-ColorTransformFn getHdrConversionFn(ultrahdr_color_gamut sdr_gamut, ultrahdr_color_gamut hdr_gamut);
-
-/*
- * Convert between YUV encodings, according to ITU-R BT.709-6, ITU-R BT.601-7, and ITU-R BT.2100-2.
- *
- * Bt.709 and Bt.2100 have well-defined YUV encodings; Display-P3's is less well defined, but is
- * treated as Bt.601 by DataSpace, hence we do the same.
- */
-Color yuv709To601(Color e_gamma);
-Color yuv709To2100(Color e_gamma);
-Color yuv601To709(Color e_gamma);
-Color yuv601To2100(Color e_gamma);
-Color yuv2100To709(Color e_gamma);
-Color yuv2100To601(Color e_gamma);
-
-/*
- * Performs a transformation at the chroma x and y coordinates provided on a YUV420 image.
- *
- * Apply the transformation by determining transformed YUV for each of the 4 Y + 1 UV; each Y gets
- * this result, and UV gets the averaged result.
- *
- * x_chroma and y_chroma should be less than or equal to half the image's width and height
- * respecitively, since input is 4:2:0 subsampled.
- */
-void transformYuv420(jr_uncompressed_ptr image, size_t x_chroma, size_t y_chroma,
-                     ColorTransformFn fn);
-
-
-////////////////////////////////////////////////////////////////////////////////
-// Gain map calculations
-
-/*
- * Calculate the 8-bit unsigned integer gain value for the given SDR and HDR
- * luminances in linear space, and the hdr ratio to encode against.
- *
- * Note: since this library always uses gamma of 1.0, offsetSdr of 0.0, and
- * offsetHdr of 0.0, this function doesn't handle different metadata values for
- * these fields.
- */
-uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata);
-uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata,
-                   float log2MinContentBoost, float log2MaxContentBoost);
-
-/*
- * Calculates the linear luminance in nits after applying the given gain
- * value, with the given hdr ratio, to the given sdr input in the range [0, 1].
- *
- * Note: similar to encodeGain(), this function only supports gamma 1.0,
- * offsetSdr 0.0, offsetHdr 0.0, hdrCapacityMin 1.0, and hdrCapacityMax equal to
- * gainMapMax, as this library encodes.
- */
-Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata);
-Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata, float displayBoost);
-Color applyGainLUT(Color e, float gain, GainLUT& gainLUT);
-
-/*
- * Helper for sampling from YUV 420 images.
- */
-Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y);
-
-/*
- * Helper for sampling from P010 images.
- *
- * Expect narrow-range image data for P010.
- */
-Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y);
-
-/*
- * Sample the image at the provided location, with a weighting based on nearby
- * pixels and the map scale factor.
- */
-Color sampleYuv420(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
-
-/*
- * Sample the image at the provided location, with a weighting based on nearby
- * pixels and the map scale factor.
- *
- * Expect narrow-range image data for P010.
- */
-Color sampleP010(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
-
-/*
- * Sample the gain value for the map from a given x,y coordinate on a scale
- * that is map scale factor larger than the map size.
- */
-float sampleMap(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_t y);
-float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y,
-                ShepardsIDW& weightTables);
-
-/*
- * Convert from Color to RGBA1010102.
- *
- * Alpha always set to 1.0.
- */
-uint32_t colorToRgba1010102(Color e_gamma);
-
-/*
- * Convert from Color to F16.
- *
- * Alpha always set to 1.0.
- */
-uint64_t colorToRgbaF16(Color e_gamma);
-
-} // namespace android::ultrahdr
-
-#endif // ANDROID_ULTRAHDR_RECOVERYMAPMATH_H
diff --git a/libs/ultrahdr/include/ultrahdr/icc.h b/libs/ultrahdr/include/ultrahdr/icc.h
deleted file mode 100644
index 971b267..0000000
--- a/libs/ultrahdr/include/ultrahdr/icc.h
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef ANDROID_ULTRAHDR_ICC_H
-#define ANDROID_ULTRAHDR_ICC_H
-
-#include <ultrahdr/gainmapmath.h>
-#include <ultrahdr/jpegr.h>
-#include <ultrahdr/jpegrutils.h>
-#include <utils/RefBase.h>
-#include <cmath>
-#include <string>
-
-#ifdef USE_BIG_ENDIAN
-#undef USE_BIG_ENDIAN
-#define USE_BIG_ENDIAN true
-#endif
-
-namespace android::ultrahdr {
-
-typedef int32_t              Fixed;
-#define Fixed1               (1 << 16)
-#define MaxS32FitsInFloat    2147483520
-#define MinS32FitsInFloat    (-MaxS32FitsInFloat)
-#define FixedToFloat(x)      ((x) * 1.52587890625e-5f)
-
-typedef struct Matrix3x3 {
-    float vals[3][3];
-} Matrix3x3;
-
-// The D50 illuminant.
-constexpr float kD50_x = 0.9642f;
-constexpr float kD50_y = 1.0000f;
-constexpr float kD50_z = 0.8249f;
-
-enum {
-    // data_color_space
-    Signature_CMYK = 0x434D594B,
-    Signature_Gray = 0x47524159,
-    Signature_RGB  = 0x52474220,
-
-    // pcs
-    Signature_Lab  = 0x4C616220,
-    Signature_XYZ  = 0x58595A20,
-};
-
-typedef uint32_t FourByteTag;
-static inline constexpr FourByteTag SetFourByteTag(char a, char b, char c, char d) {
-    return (((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)c << 8) | (uint32_t)d);
-}
-
-static constexpr char kICCIdentifier[] = "ICC_PROFILE";
-// 12 for the actual identifier, +2 for the chunk count and chunk index which
-// will always follow.
-static constexpr size_t kICCIdentifierSize = 14;
-
-// This is equal to the header size according to the ICC specification (128)
-// plus the size of the tag count (4).  We include the tag count since we
-// always require it to be present anyway.
-static constexpr size_t kICCHeaderSize = 132;
-
-// Contains a signature (4), offset (4), and size (4).
-static constexpr size_t kICCTagTableEntrySize = 12;
-
-// size should be 20; 4 bytes for type descriptor, 4 bytes reserved, 12
-// bytes for a single XYZ number type (4 bytes per coordinate).
-static constexpr size_t kColorantTagSize = 20;
-
-static constexpr uint32_t kDisplay_Profile    = SetFourByteTag('m', 'n', 't', 'r');
-static constexpr uint32_t kRGB_ColorSpace     = SetFourByteTag('R', 'G', 'B', ' ');
-static constexpr uint32_t kXYZ_PCSSpace       = SetFourByteTag('X', 'Y', 'Z', ' ');
-static constexpr uint32_t kACSP_Signature     = SetFourByteTag('a', 'c', 's', 'p');
-
-static constexpr uint32_t kTAG_desc           = SetFourByteTag('d', 'e', 's', 'c');
-static constexpr uint32_t kTAG_TextType       = SetFourByteTag('m', 'l', 'u', 'c');
-static constexpr uint32_t kTAG_rXYZ           = SetFourByteTag('r', 'X', 'Y', 'Z');
-static constexpr uint32_t kTAG_gXYZ           = SetFourByteTag('g', 'X', 'Y', 'Z');
-static constexpr uint32_t kTAG_bXYZ           = SetFourByteTag('b', 'X', 'Y', 'Z');
-static constexpr uint32_t kTAG_wtpt           = SetFourByteTag('w', 't', 'p', 't');
-static constexpr uint32_t kTAG_rTRC           = SetFourByteTag('r', 'T', 'R', 'C');
-static constexpr uint32_t kTAG_gTRC           = SetFourByteTag('g', 'T', 'R', 'C');
-static constexpr uint32_t kTAG_bTRC           = SetFourByteTag('b', 'T', 'R', 'C');
-static constexpr uint32_t kTAG_cicp           = SetFourByteTag('c', 'i', 'c', 'p');
-static constexpr uint32_t kTAG_cprt           = SetFourByteTag('c', 'p', 'r', 't');
-static constexpr uint32_t kTAG_A2B0           = SetFourByteTag('A', '2', 'B', '0');
-static constexpr uint32_t kTAG_B2A0           = SetFourByteTag('B', '2', 'A', '0');
-
-static constexpr uint32_t kTAG_CurveType      = SetFourByteTag('c', 'u', 'r', 'v');
-static constexpr uint32_t kTAG_mABType        = SetFourByteTag('m', 'A', 'B', ' ');
-static constexpr uint32_t kTAG_mBAType        = SetFourByteTag('m', 'B', 'A', ' ');
-static constexpr uint32_t kTAG_ParaCurveType  = SetFourByteTag('p', 'a', 'r', 'a');
-
-
-static constexpr Matrix3x3 kSRGB = {{
-    // ICC fixed-point (16.16) representation, taken from skcms. Please keep them exactly in sync.
-    // 0.436065674f, 0.385147095f, 0.143066406f,
-    // 0.222488403f, 0.716873169f, 0.060607910f,
-    // 0.013916016f, 0.097076416f, 0.714096069f,
-    { FixedToFloat(0x6FA2), FixedToFloat(0x6299), FixedToFloat(0x24A0) },
-    { FixedToFloat(0x38F5), FixedToFloat(0xB785), FixedToFloat(0x0F84) },
-    { FixedToFloat(0x0390), FixedToFloat(0x18DA), FixedToFloat(0xB6CF) },
-}};
-
-static constexpr Matrix3x3 kDisplayP3 = {{
-    {  0.515102f,   0.291965f,  0.157153f  },
-    {  0.241182f,   0.692236f,  0.0665819f },
-    { -0.00104941f, 0.0418818f, 0.784378f  },
-}};
-
-static constexpr Matrix3x3 kRec2020 = {{
-    {  0.673459f,   0.165661f,  0.125100f  },
-    {  0.279033f,   0.675338f,  0.0456288f },
-    { -0.00193139f, 0.0299794f, 0.797162f  },
-}};
-
-static constexpr uint32_t kCICPPrimariesSRGB = 1;
-static constexpr uint32_t kCICPPrimariesP3 = 12;
-static constexpr uint32_t kCICPPrimariesRec2020 = 9;
-
-static constexpr uint32_t kCICPTrfnSRGB = 1;
-static constexpr uint32_t kCICPTrfnLinear = 8;
-static constexpr uint32_t kCICPTrfnPQ = 16;
-static constexpr uint32_t kCICPTrfnHLG = 18;
-
-enum ParaCurveType {
-    kExponential_ParaCurveType = 0,
-    kGAB_ParaCurveType         = 1,
-    kGABC_ParaCurveType        = 2,
-    kGABDE_ParaCurveType       = 3,
-    kGABCDEF_ParaCurveType     = 4,
-};
-
-/**
- *  Return the closest int for the given float. Returns MaxS32FitsInFloat for NaN.
- */
-static inline int float_saturate2int(float x) {
-    x = x < MaxS32FitsInFloat ? x : MaxS32FitsInFloat;
-    x = x > MinS32FitsInFloat ? x : MinS32FitsInFloat;
-    return (int)x;
-}
-
-static Fixed float_round_to_fixed(float x) {
-    return float_saturate2int((float)floor((double)x * Fixed1 + 0.5));
-}
-
-static uint16_t float_round_to_unorm16(float x) {
-    x = x * 65535.f + 0.5;
-    if (x > 65535) return 65535;
-    if (x < 0) return 0;
-    return static_cast<uint16_t>(x);
-}
-
-static void float_to_table16(const float f, uint8_t* table_16) {
-    *reinterpret_cast<uint16_t*>(table_16) = Endian_SwapBE16(float_round_to_unorm16(f));
-}
-
-static bool isfinitef_(float x) { return 0 == x*0; }
-
-struct ICCHeader {
-    // Size of the profile (computed)
-    uint32_t size;
-    // Preferred CMM type (ignored)
-    uint32_t cmm_type = 0;
-    // Version 4.3 or 4.4 if CICP is included.
-    uint32_t version = Endian_SwapBE32(0x04300000);
-    // Display device profile
-    uint32_t profile_class = Endian_SwapBE32(kDisplay_Profile);
-    // RGB input color space;
-    uint32_t data_color_space = Endian_SwapBE32(kRGB_ColorSpace);
-    // Profile connection space.
-    uint32_t pcs = Endian_SwapBE32(kXYZ_PCSSpace);
-    // Date and time (ignored)
-    uint8_t creation_date_time[12] = {0};
-    // Profile signature
-    uint32_t signature = Endian_SwapBE32(kACSP_Signature);
-    // Platform target (ignored)
-    uint32_t platform = 0;
-    // Flags: not embedded, can be used independently
-    uint32_t flags = 0x00000000;
-    // Device manufacturer (ignored)
-    uint32_t device_manufacturer = 0;
-    // Device model (ignored)
-    uint32_t device_model = 0;
-    // Device attributes (ignored)
-    uint8_t device_attributes[8] = {0};
-    // Relative colorimetric rendering intent
-    uint32_t rendering_intent = Endian_SwapBE32(1);
-    // D50 standard illuminant (X, Y, Z)
-    uint32_t illuminant_X = Endian_SwapBE32(float_round_to_fixed(kD50_x));
-    uint32_t illuminant_Y = Endian_SwapBE32(float_round_to_fixed(kD50_y));
-    uint32_t illuminant_Z = Endian_SwapBE32(float_round_to_fixed(kD50_z));
-    // Profile creator (ignored)
-    uint32_t creator = 0;
-    // Profile id checksum (ignored)
-    uint8_t profile_id[16] = {0};
-    // Reserved (ignored)
-    uint8_t reserved[28] = {0};
-    // Technically not part of header, but required
-    uint32_t tag_count = 0;
-};
-
-class IccHelper {
-private:
-    static constexpr uint32_t kTrcTableSize = 65;
-    static constexpr uint32_t kGridSize = 17;
-    static constexpr size_t kNumChannels = 3;
-
-    static sp<DataStruct> write_text_tag(const char* text);
-    static std::string get_desc_string(const ultrahdr_transfer_function tf,
-                                       const ultrahdr_color_gamut gamut);
-    static sp<DataStruct> write_xyz_tag(float x, float y, float z);
-    static sp<DataStruct> write_trc_tag(const int table_entries, const void* table_16);
-    static sp<DataStruct> write_trc_tag(const TransferFunction& fn);
-    static float compute_tone_map_gain(const ultrahdr_transfer_function tf, float L);
-    static sp<DataStruct> write_cicp_tag(uint32_t color_primaries,
-                                         uint32_t transfer_characteristics);
-    static sp<DataStruct> write_mAB_or_mBA_tag(uint32_t type,
-                                               bool has_a_curves,
-                                               const uint8_t* grid_points,
-                                               const uint8_t* grid_16);
-    static void compute_lut_entry(const Matrix3x3& src_to_XYZD50, float rgb[3]);
-    static sp<DataStruct> write_clut(const uint8_t* grid_points, const uint8_t* grid_16);
-
-    // Checks if a set of xyz tags is equivalent to a 3x3 Matrix. Each input
-    // tag buffer assumed to be at least kColorantTagSize in size.
-    static bool tagsEqualToMatrix(const Matrix3x3& matrix,
-                                  const uint8_t* red_tag,
-                                  const uint8_t* green_tag,
-                                  const uint8_t* blue_tag);
-
-public:
-    // Output includes JPEG embedding identifier and chunk information, but not
-    // APPx information.
-    static sp<DataStruct> writeIccProfile(const ultrahdr_transfer_function tf,
-                                          const ultrahdr_color_gamut gamut);
-    // NOTE: this function is not robust; it can infer gamuts that IccHelper
-    // writes out but should not be considered a reference implementation for
-    // robust parsing of ICC profiles or their gamuts.
-    static ultrahdr_color_gamut readIccColorGamut(void* icc_data, size_t icc_size);
-};
-}  // namespace android::ultrahdr
-
-#endif //ANDROID_ULTRAHDR_ICC_H
diff --git a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
deleted file mode 100644
index b86ce5f4..0000000
--- a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef ANDROID_ULTRAHDR_JPEGDECODERHELPER_H
-#define ANDROID_ULTRAHDR_JPEGDECODERHELPER_H
-
-// We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
-#include <cstdio>
-extern "C" {
-#include <jerror.h>
-#include <jpeglib.h>
-}
-#include <utils/Errors.h>
-#include <vector>
-
-// constraint on max width and max height is only due to device alloc constraints
-// Can tune these values basing on the target device
-static const int kMaxWidth = 8192;
-static const int kMaxHeight = 8192;
-
-namespace android::ultrahdr {
-/*
- * Encapsulates a converter from JPEG to raw image (YUV420planer or grey-scale) format.
- * This class is not thread-safe.
- */
-class JpegDecoderHelper {
-public:
-    JpegDecoderHelper();
-    ~JpegDecoderHelper();
-    /*
-     * Decompresses JPEG image to raw image (YUV420planer, grey-scale or RGBA) format. After
-     * calling this method, call getDecompressedImage() to get the image.
-     * Returns false if decompressing the image fails.
-     */
-    bool decompressImage(const void* image, int length, bool decodeToRGBA = false);
-    /*
-     * Returns the decompressed raw image buffer pointer. This method must be called only after
-     * calling decompressImage().
-     */
-    void* getDecompressedImagePtr();
-    /*
-     * Returns the decompressed raw image buffer size. This method must be called only after
-     * calling decompressImage().
-     */
-    size_t getDecompressedImageSize();
-    /*
-     * Returns the image width in pixels. This method must be called only after calling
-     * decompressImage().
-     */
-    size_t getDecompressedImageWidth();
-    /*
-     * Returns the image width in pixels. This method must be called only after calling
-     * decompressImage().
-     */
-    size_t getDecompressedImageHeight();
-    /*
-     * Returns the XMP data from the image.
-     */
-    void* getXMPPtr();
-    /*
-     * Returns the decompressed XMP buffer size. This method must be called only after
-     * calling decompressImage() or getCompressedImageParameters().
-     */
-    size_t getXMPSize();
-    /*
-     * Extracts EXIF package and updates the EXIF position / length without decoding the image.
-     */
-    bool extractEXIF(const void* image, int length);
-    /*
-     * Returns the EXIF data from the image.
-     * This method must be called after extractEXIF() or decompressImage().
-     */
-    void* getEXIFPtr();
-    /*
-     * Returns the decompressed EXIF buffer size. This method must be called only after
-     * calling decompressImage(), extractEXIF() 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.
-     * This method must be called after extractEXIF() or decompressImage().
-     */
-    int getEXIFPos() { return mExifPos; }
-    /*
-     * Returns the ICC data from the image.
-     */
-    void* getICCPtr();
-    /*
-     * Returns the decompressed ICC buffer size. This method must be called only after
-     * calling decompressImage() or getCompressedImageParameters().
-     */
-    size_t getICCSize();
-    /*
-     * Decompresses metadata of the image. All vectors are owned by the caller.
-     */
-    bool getCompressedImageParameters(const void* image, int length, size_t* pWidth,
-                                      size_t* pHeight, std::vector<uint8_t>* iccData,
-                                      std::vector<uint8_t>* exifData);
-
-private:
-    bool decode(const void* image, int length, bool decodeToRGBA);
-    // Returns false if errors occur.
-    bool decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, bool isSingleChannel);
-    bool decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest);
-    bool decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest);
-    bool decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest);
-    // Process 16 lines of Y and 16 lines of U/V each time.
-    // We must pass at least 16 scanlines according to libjpeg documentation.
-    static const int kCompressBatchSize = 16;
-    // The buffer that holds the decompressed result.
-    std::vector<JOCTET> mResultBuffer;
-    // The buffer that holds XMP Data.
-    std::vector<JOCTET> mXMPBuffer;
-    // The buffer that holds EXIF Data.
-    std::vector<JOCTET> mEXIFBuffer;
-    // The buffer that holds ICC Data.
-    std::vector<JOCTET> mICCBuffer;
-
-    // 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.
-    ssize_t mExifPos = -1;
-};
-} /* namespace android::ultrahdr  */
-
-#endif // ANDROID_ULTRAHDR_JPEGDECODERHELPER_H
diff --git a/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h
deleted file mode 100644
index 9d06415..0000000
--- a/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef ANDROID_ULTRAHDR_JPEGENCODERHELPER_H
-#define ANDROID_ULTRAHDR_JPEGENCODERHELPER_H
-
-// We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
-#include <cstdio>
-#include <vector>
-
-extern "C" {
-#include <jerror.h>
-#include <jpeglib.h>
-}
-
-#include <utils/Errors.h>
-
-namespace android::ultrahdr {
-
-#define ALIGNM(x, m) ((((x) + ((m)-1)) / (m)) * (m))
-
-/*
- * Encapsulates a converter from raw image (YUV420planer or grey-scale) to JPEG format.
- * This class is not thread-safe.
- */
-class JpegEncoderHelper {
-public:
-    JpegEncoderHelper();
-    ~JpegEncoderHelper();
-
-    /*
-     * Compresses YUV420Planer image to JPEG format. After calling this method, call
-     * getCompressedImage() to get the image. |quality| is the jpeg image quality parameter to use.
-     * It ranges from 1 (poorest quality) to 100 (highest quality). |iccBuffer| is the buffer of
-     * ICC segment which will be added to the compressed image.
-     * Returns false if errors occur during compression.
-     */
-    bool compressImage(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, int height,
-                       int lumaStride, int chromaStride, int quality, const void* iccBuffer,
-                       unsigned int iccSize);
-
-    /*
-     * Returns the compressed JPEG buffer pointer. This method must be called only after calling
-     * compressImage().
-     */
-    void* getCompressedImagePtr();
-
-    /*
-     * Returns the compressed JPEG buffer size. This method must be called only after calling
-     * compressImage().
-     */
-    size_t getCompressedImageSize();
-
-    /*
-     * Process 16 lines of Y and 16 lines of U/V each time.
-     * We must pass at least 16 scanlines according to libjpeg documentation.
-     */
-    static const int kCompressBatchSize = 16;
-
-private:
-    // initDestination(), emptyOutputBuffer() and emptyOutputBuffer() are callback functions to be
-    // passed into jpeg library.
-    static void initDestination(j_compress_ptr cinfo);
-    static boolean emptyOutputBuffer(j_compress_ptr cinfo);
-    static void terminateDestination(j_compress_ptr cinfo);
-    static void outputErrorMessage(j_common_ptr cinfo);
-
-    // Returns false if errors occur.
-    bool encode(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, int height,
-                int lumaStride, int chromaStride, int quality, const void* iccBuffer,
-                unsigned int iccSize);
-    void setJpegDestination(jpeg_compress_struct* cinfo);
-    void setJpegCompressStruct(int width, int height, int quality, jpeg_compress_struct* cinfo,
-                               bool isSingleChannel);
-    // Returns false if errors occur.
-    bool compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, const uint8_t* uvBuffer,
-                     int lumaStride, int chromaStride);
-    bool compressY(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, int lumaStride);
-
-    // The block size for encoded jpeg image buffer.
-    static const int kBlockSize = 16384;
-
-    // The buffer that holds the compressed result.
-    std::vector<JOCTET> mResultBuffer;
-};
-
-} /* namespace android::ultrahdr  */
-
-#endif // ANDROID_ULTRAHDR_JPEGENCODERHELPER_H
diff --git a/libs/ultrahdr/include/ultrahdr/jpegr.h b/libs/ultrahdr/include/ultrahdr/jpegr.h
deleted file mode 100644
index 114c81d..0000000
--- a/libs/ultrahdr/include/ultrahdr/jpegr.h
+++ /dev/null
@@ -1,452 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef ANDROID_ULTRAHDR_JPEGR_H
-#define ANDROID_ULTRAHDR_JPEGR_H
-
-#include <cstdint>
-#include <vector>
-
-#include "ultrahdr/jpegdecoderhelper.h"
-#include "ultrahdr/jpegencoderhelper.h"
-#include "ultrahdr/jpegrerrorcode.h"
-#include "ultrahdr/ultrahdr.h"
-
-#ifndef FLT_MAX
-#define FLT_MAX 0x1.fffffep127f
-#endif
-
-namespace android::ultrahdr {
-
-// The current JPEGR version that we encode to
-static const char* const kJpegrVersion = "1.0";
-
-// Map is quarter res / sixteenth size
-static const size_t kMapDimensionScaleFactor = 4;
-
-// Gain Map width is (image_width / kMapDimensionScaleFactor). If we were to
-// compress 420 GainMap in jpeg, then we need at least 2 samples. For Grayscale
-// 1 sample is sufficient. We are using 2 here anyways
-static const int kMinWidth = 2 * kMapDimensionScaleFactor;
-static const int kMinHeight = 2 * kMapDimensionScaleFactor;
-
-// Minimum Codec Unit(MCU) for 420 sub-sampling is decided by JPEG encoder by parameter
-// JpegEncoderHelper::kCompressBatchSize.
-// The width and height of image under compression is expected to be a multiple of MCU size.
-// If this criteria is not satisfied, padding is done.
-static const size_t kJpegBlock = JpegEncoderHelper::kCompressBatchSize;
-
-/*
- * Holds information of jpegr image
- */
-struct jpegr_info_struct {
-    size_t width;
-    size_t height;
-    std::vector<uint8_t>* iccData;
-    std::vector<uint8_t>* exifData;
-};
-
-/*
- * Holds information for uncompressed image or gain map.
- */
-struct jpegr_uncompressed_struct {
-    // Pointer to the data location.
-    void* data;
-    // Width of the gain map or the luma plane of the image in pixels.
-    int width;
-    // Height of the gain map or the luma plane of the image in pixels.
-    int height;
-    // Color gamut.
-    ultrahdr_color_gamut colorGamut;
-
-    // Values below are optional
-    // Pointer to chroma data, if it's NULL, chroma plane is considered to be immediately
-    // after the luma plane.
-    void* chroma_data = nullptr;
-    // Stride of Y plane in number of pixels. 0 indicates the member is uninitialized. If
-    // non-zero this value must be larger than or equal to luma width. If stride is
-    // uninitialized then it is assumed to be equal to luma width.
-    int luma_stride = 0;
-    // Stride of UV plane in number of pixels.
-    // 1. If this handle points to P010 image then this value must be larger than
-    //    or equal to luma width.
-    // 2. If this handle points to 420 image then this value must be larger than
-    //    or equal to (luma width / 2).
-    // NOTE: if chroma_data is nullptr, chroma_stride is irrelevant. Just as the way,
-    // chroma_data is derived from luma ptr, chroma stride is derived from luma stride.
-    int chroma_stride = 0;
-};
-
-/*
- * Holds information for compressed image or gain map.
- */
-struct jpegr_compressed_struct {
-    // Pointer to the data location.
-    void* data;
-    // Used data length in bytes.
-    int length;
-    // Maximum available data length in bytes.
-    int maxLength;
-    // Color gamut.
-    ultrahdr_color_gamut colorGamut;
-};
-
-/*
- * Holds information for EXIF metadata.
- */
-struct jpegr_exif_struct {
-    // Pointer to the data location.
-    void* data;
-    // Data length;
-    int length;
-};
-
-typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr;
-typedef struct jpegr_compressed_struct* jr_compressed_ptr;
-typedef struct jpegr_exif_struct* jr_exif_ptr;
-typedef struct jpegr_info_struct* jr_info_ptr;
-
-class JpegR {
-public:
-    /*
-     * Experimental only
-     *
-     * Encode API-0
-     * Compress JPEGR image from 10-bit HDR YUV.
-     *
-     * Tonemap the HDR input to a SDR image, generate gain map from the HDR and SDR images,
-     * compress SDR YUV to 8-bit JPEG and append the gain map to the end of the compressed
-     * JPEG.
-     * @param p010_image_ptr uncompressed HDR image in P010 color format
-     * @param hdr_tf transfer function of the HDR image
-     * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
-     *             represents the maximum available size of the destination buffer, and it must be
-     *             set before calling this method. If the encoded JPEGR size exceeds
-     *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
-     * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is
-     *                the highest quality
-     * @param exif pointer to the exif metadata.
-     * @return NO_ERROR if encoding succeeds, error code if error occurs.
-     */
-    status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf,
-                         jr_compressed_ptr dest, int quality, jr_exif_ptr exif);
-
-    /*
-     * Encode API-1
-     * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV.
-     *
-     * Generate gain map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append
-     * the gain map to the end of the compressed JPEG. HDR and SDR inputs must be the same
-     * resolution. SDR input is assumed to use the sRGB transfer function.
-     * @param p010_image_ptr uncompressed HDR image in P010 color format
-     * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format
-     * @param hdr_tf transfer function of the HDR image
-     * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
-     *             represents the maximum available size of the desitination buffer, and it must be
-     *             set before calling this method. If the encoded JPEGR size exceeds
-     *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
-     * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is
-     *                the highest quality
-     * @param exif pointer to the exif metadata.
-     * @return NO_ERROR if encoding succeeds, error code if error occurs.
-     */
-    status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_uncompressed_ptr yuv420_image_ptr,
-                         ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest, int quality,
-                         jr_exif_ptr exif);
-
-    /*
-     * Encode API-2
-     * Compress JPEGR image from 10-bit HDR YUV, 8-bit SDR YUV and compressed 8-bit JPEG.
-     *
-     * This method requires HAL Hardware JPEG encoder.
-     *
-     * Generate gain map from the HDR and SDR inputs, append the gain map to the end of the
-     * compressed JPEG. Adds an ICC profile if one isn't present in the input JPEG image. HDR and
-     * SDR inputs must be the same resolution and color space. SDR image is assumed to use the sRGB
-     * transfer function.
-     * @param p010_image_ptr uncompressed HDR image in P010 color format
-     * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format
-     * @param yuv420jpg_image_ptr SDR image compressed in jpeg format
-     *                            Note: the compressed SDR image must be the compressed
-     *                                  yuv420_image_ptr image in JPEG format.
-     * @param hdr_tf transfer function of the HDR image
-     * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
-     *             represents the maximum available size of the desitination buffer, and it must be
-     *             set before calling this method. If the encoded JPEGR size exceeds
-     *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
-     * @return NO_ERROR if encoding succeeds, error code if error occurs.
-     */
-    status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_uncompressed_ptr yuv420_image_ptr,
-                         jr_compressed_ptr yuv420jpg_image_ptr, ultrahdr_transfer_function hdr_tf,
-                         jr_compressed_ptr dest);
-
-    /*
-     * Encode API-3
-     * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV.
-     *
-     * This method requires HAL Hardware JPEG encoder.
-     *
-     * Decode the compressed 8-bit JPEG image to YUV SDR, generate gain map from the HDR input
-     * and the decoded SDR result, append the gain map to the end of the compressed JPEG. Adds an
-     * ICC profile if one isn't present in the input JPEG image. HDR and SDR inputs must be the same
-     * resolution. JPEG image is assumed to use the sRGB transfer function.
-     * @param p010_image_ptr uncompressed HDR image in P010 color format
-     * @param yuv420jpg_image_ptr SDR image compressed in jpeg format
-     * @param hdr_tf transfer function of the HDR image
-     * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
-     *             represents the maximum available size of the desitination buffer, and it must be
-     *             set before calling this method. If the encoded JPEGR size exceeds
-     *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
-     * @return NO_ERROR if encoding succeeds, error code if error occurs.
-     */
-    status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_compressed_ptr yuv420jpg_image_ptr,
-                         ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest);
-
-    /*
-     * Encode API-4
-     * Assemble JPEGR image from SDR JPEG and gainmap JPEG.
-     *
-     * Assemble the primary JPEG image, the gain map and the metadata to JPEG/R format. Adds an ICC
-     * profile if one isn't present in the input JPEG image.
-     * @param yuv420jpg_image_ptr SDR image compressed in jpeg format
-     * @param gainmapjpg_image_ptr gain map image compressed in jpeg format
-     * @param metadata metadata to be written in XMP of the primary jpeg
-     * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
-     *             represents the maximum available size of the desitination buffer, and it must be
-     *             set before calling this method. If the encoded JPEGR size exceeds
-     *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
-     * @return NO_ERROR if encoding succeeds, error code if error occurs.
-     */
-    status_t encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr,
-                         jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata,
-                         jr_compressed_ptr dest);
-
-    /*
-     * Decode API
-     * Decompress JPEGR image.
-     *
-     * This method assumes that the JPEGR image contains an ICC profile with primaries that match
-     * those of a color gamut that this library is aware of; Bt.709, Display-P3, or Bt.2100. It also
-     * assumes the base image uses the sRGB transfer function.
-     *
-     * This method only supports single gain map metadata values for fields that allow multi-channel
-     * metadata values.
-     * @param jpegr_image_ptr compressed JPEGR image.
-     * @param dest destination of the uncompressed JPEGR image.
-     * @param max_display_boost (optional) the maximum available boost supported by a display,
-     *                          the value must be greater than or equal to 1.0.
-     * @param exif destination of the decoded EXIF metadata. The default value is NULL where the
-                   decoder will do nothing about it. If configured not NULL the decoder will write
-                   EXIF data into this structure. The format is defined in {@code jpegr_exif_struct}
-     * @param output_format flag for setting output color format. Its value configures the output
-                            color format. The default value is {@code JPEGR_OUTPUT_HDR_LINEAR}.
-                            ----------------------------------------------------------------------
-                            |      output_format       |    decoded color format to be written   |
-                            ----------------------------------------------------------------------
-                            |     JPEGR_OUTPUT_SDR     |                RGBA_8888                |
-                            ----------------------------------------------------------------------
-                            | JPEGR_OUTPUT_HDR_LINEAR  |        (default)RGBA_F16 linear         |
-                            ----------------------------------------------------------------------
-                            |   JPEGR_OUTPUT_HDR_PQ    |             RGBA_1010102 PQ             |
-                            ----------------------------------------------------------------------
-                            |   JPEGR_OUTPUT_HDR_HLG   |            RGBA_1010102 HLG             |
-                            ----------------------------------------------------------------------
-     * @param gainmap_image_ptr destination of the decoded gain map. The default value is NULL
-                                where the decoder will do nothing about it. If configured not NULL
-                                the decoder will write the decoded gain_map data into this
-                                structure. The format is defined in
-                                {@code jpegr_uncompressed_struct}.
-     * @param metadata destination of the decoded metadata. The default value is NULL where the
-                       decoder will do nothing about it. If configured not NULL the decoder will
-                       write metadata into this structure. the format of metadata is defined in
-                       {@code ultrahdr_metadata_struct}.
-     * @return NO_ERROR if decoding succeeds, error code if error occurs.
-     */
-    status_t decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest,
-                         float max_display_boost = FLT_MAX, jr_exif_ptr exif = nullptr,
-                         ultrahdr_output_format output_format = ULTRAHDR_OUTPUT_HDR_LINEAR,
-                         jr_uncompressed_ptr gainmap_image_ptr = nullptr,
-                         ultrahdr_metadata_ptr metadata = nullptr);
-
-    /*
-     * Gets Info from JPEGR file without decoding it.
-     *
-     * This method only supports single gain map metadata values for fields that allow multi-channel
-     * metadata values.
-     *
-     * The output is filled jpegr_info structure
-     * @param jpegr_image_ptr compressed JPEGR image
-     * @param jpeg_image_info_ptr pointer to jpegr info struct. Members of jpegr_info
-     *                            are owned by the caller
-     * @return NO_ERROR if JPEGR parsing succeeds, error code otherwise
-     */
-    status_t getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpeg_image_info_ptr);
-
-protected:
-    /*
-     * This method is called in the encoding pipeline. It will take the uncompressed 8-bit and
-     * 10-bit yuv images as input, and calculate the uncompressed gain map. The input images
-     * must be the same resolution. The SDR input is assumed to use the sRGB transfer function.
-     *
-     * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format
-     * @param p010_image_ptr uncompressed HDR image in P010 color format
-     * @param hdr_tf transfer function of the HDR image
-     * @param metadata everything but "version" is filled in this struct
-     * @param dest location at which gain map image is stored (caller responsible for memory
-                   of data).
-     * @param sdr_is_601 if true, then use BT.601 decoding of YUV regardless of SDR image gamut
-     * @return NO_ERROR if calculation succeeds, error code if error occurs.
-     */
-    status_t generateGainMap(jr_uncompressed_ptr yuv420_image_ptr,
-                             jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf,
-                             ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr dest,
-                             bool sdr_is_601 = false);
-
-    /*
-     * This method is called in the decoding pipeline. It will take the uncompressed (decoded)
-     * 8-bit yuv image, the uncompressed (decoded) gain map, and extracted JPEG/R metadata as
-     * input, and calculate the 10-bit recovered image. The recovered output image is the same
-     * color gamut as the SDR image, with HLG transfer function, and is in RGBA1010102 data format.
-     * The SDR image is assumed to use the sRGB transfer function. The SDR image is also assumed to
-     * be a decoded JPEG for the purpose of YUV interpration.
-     *
-     * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format
-     * @param gainmap_image_ptr pointer to uncompressed gain map image struct.
-     * @param metadata JPEG/R metadata extracted from XMP.
-     * @param output_format flag for setting output color format. if set to
-     *                      {@code JPEGR_OUTPUT_SDR}, decoder will only decode the primary image
-     *                      which is SDR. Default value is JPEGR_OUTPUT_HDR_LINEAR.
-     * @param max_display_boost the maximum available boost supported by a display
-     * @param dest reconstructed HDR image
-     * @return NO_ERROR if calculation succeeds, error code if error occurs.
-     */
-    status_t applyGainMap(jr_uncompressed_ptr yuv420_image_ptr,
-                          jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata,
-                          ultrahdr_output_format output_format, float max_display_boost,
-                          jr_uncompressed_ptr dest);
-
-private:
-    /*
-     * This method is called in the encoding pipeline. It will encode the gain map.
-     *
-     * @param gainmap_image_ptr pointer to uncompressed gain map image struct
-     * @param jpeg_enc_obj_ptr helper resource to compress gain map
-     * @return NO_ERROR if encoding succeeds, error code if error occurs.
-     */
-    status_t compressGainMap(jr_uncompressed_ptr gainmap_image_ptr,
-                             JpegEncoderHelper* jpeg_enc_obj_ptr);
-
-    /*
-     * This method is called to separate primary image and gain map image from JPEGR
-     *
-     * @param jpegr_image_ptr pointer to compressed JPEGR image.
-     * @param primary_jpg_image_ptr destination of primary image
-     * @param gainmap_jpg_image_ptr destination of compressed gain map image
-     * @return NO_ERROR if calculation succeeds, error code if error occurs.
-     */
-    status_t extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,
-                                           jr_compressed_ptr primary_jpg_image_ptr,
-                                           jr_compressed_ptr gainmap_jpg_image_ptr);
-
-    /*
-     * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image,
-     * the compressed gain map and optionally the exif package as inputs, and generate the XMP
-     * metadata, and finally append everything in the order of:
-     *     SOI, APP2(EXIF) (if EXIF is from outside), APP2(XMP), primary image, gain map
-     *
-     * Note that in the final JPEG/R output, EXIF package will appear if ONLY ONE of the following
-     * conditions is fulfilled:
-     *  (1) EXIF package is available from outside input. I.e. pExif != nullptr.
-     *  (2) Input JPEG has EXIF.
-     * If both conditions are fulfilled, this method will return ERROR_JPEGR_INVALID_INPUT_TYPE
-     *
-     * @param primary_jpg_image_ptr destination of primary image
-     * @param gainmap_jpg_image_ptr destination of compressed gain map image
-     * @param (nullable) pExif EXIF package
-     * @param (nullable) pIcc ICC package
-     * @param icc_size length in bytes of ICC 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 appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
-                           jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif, void* pIcc,
-                           size_t icc_size, ultrahdr_metadata_ptr metadata, jr_compressed_ptr dest);
-
-    /*
-     * This method will tone map a HDR image to an SDR image.
-     *
-     * @param src pointer to uncompressed HDR image struct. HDR image is expected to be
-     *            in p010 color format
-     * @param dest pointer to store tonemapped SDR image
-     */
-    status_t toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest);
-
-    /*
-     * This method will convert a YUV420 image from one YUV encoding to another in-place (eg.
-     * Bt.709 to Bt.601 YUV encoding).
-     *
-     * src_encoding and dest_encoding indicate the encoding via the YUV conversion defined for that
-     * gamut. P3 indicates Rec.601, since this is how DataSpace encodes Display-P3 YUV data.
-     *
-     * @param image the YUV420 image to convert
-     * @param src_encoding input YUV encoding
-     * @param dest_encoding output YUV encoding
-     * @return NO_ERROR if calculation succeeds, error code if error occurs.
-     */
-    status_t convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding,
-                        ultrahdr_color_gamut dest_encoding);
-
-    /*
-     * This method will check the validity of the input arguments.
-     *
-     * @param p010_image_ptr uncompressed HDR image in P010 color format
-     * @param yuv420_image_ptr pointer to uncompressed SDR image struct. HDR image is expected to
-     *                         be in 420p color format
-     * @param hdr_tf transfer function of the HDR image
-     * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
-     *             represents the maximum available size of the desitination buffer, and it must be
-     *             set before calling this method. If the encoded JPEGR size exceeds
-     *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
-     * @return NO_ERROR if the input args are valid, error code is not valid.
-     */
-    status_t areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
-                                    jr_uncompressed_ptr yuv420_image_ptr,
-                                    ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest_ptr);
-
-    /*
-     * This method will check the validity of the input arguments.
-     *
-     * @param p010_image_ptr uncompressed HDR image in P010 color format
-     * @param yuv420_image_ptr pointer to uncompressed SDR image struct. HDR image is expected to
-     *                         be in 420p color format
-     * @param hdr_tf transfer function of the HDR image
-     * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
-     *             represents the maximum available size of the destination buffer, and it must be
-     *             set before calling this method. If the encoded JPEGR size exceeds
-     *             {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
-     * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is
-     *                the highest quality
-     * @return NO_ERROR if the input args are valid, error code is not valid.
-     */
-    status_t areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
-                                    jr_uncompressed_ptr yuv420_image_ptr,
-                                    ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest,
-                                    int quality);
-};
-} // namespace android::ultrahdr
-
-#endif // ANDROID_ULTRAHDR_JPEGR_H
diff --git a/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h b/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h
deleted file mode 100644
index 5420e1c..0000000
--- a/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h
+++ /dev/null
@@ -1,61 +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.
- */
-
-#ifndef ANDROID_ULTRAHDR_JPEGRERRORCODE_H
-#define ANDROID_ULTRAHDR_JPEGRERRORCODE_H
-
-#include <utils/Errors.h>
-
-namespace android::ultrahdr {
-
-enum {
-    // status_t map for errors in the media framework
-    // OK or NO_ERROR or 0 represents no error.
-
-    // See system/core/include/utils/Errors.h
-    // System standard errors from -1 through (possibly) -133
-    //
-    // Errors with special meanings and side effects.
-    // INVALID_OPERATION:  Operation attempted in an illegal state (will try to signal to app).
-    // DEAD_OBJECT:        Signal from CodecBase to MediaCodec that MediaServer has died.
-    // NAME_NOT_FOUND:     Signal from CodecBase to MediaCodec that the component was not found.
-
-    // JPEGR errors
-    JPEGR_IO_ERROR_BASE                 = -10000,
-    ERROR_JPEGR_INVALID_INPUT_TYPE      = JPEGR_IO_ERROR_BASE,
-    ERROR_JPEGR_INVALID_OUTPUT_TYPE     = JPEGR_IO_ERROR_BASE - 1,
-    ERROR_JPEGR_INVALID_NULL_PTR        = JPEGR_IO_ERROR_BASE - 2,
-    ERROR_JPEGR_RESOLUTION_MISMATCH     = JPEGR_IO_ERROR_BASE - 3,
-    ERROR_JPEGR_BUFFER_TOO_SMALL        = JPEGR_IO_ERROR_BASE - 4,
-    ERROR_JPEGR_INVALID_COLORGAMUT      = JPEGR_IO_ERROR_BASE - 5,
-    ERROR_JPEGR_INVALID_TRANS_FUNC      = JPEGR_IO_ERROR_BASE - 6,
-    ERROR_JPEGR_INVALID_METADATA        = JPEGR_IO_ERROR_BASE - 7,
-    ERROR_JPEGR_UNSUPPORTED_METADATA    = JPEGR_IO_ERROR_BASE - 8,
-    ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND = JPEGR_IO_ERROR_BASE - 9,
-
-    JPEGR_RUNTIME_ERROR_BASE            = -20000,
-    ERROR_JPEGR_ENCODE_ERROR            = JPEGR_RUNTIME_ERROR_BASE - 1,
-    ERROR_JPEGR_DECODE_ERROR            = JPEGR_RUNTIME_ERROR_BASE - 2,
-    ERROR_JPEGR_CALCULATION_ERROR       = JPEGR_RUNTIME_ERROR_BASE - 3,
-    ERROR_JPEGR_METADATA_ERROR          = JPEGR_RUNTIME_ERROR_BASE - 4,
-    ERROR_JPEGR_TONEMAP_ERROR           = JPEGR_RUNTIME_ERROR_BASE - 5,
-
-    ERROR_JPEGR_UNSUPPORTED_FEATURE     = -20000,
-};
-
-}  // namespace android::ultrahdr
-
-#endif // ANDROID_ULTRAHDR_JPEGRERRORCODE_H
diff --git a/libs/ultrahdr/include/ultrahdr/jpegrutils.h b/libs/ultrahdr/include/ultrahdr/jpegrutils.h
deleted file mode 100644
index 4ab664e..0000000
--- a/libs/ultrahdr/include/ultrahdr/jpegrutils.h
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef ANDROID_ULTRAHDR_JPEGRUTILS_H
-#define ANDROID_ULTRAHDR_JPEGRUTILS_H
-
-#include <ultrahdr/jpegr.h>
-#include <utils/RefBase.h>
-
-#include <sstream>
-#include <stdint.h>
-#include <string>
-#include <cstdio>
-
-namespace android::ultrahdr {
-
-static constexpr uint32_t EndianSwap32(uint32_t value) {
-    return ((value & 0xFF) << 24) |
-           ((value & 0xFF00) << 8) |
-           ((value & 0xFF0000) >> 8) |
-           (value >> 24);
-}
-static inline uint16_t EndianSwap16(uint16_t value) {
-    return static_cast<uint16_t>((value >> 8) | ((value & 0xFF) << 8));
-}
-
-#if USE_BIG_ENDIAN
-    #define Endian_SwapBE32(n) EndianSwap32(n)
-    #define Endian_SwapBE16(n) EndianSwap16(n)
-#else
-    #define Endian_SwapBE32(n) (n)
-    #define Endian_SwapBE16(n) (n)
-#endif
-
-struct ultrahdr_metadata_struct;
-/*
- * Mutable data structure. Holds information for metadata.
- */
-class DataStruct : public RefBase {
-private:
-    void* data;
-    int writePos;
-    int length;
-    ~DataStruct();
-
-public:
-    DataStruct(int s);
-    void* getData();
-    int getLength();
-    int getBytesWritten();
-    bool write8(uint8_t value);
-    bool write16(uint16_t value);
-    bool write32(uint32_t value);
-    bool write(const void* src, int size);
-};
-
-/*
- * Helper function used for writing data to destination.
- *
- * @param destination destination of the data to be written.
- * @param source source of data being written.
- * @param length length of the data to be written.
- * @param position cursor in desitination where the data is to be written.
- * @return status of succeed or error code.
- */
-status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position);
-
-
-/*
- * Parses XMP packet and fills metadata with data from XMP
- *
- * @param xmp_data pointer to XMP packet
- * @param xmp_size size of XMP packet
- * @param metadata place to store HDR metadata values
- * @return true if metadata is successfully retrieved, false otherwise
-*/
-bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, ultrahdr_metadata_struct* metadata);
-
-/*
- * This method generates XMP metadata for the primary image.
- *
- * below is an example of the XMP metadata that this function generates where
- * secondary_image_length = 1000
- *
- * <x:xmpmeta
- *   xmlns:x="adobe:ns:meta/"
- *   x:xmptk="Adobe XMP Core 5.1.2">
- *   <rdf:RDF
- *     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
- *     <rdf:Description
- *       xmlns:Container="http://ns.google.com/photos/1.0/container/"
- *       xmlns:Item="http://ns.google.com/photos/1.0/container/item/"
- *       xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/"
- *       hdrgm:Version="1">
- *       <Container:Directory>
- *         <rdf:Seq>
- *           <rdf:li
- *             rdf:parseType="Resource">
- *             <Container:Item
- *               Item:Semantic="Primary"
- *               Item:Mime="image/jpeg"/>
- *           </rdf:li>
- *           <rdf:li
- *             rdf:parseType="Resource">
- *             <Container:Item
- *               Item:Semantic="GainMap"
- *               Item:Mime="image/jpeg"
- *               Item:Length="1000"/>
- *           </rdf:li>
- *         </rdf:Seq>
- *       </Container:Directory>
- *     </rdf:Description>
- *   </rdf:RDF>
- * </x:xmpmeta>
- *
- * @param secondary_image_length length of secondary image
- * @return XMP metadata in type of string
- */
-std::string generateXmpForPrimaryImage(int secondary_image_length,
-                                       ultrahdr_metadata_struct& metadata);
-
-/*
- * This method generates XMP metadata for the recovery map image.
- *
- * below is an example of the XMP metadata that this function generates where
- * max_content_boost = 8.0
- * min_content_boost = 0.5
- *
- * <x:xmpmeta
- *   xmlns:x="adobe:ns:meta/"
- *   x:xmptk="Adobe XMP Core 5.1.2">
- *   <rdf:RDF
- *     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
- *     <rdf:Description
- *       xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/"
- *       hdrgm:Version="1"
- *       hdrgm:GainMapMin="-1"
- *       hdrgm:GainMapMax="3"
- *       hdrgm:Gamma="1"
- *       hdrgm:OffsetSDR="0"
- *       hdrgm:OffsetHDR="0"
- *       hdrgm:HDRCapacityMin="0"
- *       hdrgm:HDRCapacityMax="3"
- *       hdrgm:BaseRenditionIsHDR="False"/>
- *   </rdf:RDF>
- * </x:xmpmeta>
- *
- * @param metadata JPEG/R metadata to encode as XMP
- * @return XMP metadata in type of string
- */
- std::string generateXmpForSecondaryImage(ultrahdr_metadata_struct& metadata);
-}  // namespace android::ultrahdr
-
-#endif //ANDROID_ULTRAHDR_JPEGRUTILS_H
diff --git a/libs/ultrahdr/include/ultrahdr/multipictureformat.h b/libs/ultrahdr/include/ultrahdr/multipictureformat.h
deleted file mode 100644
index c5bd09d..0000000
--- a/libs/ultrahdr/include/ultrahdr/multipictureformat.h
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef ANDROID_ULTRAHDR_MULTIPICTUREFORMAT_H
-#define ANDROID_ULTRAHDR_MULTIPICTUREFORMAT_H
-
-#include <ultrahdr/jpegrutils.h>
-
-#ifdef USE_BIG_ENDIAN
-#undef USE_BIG_ENDIAN
-#define USE_BIG_ENDIAN true
-#endif
-
-namespace android::ultrahdr {
-
-constexpr size_t kNumPictures = 2;
-constexpr size_t kMpEndianSize = 4;
-constexpr uint16_t kTagSerializedCount = 3;
-constexpr uint32_t kTagSize = 12;
-
-constexpr uint16_t kTypeLong = 0x4;
-constexpr uint16_t kTypeUndefined = 0x7;
-
-static constexpr uint8_t kMpfSig[] = {'M', 'P', 'F', '\0'};
-constexpr uint8_t kMpLittleEndian[kMpEndianSize] = {0x49, 0x49, 0x2A, 0x00};
-constexpr uint8_t kMpBigEndian[kMpEndianSize] = {0x4D, 0x4D, 0x00, 0x2A};
-
-constexpr uint16_t kVersionTag = 0xB000;
-constexpr uint16_t kVersionType = kTypeUndefined;
-constexpr uint32_t kVersionCount = 4;
-constexpr size_t kVersionSize = 4;
-constexpr uint8_t kVersionExpected[kVersionSize] = {'0', '1', '0', '0'};
-
-constexpr uint16_t kNumberOfImagesTag = 0xB001;
-constexpr uint16_t kNumberOfImagesType = kTypeLong;
-constexpr uint32_t kNumberOfImagesCount = 1;
-
-constexpr uint16_t kMPEntryTag = 0xB002;
-constexpr uint16_t kMPEntryType = kTypeUndefined;
-constexpr uint32_t kMPEntrySize = 16;
-
-constexpr uint32_t kMPEntryAttributeFormatJpeg = 0x0000000;
-constexpr uint32_t kMPEntryAttributeTypePrimary = 0x030000;
-
-size_t calculateMpfSize();
-sp<DataStruct> generateMpf(int primary_image_size, int primary_image_offset,
-                           int secondary_image_size, int secondary_image_offset);
-
-}  // namespace android::ultrahdr
-
-#endif //ANDROID_ULTRAHDR_MULTIPICTUREFORMAT_H
diff --git a/libs/ultrahdr/include/ultrahdr/ultrahdr.h b/libs/ultrahdr/include/ultrahdr/ultrahdr.h
deleted file mode 100644
index 0252391..0000000
--- a/libs/ultrahdr/include/ultrahdr/ultrahdr.h
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_ULTRAHDR_ULTRAHDR_H
-#define ANDROID_ULTRAHDR_ULTRAHDR_H
-
-#include <string>
-
-namespace android::ultrahdr {
-// Color gamuts for image data
-typedef enum {
-  ULTRAHDR_COLORGAMUT_UNSPECIFIED = -1,
-  ULTRAHDR_COLORGAMUT_BT709,
-  ULTRAHDR_COLORGAMUT_P3,
-  ULTRAHDR_COLORGAMUT_BT2100,
-  ULTRAHDR_COLORGAMUT_MAX = ULTRAHDR_COLORGAMUT_BT2100,
-} ultrahdr_color_gamut;
-
-// Transfer functions for image data
-// TODO: TF LINEAR is deprecated, remove this enum and the code surrounding it.
-typedef enum {
-  ULTRAHDR_TF_UNSPECIFIED = -1,
-  ULTRAHDR_TF_LINEAR = 0,
-  ULTRAHDR_TF_HLG = 1,
-  ULTRAHDR_TF_PQ = 2,
-  ULTRAHDR_TF_SRGB = 3,
-  ULTRAHDR_TF_MAX = ULTRAHDR_TF_SRGB,
-} ultrahdr_transfer_function;
-
-// Target output formats for decoder
-typedef enum {
-  ULTRAHDR_OUTPUT_UNSPECIFIED = -1,
-  ULTRAHDR_OUTPUT_SDR,          // SDR in RGBA_8888 color format
-  ULTRAHDR_OUTPUT_HDR_LINEAR,   // HDR in F16 color format (linear)
-  ULTRAHDR_OUTPUT_HDR_PQ,       // HDR in RGBA_1010102 color format (PQ transfer function)
-  ULTRAHDR_OUTPUT_HDR_HLG,      // HDR in RGBA_1010102 color format (HLG transfer function)
-  ULTRAHDR_OUTPUT_MAX = ULTRAHDR_OUTPUT_HDR_HLG,
-} ultrahdr_output_format;
-
-/*
- * Holds information for gain map related metadata.
- *
- * Not: all values stored in linear. This differs from the metadata encoding in XMP, where
- * maxContentBoost (aka gainMapMax), minContentBoost (aka gainMapMin), hdrCapacityMin, and
- * hdrCapacityMax are stored in log2 space.
- */
-struct ultrahdr_metadata_struct {
-  // Ultra HDR format version
-  std::string version;
-  // Max Content Boost for the map
-  float maxContentBoost;
-  // Min Content Boost for the map
-  float minContentBoost;
-  // Gamma of the map data
-  float gamma;
-  // Offset for SDR data in map calculations
-  float offsetSdr;
-  // Offset for HDR data in map calculations
-  float offsetHdr;
-  // HDR capacity to apply the map at all
-  float hdrCapacityMin;
-  // HDR capacity to apply the map completely
-  float hdrCapacityMax;
-};
-typedef struct ultrahdr_metadata_struct* ultrahdr_metadata_ptr;
-
-}  // namespace android::ultrahdr
-
-#endif //ANDROID_ULTRAHDR_ULTRAHDR_H
diff --git a/libs/ultrahdr/jpegdecoderhelper.cpp b/libs/ultrahdr/jpegdecoderhelper.cpp
deleted file mode 100644
index 2e7940c..0000000
--- a/libs/ultrahdr/jpegdecoderhelper.cpp
+++ /dev/null
@@ -1,543 +0,0 @@
-/*
- * 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 <ultrahdr/jpegdecoderhelper.h>
-
-#include <utils/Log.h>
-
-#include <errno.h>
-#include <setjmp.h>
-#include <string>
-
-using namespace std;
-
-namespace android::ultrahdr {
-
-#define ALIGNM(x, m) ((((x) + ((m)-1)) / (m)) * (m))
-
-const uint32_t kAPP0Marker = JPEG_APP0;     // JFIF
-const uint32_t kAPP1Marker = JPEG_APP0 + 1; // EXIF, XMP
-const uint32_t kAPP2Marker = JPEG_APP0 + 2; // ICC
-
-constexpr uint32_t kICCMarkerHeaderSize = 14;
-constexpr uint8_t kICCSig[] = {
-        'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\0',
-};
-constexpr uint8_t kXmpNameSpace[] = {
-        'h', 't', 't', 'p', ':', '/', '/', 'n', 's', '.', 'a', 'd', 'o', 'b', 'e',
-        '.', 'c', 'o', 'm', '/', 'x', 'a', 'p', '/', '1', '.', '0', '/', '\0',
-};
-constexpr uint8_t kExifIdCode[] = {
-        'E', 'x', 'i', 'f', '\0', '\0',
-};
-
-struct jpegr_source_mgr : jpeg_source_mgr {
-    jpegr_source_mgr(const uint8_t* ptr, int len);
-    ~jpegr_source_mgr();
-
-    const uint8_t* mBufferPtr;
-    size_t mBufferLength;
-};
-
-struct jpegrerror_mgr {
-    struct jpeg_error_mgr pub;
-    jmp_buf setjmp_buffer;
-};
-
-static void jpegr_init_source(j_decompress_ptr cinfo) {
-    jpegr_source_mgr* src = static_cast<jpegr_source_mgr*>(cinfo->src);
-    src->next_input_byte = static_cast<const JOCTET*>(src->mBufferPtr);
-    src->bytes_in_buffer = src->mBufferLength;
-}
-
-static boolean jpegr_fill_input_buffer(j_decompress_ptr /* cinfo */) {
-    ALOGE("%s : should not get here", __func__);
-    return FALSE;
-}
-
-static void jpegr_skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
-    jpegr_source_mgr* src = static_cast<jpegr_source_mgr*>(cinfo->src);
-
-    if (num_bytes > static_cast<long>(src->bytes_in_buffer)) {
-        ALOGE("jpegr_skip_input_data - num_bytes > (long)src->bytes_in_buffer");
-    } else {
-        src->next_input_byte += num_bytes;
-        src->bytes_in_buffer -= num_bytes;
-    }
-}
-
-static void jpegr_term_source(j_decompress_ptr /*cinfo*/) {}
-
-jpegr_source_mgr::jpegr_source_mgr(const uint8_t* ptr, int len)
-      : mBufferPtr(ptr), mBufferLength(len) {
-    init_source = jpegr_init_source;
-    fill_input_buffer = jpegr_fill_input_buffer;
-    skip_input_data = jpegr_skip_input_data;
-    resync_to_restart = jpeg_resync_to_restart;
-    term_source = jpegr_term_source;
-}
-
-jpegr_source_mgr::~jpegr_source_mgr() {}
-
-static void jpegrerror_exit(j_common_ptr cinfo) {
-    jpegrerror_mgr* err = reinterpret_cast<jpegrerror_mgr*>(cinfo->err);
-    longjmp(err->setjmp_buffer, 1);
-}
-
-JpegDecoderHelper::JpegDecoderHelper() {}
-
-JpegDecoderHelper::~JpegDecoderHelper() {}
-
-bool JpegDecoderHelper::decompressImage(const void* image, int length, bool decodeToRGBA) {
-    if (image == nullptr || length <= 0) {
-        ALOGE("Image size can not be handled: %d", length);
-        return false;
-    }
-    mResultBuffer.clear();
-    mXMPBuffer.clear();
-    return decode(image, length, decodeToRGBA);
-}
-
-void* JpegDecoderHelper::getDecompressedImagePtr() {
-    return mResultBuffer.data();
-}
-
-size_t JpegDecoderHelper::getDecompressedImageSize() {
-    return mResultBuffer.size();
-}
-
-void* JpegDecoderHelper::getXMPPtr() {
-    return mXMPBuffer.data();
-}
-
-size_t JpegDecoderHelper::getXMPSize() {
-    return mXMPBuffer.size();
-}
-
-void* JpegDecoderHelper::getEXIFPtr() {
-    return mEXIFBuffer.data();
-}
-
-size_t JpegDecoderHelper::getEXIFSize() {
-    return mEXIFBuffer.size();
-}
-
-void* JpegDecoderHelper::getICCPtr() {
-    return mICCBuffer.data();
-}
-
-size_t JpegDecoderHelper::getICCSize() {
-    return mICCBuffer.size();
-}
-
-size_t JpegDecoderHelper::getDecompressedImageWidth() {
-    return mWidth;
-}
-
-size_t JpegDecoderHelper::getDecompressedImageHeight() {
-    return mHeight;
-}
-
-// Here we only handle the first EXIF package, and in theary EXIF (or JFIF) must be the first
-// in the image file.
-// 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.
-bool JpegDecoderHelper::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);
-
-    cinfo.src = &mgr;
-    jpeg_read_header(&cinfo, TRUE);
-
-    size_t pos = 2;  // position after SOI
-    for (jpeg_marker_struct* marker = cinfo.marker_list;
-         marker;
-         marker = marker->next) {
-
-        pos += 4;
-        pos += marker->original_length;
-
-        if (marker->marker != kAPP1Marker) {
-            continue;
-        }
-
-        const unsigned int len = marker->data_length;
-
-        if (len > sizeof(kExifIdCode) &&
-            !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
-            mEXIFBuffer.resize(len, 0);
-            memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
-            mExifPos = pos - marker->original_length;
-            break;
-        }
-    }
-
-    jpeg_destroy_decompress(&cinfo);
-    return true;
-}
-
-bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) {
-    bool status = true;
-    jpeg_decompress_struct cinfo;
-    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);
-
-    jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
-    cinfo.src = &mgr;
-    if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
-        jpeg_destroy_decompress(&cinfo);
-        return false;
-    }
-
-    // Save XMP data, EXIF data, and ICC data.
-    // Here we only handle the first XMP / EXIF / ICC package.
-    // 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.
-    bool exifAppears = false;
-    bool xmpAppears = false;
-    bool iccAppears = false;
-    size_t pos = 2;  // position after SOI
-    for (jpeg_marker_struct* marker = cinfo.marker_list;
-         marker && !(exifAppears && xmpAppears && iccAppears);
-         marker = marker->next) {
-         pos += 4;
-         pos += marker->original_length;
-        if (marker->marker != kAPP1Marker && marker->marker != kAPP2Marker) {
-            continue;
-        }
-        const unsigned int len = marker->data_length;
-        if (!xmpAppears &&
-            len > sizeof(kXmpNameSpace) &&
-            !memcmp(marker->data, kXmpNameSpace, sizeof(kXmpNameSpace))) {
-            mXMPBuffer.resize(len+1, 0);
-            memcpy(static_cast<void*>(mXMPBuffer.data()), marker->data, len);
-            xmpAppears = true;
-        } else if (!exifAppears &&
-                   len > sizeof(kExifIdCode) &&
-                   !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
-            mEXIFBuffer.resize(len, 0);
-            memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
-            exifAppears = true;
-            mExifPos = pos - marker->original_length;
-        } else if (!iccAppears &&
-                   len > sizeof(kICCSig) &&
-                   !memcmp(marker->data, kICCSig, sizeof(kICCSig))) {
-            mICCBuffer.resize(len, 0);
-            memcpy(static_cast<void*>(mICCBuffer.data()), marker->data, len);
-            iccAppears = true;
-        }
-    }
-
-    mWidth = cinfo.image_width;
-    mHeight = cinfo.image_height;
-    if (mWidth > kMaxWidth || mHeight > kMaxHeight) {
-        status = false;
-        goto CleanUp;
-    }
-
-    if (decodeToRGBA) {
-        // The primary image is expected to be yuv420 sampling
-        if (cinfo.jpeg_color_space != JCS_YCbCr) {
-            status = false;
-            ALOGE("%s: decodeToRGBA unexpected jpeg color space ", __func__);
-            goto CleanUp;
-        }
-        if (cinfo.comp_info[0].h_samp_factor != 2 || cinfo.comp_info[0].v_samp_factor != 2 ||
-            cinfo.comp_info[1].h_samp_factor != 1 || cinfo.comp_info[1].v_samp_factor != 1 ||
-            cinfo.comp_info[2].h_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) {
-            status = false;
-            ALOGE("%s: decodeToRGBA unexpected primary image sub-sampling", __func__);
-            goto CleanUp;
-        }
-        // 4 bytes per pixel
-        mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 4);
-        cinfo.out_color_space = JCS_EXT_RGBA;
-    } else {
-        if (cinfo.jpeg_color_space == JCS_YCbCr) {
-            if (cinfo.comp_info[0].h_samp_factor != 2 || cinfo.comp_info[0].v_samp_factor != 2 ||
-                cinfo.comp_info[1].h_samp_factor != 1 || cinfo.comp_info[1].v_samp_factor != 1 ||
-                cinfo.comp_info[2].h_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) {
-                status = false;
-                ALOGE("%s: decoding to YUV only supports 4:2:0 subsampling", __func__);
-                goto CleanUp;
-            }
-            mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0);
-        } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
-            mResultBuffer.resize(cinfo.image_width * cinfo.image_height, 0);
-        } else {
-            status = false;
-            ALOGE("%s: decodeToYUV unexpected jpeg color space", __func__);
-            goto CleanUp;
-        }
-        cinfo.out_color_space = cinfo.jpeg_color_space;
-        cinfo.raw_data_out = TRUE;
-    }
-
-    cinfo.dct_method = JDCT_ISLOW;
-    jpeg_start_decompress(&cinfo);
-    if (!decompress(&cinfo, static_cast<const uint8_t*>(mResultBuffer.data()),
-                    cinfo.jpeg_color_space == JCS_GRAYSCALE)) {
-        status = false;
-        goto CleanUp;
-    }
-
-CleanUp:
-    jpeg_finish_decompress(&cinfo);
-    jpeg_destroy_decompress(&cinfo);
-
-    return status;
-}
-
-bool JpegDecoderHelper::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest,
-                                   bool isSingleChannel) {
-    return isSingleChannel
-            ? decompressSingleChannel(cinfo, dest)
-            : ((cinfo->out_color_space == JCS_EXT_RGBA) ? decompressRGBA(cinfo, dest)
-                                                        : decompressYUV(cinfo, dest));
-}
-
-bool JpegDecoderHelper::getCompressedImageParameters(const void* image, int length, size_t* pWidth,
-                                                     size_t* pHeight, std::vector<uint8_t>* iccData,
-                                                     std::vector<uint8_t>* exifData) {
-    jpeg_decompress_struct cinfo;
-    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, kAPP1Marker, 0xFFFF);
-    jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
-
-    jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
-    cinfo.src = &mgr;
-    if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
-        jpeg_destroy_decompress(&cinfo);
-        return false;
-    }
-
-    if (pWidth != nullptr) {
-        *pWidth = cinfo.image_width;
-    }
-    if (pHeight != nullptr) {
-        *pHeight = cinfo.image_height;
-    }
-
-    if (iccData != nullptr) {
-        for (jpeg_marker_struct* marker = cinfo.marker_list; marker; marker = marker->next) {
-            if (marker->marker != kAPP2Marker) {
-                continue;
-            }
-            if (marker->data_length <= kICCMarkerHeaderSize ||
-                memcmp(marker->data, kICCSig, sizeof(kICCSig)) != 0) {
-                continue;
-            }
-
-            iccData->insert(iccData->end(), marker->data, marker->data + marker->data_length);
-        }
-    }
-
-    if (exifData != nullptr) {
-        bool exifAppears = false;
-        for (jpeg_marker_struct* marker = cinfo.marker_list; marker && !exifAppears;
-             marker = marker->next) {
-            if (marker->marker != kAPP1Marker) {
-                continue;
-            }
-
-            const unsigned int len = marker->data_length;
-            if (len >= sizeof(kExifIdCode) &&
-                !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
-                exifData->resize(len, 0);
-                memcpy(static_cast<void*>(exifData->data()), marker->data, len);
-                exifAppears = true;
-            }
-        }
-    }
-
-    jpeg_destroy_decompress(&cinfo);
-    return true;
-}
-
-bool JpegDecoderHelper::decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
-    JSAMPLE* out = (JSAMPLE*)dest;
-
-    while (cinfo->output_scanline < cinfo->image_height) {
-        if (1 != jpeg_read_scanlines(cinfo, &out, 1)) return false;
-        out += cinfo->image_width * 4;
-    }
-    return true;
-}
-
-bool JpegDecoderHelper::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
-    JSAMPROW y[kCompressBatchSize];
-    JSAMPROW cb[kCompressBatchSize / 2];
-    JSAMPROW cr[kCompressBatchSize / 2];
-    JSAMPARRAY planes[3]{y, cb, cr};
-
-    size_t y_plane_size = cinfo->image_width * cinfo->image_height;
-    size_t uv_plane_size = y_plane_size / 4;
-    uint8_t* y_plane = const_cast<uint8_t*>(dest);
-    uint8_t* u_plane = const_cast<uint8_t*>(dest + y_plane_size);
-    uint8_t* v_plane = const_cast<uint8_t*>(dest + y_plane_size + uv_plane_size);
-    std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
-    memset(empty.get(), 0, cinfo->image_width);
-
-    const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
-    bool is_width_aligned = (aligned_width == cinfo->image_width);
-    std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
-    uint8_t* y_plane_intrm = nullptr;
-    uint8_t* u_plane_intrm = nullptr;
-    uint8_t* v_plane_intrm = nullptr;
-    JSAMPROW y_intrm[kCompressBatchSize];
-    JSAMPROW cb_intrm[kCompressBatchSize / 2];
-    JSAMPROW cr_intrm[kCompressBatchSize / 2];
-    JSAMPARRAY planes_intrm[3]{y_intrm, cb_intrm, cr_intrm};
-    if (!is_width_aligned) {
-        size_t mcu_row_size = aligned_width * kCompressBatchSize * 3 / 2;
-        buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
-        y_plane_intrm = buffer_intrm.get();
-        u_plane_intrm = y_plane_intrm + (aligned_width * kCompressBatchSize);
-        v_plane_intrm = u_plane_intrm + (aligned_width * kCompressBatchSize) / 4;
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            y_intrm[i] = y_plane_intrm + i * aligned_width;
-        }
-        for (int i = 0; i < kCompressBatchSize / 2; ++i) {
-            int offset_intrm = i * (aligned_width / 2);
-            cb_intrm[i] = u_plane_intrm + offset_intrm;
-            cr_intrm[i] = v_plane_intrm + offset_intrm;
-        }
-    }
-
-    while (cinfo->output_scanline < cinfo->image_height) {
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            size_t scanline = cinfo->output_scanline + i;
-            if (scanline < cinfo->image_height) {
-                y[i] = y_plane + scanline * cinfo->image_width;
-            } else {
-                y[i] = empty.get();
-            }
-        }
-        // cb, cr only have half scanlines
-        for (int i = 0; i < kCompressBatchSize / 2; ++i) {
-            size_t scanline = cinfo->output_scanline / 2 + i;
-            if (scanline < cinfo->image_height / 2) {
-                int offset = scanline * (cinfo->image_width / 2);
-                cb[i] = u_plane + offset;
-                cr[i] = v_plane + offset;
-            } else {
-                cb[i] = cr[i] = empty.get();
-            }
-        }
-
-        int processed = jpeg_read_raw_data(cinfo, is_width_aligned ? planes : planes_intrm,
-                                           kCompressBatchSize);
-        if (processed != kCompressBatchSize) {
-            ALOGE("Number of processed lines does not equal input lines.");
-            return false;
-        }
-        if (!is_width_aligned) {
-            for (int i = 0; i < kCompressBatchSize; ++i) {
-                memcpy(y[i], y_intrm[i], cinfo->image_width);
-            }
-            for (int i = 0; i < kCompressBatchSize / 2; ++i) {
-                memcpy(cb[i], cb_intrm[i], cinfo->image_width / 2);
-                memcpy(cr[i], cr_intrm[i], cinfo->image_width / 2);
-            }
-        }
-    }
-    return true;
-}
-
-bool JpegDecoderHelper::decompressSingleChannel(jpeg_decompress_struct* cinfo,
-                                                const uint8_t* dest) {
-    JSAMPROW y[kCompressBatchSize];
-    JSAMPARRAY planes[1]{y};
-
-    uint8_t* y_plane = const_cast<uint8_t*>(dest);
-    std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
-    memset(empty.get(), 0, cinfo->image_width);
-
-    int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
-    bool is_width_aligned = (aligned_width == cinfo->image_width);
-    std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
-    uint8_t* y_plane_intrm = nullptr;
-    JSAMPROW y_intrm[kCompressBatchSize];
-    JSAMPARRAY planes_intrm[1]{y_intrm};
-    if (!is_width_aligned) {
-        size_t mcu_row_size = aligned_width * kCompressBatchSize;
-        buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
-        y_plane_intrm = buffer_intrm.get();
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            y_intrm[i] = y_plane_intrm + i * aligned_width;
-        }
-    }
-
-    while (cinfo->output_scanline < cinfo->image_height) {
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            size_t scanline = cinfo->output_scanline + i;
-            if (scanline < cinfo->image_height) {
-                y[i] = y_plane + scanline * cinfo->image_width;
-            } else {
-                y[i] = empty.get();
-            }
-        }
-
-        int processed = jpeg_read_raw_data(cinfo, is_width_aligned ? planes : planes_intrm,
-                                           kCompressBatchSize);
-        if (processed != kCompressBatchSize / 2) {
-            ALOGE("Number of processed lines does not equal input lines.");
-            return false;
-        }
-        if (!is_width_aligned) {
-            for (int i = 0; i < kCompressBatchSize; ++i) {
-                memcpy(y[i], y_intrm[i], cinfo->image_width);
-            }
-        }
-    }
-    return true;
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/jpegencoderhelper.cpp b/libs/ultrahdr/jpegencoderhelper.cpp
deleted file mode 100644
index 13ae742..0000000
--- a/libs/ultrahdr/jpegencoderhelper.cpp
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * 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 <cstring>
-#include <memory>
-#include <vector>
-
-#include <ultrahdr/jpegencoderhelper.h>
-#include <utils/Log.h>
-
-namespace android::ultrahdr {
-
-// The destination manager that can access |mResultBuffer| in JpegEncoderHelper.
-struct destination_mgr {
-    struct jpeg_destination_mgr mgr;
-    JpegEncoderHelper* encoder;
-};
-
-JpegEncoderHelper::JpegEncoderHelper() {}
-
-JpegEncoderHelper::~JpegEncoderHelper() {}
-
-bool JpegEncoderHelper::compressImage(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width,
-                                      int height, int lumaStride, int chromaStride, int quality,
-                                      const void* iccBuffer, unsigned int iccSize) {
-    mResultBuffer.clear();
-    if (!encode(yBuffer, uvBuffer, width, height, lumaStride, chromaStride, quality, iccBuffer,
-                iccSize)) {
-        return false;
-    }
-    ALOGI("Compressed JPEG: %d[%dx%d] -> %zu bytes", (width * height * 12) / 8, width, height,
-          mResultBuffer.size());
-    return true;
-}
-
-void* JpegEncoderHelper::getCompressedImagePtr() {
-    return mResultBuffer.data();
-}
-
-size_t JpegEncoderHelper::getCompressedImageSize() {
-    return mResultBuffer.size();
-}
-
-void JpegEncoderHelper::initDestination(j_compress_ptr cinfo) {
-    destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
-    std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
-    buffer.resize(kBlockSize);
-    dest->mgr.next_output_byte = &buffer[0];
-    dest->mgr.free_in_buffer = buffer.size();
-}
-
-boolean JpegEncoderHelper::emptyOutputBuffer(j_compress_ptr cinfo) {
-    destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
-    std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
-    size_t oldsize = buffer.size();
-    buffer.resize(oldsize + kBlockSize);
-    dest->mgr.next_output_byte = &buffer[oldsize];
-    dest->mgr.free_in_buffer = kBlockSize;
-    return true;
-}
-
-void JpegEncoderHelper::terminateDestination(j_compress_ptr cinfo) {
-    destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
-    std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
-    buffer.resize(buffer.size() - dest->mgr.free_in_buffer);
-}
-
-void JpegEncoderHelper::outputErrorMessage(j_common_ptr cinfo) {
-    char buffer[JMSG_LENGTH_MAX];
-
-    /* Create the message */
-    (*cinfo->err->format_message)(cinfo, buffer);
-    ALOGE("%s\n", buffer);
-}
-
-bool JpegEncoderHelper::encode(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width,
-                               int height, int lumaStride, int chromaStride, int quality,
-                               const void* iccBuffer, unsigned int iccSize) {
-    jpeg_compress_struct cinfo;
-    jpeg_error_mgr jerr;
-
-    cinfo.err = jpeg_std_error(&jerr);
-    cinfo.err->output_message = &outputErrorMessage;
-    jpeg_create_compress(&cinfo);
-    setJpegDestination(&cinfo);
-    setJpegCompressStruct(width, height, quality, &cinfo, uvBuffer == nullptr);
-    jpeg_start_compress(&cinfo, TRUE);
-    if (iccBuffer != nullptr && iccSize > 0) {
-        jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast<const JOCTET*>(iccBuffer), iccSize);
-    }
-    bool status = cinfo.num_components == 1
-            ? compressY(&cinfo, yBuffer, lumaStride)
-            : compressYuv(&cinfo, yBuffer, uvBuffer, lumaStride, chromaStride);
-    jpeg_finish_compress(&cinfo);
-    jpeg_destroy_compress(&cinfo);
-
-    return status;
-}
-
-void JpegEncoderHelper::setJpegDestination(jpeg_compress_struct* cinfo) {
-    destination_mgr* dest = static_cast<struct destination_mgr*>(
-            (*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_PERMANENT,
-                                       sizeof(destination_mgr)));
-    dest->encoder = this;
-    dest->mgr.init_destination = &initDestination;
-    dest->mgr.empty_output_buffer = &emptyOutputBuffer;
-    dest->mgr.term_destination = &terminateDestination;
-    cinfo->dest = reinterpret_cast<struct jpeg_destination_mgr*>(dest);
-}
-
-void JpegEncoderHelper::setJpegCompressStruct(int width, int height, int quality,
-                                              jpeg_compress_struct* cinfo, bool isSingleChannel) {
-    cinfo->image_width = width;
-    cinfo->image_height = height;
-    cinfo->input_components = isSingleChannel ? 1 : 3;
-    cinfo->in_color_space = isSingleChannel ? JCS_GRAYSCALE : JCS_YCbCr;
-    jpeg_set_defaults(cinfo);
-    jpeg_set_quality(cinfo, quality, TRUE);
-    cinfo->raw_data_in = TRUE;
-    cinfo->dct_method = JDCT_ISLOW;
-    cinfo->comp_info[0].h_samp_factor = cinfo->in_color_space == JCS_GRAYSCALE ? 1 : 2;
-    cinfo->comp_info[0].v_samp_factor = cinfo->in_color_space == JCS_GRAYSCALE ? 1 : 2;
-    for (int i = 1; i < cinfo->num_components; i++) {
-        cinfo->comp_info[i].h_samp_factor = 1;
-        cinfo->comp_info[i].v_samp_factor = 1;
-    }
-}
-
-bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yBuffer,
-                                    const uint8_t* uvBuffer, int lumaStride, int chromaStride) {
-    JSAMPROW y[kCompressBatchSize];
-    JSAMPROW cb[kCompressBatchSize / 2];
-    JSAMPROW cr[kCompressBatchSize / 2];
-    JSAMPARRAY planes[3]{y, cb, cr};
-
-    size_t y_plane_size = lumaStride * cinfo->image_height;
-    size_t u_plane_size = chromaStride * cinfo->image_height / 2;
-    uint8_t* y_plane = const_cast<uint8_t*>(yBuffer);
-    uint8_t* u_plane = const_cast<uint8_t*>(uvBuffer);
-    uint8_t* v_plane = const_cast<uint8_t*>(u_plane + u_plane_size);
-    std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
-    memset(empty.get(), 0, cinfo->image_width);
-
-    const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
-    const bool need_padding = (lumaStride < aligned_width);
-    std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
-    uint8_t* y_plane_intrm = nullptr;
-    uint8_t* u_plane_intrm = nullptr;
-    uint8_t* v_plane_intrm = nullptr;
-    JSAMPROW y_intrm[kCompressBatchSize];
-    JSAMPROW cb_intrm[kCompressBatchSize / 2];
-    JSAMPROW cr_intrm[kCompressBatchSize / 2];
-    JSAMPARRAY planes_intrm[3]{y_intrm, cb_intrm, cr_intrm};
-    if (need_padding) {
-        size_t mcu_row_size = aligned_width * kCompressBatchSize * 3 / 2;
-        buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
-        y_plane_intrm = buffer_intrm.get();
-        u_plane_intrm = y_plane_intrm + (aligned_width * kCompressBatchSize);
-        v_plane_intrm = u_plane_intrm + (aligned_width * kCompressBatchSize) / 4;
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            y_intrm[i] = y_plane_intrm + i * aligned_width;
-            memset(y_intrm[i] + cinfo->image_width, 0, aligned_width - cinfo->image_width);
-        }
-        for (int i = 0; i < kCompressBatchSize / 2; ++i) {
-            int offset_intrm = i * (aligned_width / 2);
-            cb_intrm[i] = u_plane_intrm + offset_intrm;
-            cr_intrm[i] = v_plane_intrm + offset_intrm;
-            memset(cb_intrm[i] + cinfo->image_width / 2, 0,
-                   (aligned_width - cinfo->image_width) / 2);
-            memset(cr_intrm[i] + cinfo->image_width / 2, 0,
-                   (aligned_width - cinfo->image_width) / 2);
-        }
-    }
-
-    while (cinfo->next_scanline < cinfo->image_height) {
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            size_t scanline = cinfo->next_scanline + i;
-            if (scanline < cinfo->image_height) {
-                y[i] = y_plane + scanline * lumaStride;
-            } else {
-                y[i] = empty.get();
-            }
-            if (need_padding) {
-                memcpy(y_intrm[i], y[i], cinfo->image_width);
-            }
-        }
-        // cb, cr only have half scanlines
-        for (int i = 0; i < kCompressBatchSize / 2; ++i) {
-            size_t scanline = cinfo->next_scanline / 2 + i;
-            if (scanline < cinfo->image_height / 2) {
-                int offset = scanline * chromaStride;
-                cb[i] = u_plane + offset;
-                cr[i] = v_plane + offset;
-            } else {
-                cb[i] = cr[i] = empty.get();
-            }
-            if (need_padding) {
-                memcpy(cb_intrm[i], cb[i], cinfo->image_width / 2);
-                memcpy(cr_intrm[i], cr[i], cinfo->image_width / 2);
-            }
-        }
-        int processed = jpeg_write_raw_data(cinfo, need_padding ? planes_intrm : planes,
-                                            kCompressBatchSize);
-        if (processed != kCompressBatchSize) {
-            ALOGE("Number of processed lines does not equal input lines.");
-            return false;
-        }
-    }
-    return true;
-}
-
-bool JpegEncoderHelper::compressY(jpeg_compress_struct* cinfo, const uint8_t* yBuffer,
-                                  int lumaStride) {
-    JSAMPROW y[kCompressBatchSize];
-    JSAMPARRAY planes[1]{y};
-
-    uint8_t* y_plane = const_cast<uint8_t*>(yBuffer);
-    std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
-    memset(empty.get(), 0, cinfo->image_width);
-
-    const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
-    const bool need_padding = (lumaStride < aligned_width);
-    std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
-    uint8_t* y_plane_intrm = nullptr;
-    uint8_t* u_plane_intrm = nullptr;
-    JSAMPROW y_intrm[kCompressBatchSize];
-    JSAMPARRAY planes_intrm[]{y_intrm};
-    if (need_padding) {
-        size_t mcu_row_size = aligned_width * kCompressBatchSize;
-        buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
-        y_plane_intrm = buffer_intrm.get();
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            y_intrm[i] = y_plane_intrm + i * aligned_width;
-            memset(y_intrm[i] + cinfo->image_width, 0, aligned_width - cinfo->image_width);
-        }
-    }
-
-    while (cinfo->next_scanline < cinfo->image_height) {
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            size_t scanline = cinfo->next_scanline + i;
-            if (scanline < cinfo->image_height) {
-                y[i] = y_plane + scanline * lumaStride;
-            } else {
-                y[i] = empty.get();
-            }
-            if (need_padding) {
-                memcpy(y_intrm[i], y[i], cinfo->image_width);
-            }
-        }
-        int processed = jpeg_write_raw_data(cinfo, need_padding ? planes_intrm : planes,
-                                            kCompressBatchSize);
-        if (processed != kCompressBatchSize / 2) {
-            ALOGE("Number of processed lines does not equal input lines.");
-            return false;
-        }
-    }
-    return true;
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp
deleted file mode 100644
index 3d70fce..0000000
--- a/libs/ultrahdr/jpegr.cpp
+++ /dev/null
@@ -1,1503 +0,0 @@
-/*
- * 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 <cmath>
-#include <condition_variable>
-#include <deque>
-#include <memory>
-#include <mutex>
-#include <thread>
-
-#include <ultrahdr/gainmapmath.h>
-#include <ultrahdr/icc.h>
-#include <ultrahdr/jpegr.h>
-#include <ultrahdr/jpegrutils.h>
-#include <ultrahdr/multipictureformat.h>
-
-#include <image_io/base/data_segment_data_source.h>
-#include <image_io/jpeg/jpeg_info.h>
-#include <image_io/jpeg/jpeg_info_builder.h>
-#include <image_io/jpeg/jpeg_marker.h>
-#include <image_io/jpeg/jpeg_scanner.h>
-
-#include <utils/Log.h>
-
-using namespace std;
-using namespace photos_editing_formats::image_io;
-
-namespace android::ultrahdr {
-
-#define USE_SRGB_INVOETF_LUT 1
-#define USE_HLG_OETF_LUT 1
-#define USE_PQ_OETF_LUT 1
-#define USE_HLG_INVOETF_LUT 1
-#define USE_PQ_INVOETF_LUT 1
-#define USE_APPLY_GAIN_LUT 1
-
-#define JPEGR_CHECK(x)          \
-  {                             \
-    status_t status = (x);      \
-    if ((status) != NO_ERROR) { \
-      return status;            \
-    }                           \
-  }
-
-// JPEG compress quality (0 ~ 100) for gain map
-static const int kMapCompressQuality = 85;
-
-#define CONFIG_MULTITHREAD 1
-int GetCPUCoreCount() {
-  int cpuCoreCount = 1;
-#if CONFIG_MULTITHREAD
-#if defined(_SC_NPROCESSORS_ONLN)
-  cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN);
-#else
-  // _SC_NPROC_ONLN must be defined...
-  cpuCoreCount = sysconf(_SC_NPROC_ONLN);
-#endif
-#endif
-  return cpuCoreCount;
-}
-
-/*
- * Helper function copies the JPEG image from without EXIF.
- *
- * @param pDest destination of the data to be written.
- * @param pSource source of data being written.
- * @param exif_pos position of the EXIF package, which is aligned with jpegdecoder.getEXIFPos().
- *                 (4 bytes offset to FF sign, the byte after FF E1 XX XX <this byte>).
- * @param exif_size exif size without the initial 4 bytes, aligned with jpegdecoder.getEXIFSize().
- */
-static void copyJpegWithoutExif(jr_compressed_ptr pDest,
-                                jr_compressed_ptr pSource,
-                                size_t exif_pos,
-                                size_t exif_size) {
-  const size_t exif_offset = 4; //exif_pos has 4 bytes offset to the FF sign
-  pDest->length = pSource->length - exif_size - exif_offset;
-  pDest->data = new uint8_t[pDest->length];
-  pDest->maxLength = pDest->length;
-  pDest->colorGamut = pSource->colorGamut;
-  memcpy(pDest->data, pSource->data, exif_pos - exif_offset);
-  memcpy((uint8_t*)pDest->data + exif_pos - exif_offset,
-         (uint8_t*)pSource->data + exif_pos + exif_size,
-         pSource->length - exif_pos - exif_size);
-}
-
-status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
-                                       jr_uncompressed_ptr yuv420_image_ptr,
-                                       ultrahdr_transfer_function hdr_tf,
-                                       jr_compressed_ptr dest_ptr) {
-  if (p010_image_ptr == nullptr || p010_image_ptr->data == nullptr) {
-    ALOGE("Received nullptr for input p010 image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (p010_image_ptr->width % 2 != 0 || p010_image_ptr->height % 2 != 0) {
-    ALOGE("Image dimensions cannot be odd, image dimensions %dx%d", p010_image_ptr->width,
-          p010_image_ptr->height);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (p010_image_ptr->width < kMinWidth || p010_image_ptr->height < kMinHeight) {
-    ALOGE("Image dimensions cannot be less than %dx%d, image dimensions %dx%d", kMinWidth,
-          kMinHeight, p010_image_ptr->width, p010_image_ptr->height);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (p010_image_ptr->width > kMaxWidth || p010_image_ptr->height > kMaxHeight) {
-    ALOGE("Image dimensions cannot be larger than %dx%d, image dimensions %dx%d", kMaxWidth,
-          kMaxHeight, p010_image_ptr->width, p010_image_ptr->height);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (p010_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
-      p010_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
-    ALOGE("Unrecognized p010 color gamut %d", p010_image_ptr->colorGamut);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (p010_image_ptr->luma_stride != 0 && p010_image_ptr->luma_stride < p010_image_ptr->width) {
-    ALOGE("Luma stride must not be smaller than width, stride=%d, width=%d",
-          p010_image_ptr->luma_stride, p010_image_ptr->width);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (p010_image_ptr->chroma_data != nullptr &&
-      p010_image_ptr->chroma_stride < p010_image_ptr->width) {
-    ALOGE("Chroma stride must not be smaller than width, stride=%d, width=%d",
-          p010_image_ptr->chroma_stride, p010_image_ptr->width);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (dest_ptr == nullptr || dest_ptr->data == nullptr) {
-    ALOGE("Received nullptr for destination");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX || hdr_tf == ULTRAHDR_TF_SRGB) {
-    ALOGE("Invalid hdr transfer function %d", hdr_tf);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (yuv420_image_ptr == nullptr) {
-    return NO_ERROR;
-  }
-  if (yuv420_image_ptr->data == nullptr) {
-    ALOGE("Received nullptr for uncompressed 420 image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (yuv420_image_ptr->luma_stride != 0 &&
-      yuv420_image_ptr->luma_stride < yuv420_image_ptr->width) {
-    ALOGE("Luma stride must not be smaller than width, stride=%d, width=%d",
-          yuv420_image_ptr->luma_stride, yuv420_image_ptr->width);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (yuv420_image_ptr->chroma_data != nullptr &&
-      yuv420_image_ptr->chroma_stride < yuv420_image_ptr->width / 2) {
-    ALOGE("Chroma stride must not be smaller than (width / 2), stride=%d, width=%d",
-          yuv420_image_ptr->chroma_stride, yuv420_image_ptr->width);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (p010_image_ptr->width != yuv420_image_ptr->width ||
-      p010_image_ptr->height != yuv420_image_ptr->height) {
-    ALOGE("Image resolutions mismatch: P010: %dx%d, YUV420: %dx%d", p010_image_ptr->width,
-          p010_image_ptr->height, yuv420_image_ptr->width, yuv420_image_ptr->height);
-    return ERROR_JPEGR_RESOLUTION_MISMATCH;
-  }
-  if (yuv420_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
-      yuv420_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
-    ALOGE("Unrecognized 420 color gamut %d", yuv420_image_ptr->colorGamut);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  return NO_ERROR;
-}
-
-status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
-                                       jr_uncompressed_ptr yuv420_image_ptr,
-                                       ultrahdr_transfer_function hdr_tf,
-                                       jr_compressed_ptr dest_ptr, int quality) {
-  if (quality < 0 || quality > 100) {
-    ALOGE("quality factor is out side range [0-100], quality factor : %d", quality);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  return areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest_ptr);
-}
-
-/* Encode API-0 */
-status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf,
-                            jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
-  // validate input arguments
-  if (auto ret = areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest, quality);
-      ret != NO_ERROR) {
-    return ret;
-  }
-  if (exif != nullptr && exif->data == nullptr) {
-    ALOGE("received nullptr for exif metadata");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-
-  // clean up input structure for later usage
-  jpegr_uncompressed_struct p010_image = *p010_image_ptr;
-  if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
-  if (!p010_image.chroma_data) {
-    uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
-    p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
-    p010_image.chroma_stride = p010_image.luma_stride;
-  }
-
-  const int yu420_luma_stride = ALIGNM(p010_image.width, kJpegBlock);
-  unique_ptr<uint8_t[]> yuv420_image_data =
-          make_unique<uint8_t[]>(yu420_luma_stride * p010_image.height * 3 / 2);
-  jpegr_uncompressed_struct yuv420_image = {.data = yuv420_image_data.get(),
-                                            .width = p010_image.width,
-                                            .height = p010_image.height,
-                                            .colorGamut = p010_image.colorGamut,
-                                            .luma_stride = yu420_luma_stride,
-                                            .chroma_data = nullptr,
-                                            .chroma_stride = yu420_luma_stride >> 1};
-  uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
-  yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
-
-  // tone map
-  JPEGR_CHECK(toneMap(&p010_image, &yuv420_image));
-
-  // gain map
-  ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
-  jpegr_uncompressed_struct gainmap_image;
-  JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
-  std::unique_ptr<uint8_t[]> map_data;
-  map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
-
-  // compress gain map
-  JpegEncoderHelper jpeg_enc_obj_gm;
-  JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
-  jpegr_compressed_struct compressed_map = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
-                                            .length = static_cast<int>(
-                                                    jpeg_enc_obj_gm.getCompressedImageSize()),
-                                            .maxLength = static_cast<int>(
-                                                    jpeg_enc_obj_gm.getCompressedImageSize()),
-                                            .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-
-  sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut);
-
-  // convert to Bt601 YUV encoding for JPEG encode
-  if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) {
-    JPEGR_CHECK(convertYuv(&yuv420_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
-  }
-
-  // compress 420 image
-  JpegEncoderHelper jpeg_enc_obj_yuv420;
-  if (!jpeg_enc_obj_yuv420.compressImage(reinterpret_cast<uint8_t*>(yuv420_image.data),
-                                         reinterpret_cast<uint8_t*>(yuv420_image.chroma_data),
-                                         yuv420_image.width, yuv420_image.height,
-                                         yuv420_image.luma_stride, yuv420_image.chroma_stride,
-                                         quality, icc->getData(), icc->getLength())) {
-    return ERROR_JPEGR_ENCODE_ERROR;
-  }
-  jpegr_compressed_struct jpeg = {.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(),
-                                  .length = static_cast<int>(
-                                          jpeg_enc_obj_yuv420.getCompressedImageSize()),
-                                  .maxLength = static_cast<int>(
-                                          jpeg_enc_obj_yuv420.getCompressedImageSize()),
-                                  .colorGamut = yuv420_image.colorGamut};
-
-  // append gain map, no ICC since JPEG encode already did it
-  JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
-                            &metadata, dest));
-
-  return NO_ERROR;
-}
-
-/* Encode API-1 */
-status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
-                            jr_uncompressed_ptr yuv420_image_ptr, ultrahdr_transfer_function hdr_tf,
-                            jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
-  // validate input arguments
-  if (yuv420_image_ptr == nullptr) {
-    ALOGE("received nullptr for uncompressed 420 image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (exif != nullptr && exif->data == nullptr) {
-    ALOGE("received nullptr for exif metadata");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (auto ret = areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest, quality);
-      ret != NO_ERROR) {
-    return ret;
-  }
-
-  // clean up input structure for later usage
-  jpegr_uncompressed_struct p010_image = *p010_image_ptr;
-  if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
-  if (!p010_image.chroma_data) {
-    uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
-    p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
-    p010_image.chroma_stride = p010_image.luma_stride;
-  }
-  jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
-  if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
-  if (!yuv420_image.chroma_data) {
-    uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
-    yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
-    yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
-  }
-
-  // gain map
-  ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
-  jpegr_uncompressed_struct gainmap_image;
-  JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
-  std::unique_ptr<uint8_t[]> map_data;
-  map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
-
-  // compress gain map
-  JpegEncoderHelper jpeg_enc_obj_gm;
-  JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
-  jpegr_compressed_struct compressed_map = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
-                                            .length = static_cast<int>(
-                                                    jpeg_enc_obj_gm.getCompressedImageSize()),
-                                            .maxLength = static_cast<int>(
-                                                    jpeg_enc_obj_gm.getCompressedImageSize()),
-                                            .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-
-  sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut);
-
-  jpegr_uncompressed_struct yuv420_bt601_image = yuv420_image;
-  unique_ptr<uint8_t[]> yuv_420_bt601_data;
-  // Convert to bt601 YUV encoding for JPEG encode
-  if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) {
-    const int yuv_420_bt601_luma_stride = ALIGNM(yuv420_image.width, kJpegBlock);
-    yuv_420_bt601_data =
-            make_unique<uint8_t[]>(yuv_420_bt601_luma_stride * yuv420_image.height * 3 / 2);
-    yuv420_bt601_image.data = yuv_420_bt601_data.get();
-    yuv420_bt601_image.colorGamut = yuv420_image.colorGamut;
-    yuv420_bt601_image.luma_stride = yuv_420_bt601_luma_stride;
-    uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_bt601_image.data);
-    yuv420_bt601_image.chroma_data = data + yuv_420_bt601_luma_stride * yuv420_image.height;
-    yuv420_bt601_image.chroma_stride = yuv_420_bt601_luma_stride >> 1;
-
-    {
-      // copy luma
-      uint8_t* y_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.data);
-      uint8_t* y_src = reinterpret_cast<uint8_t*>(yuv420_image.data);
-      if (yuv420_bt601_image.luma_stride == yuv420_image.luma_stride) {
-        memcpy(y_dst, y_src, yuv420_bt601_image.luma_stride * yuv420_image.height);
-      } else {
-        for (size_t i = 0; i < yuv420_image.height; i++) {
-          memcpy(y_dst, y_src, yuv420_image.width);
-          if (yuv420_image.width != yuv420_bt601_image.luma_stride) {
-            memset(y_dst + yuv420_image.width, 0,
-                   yuv420_bt601_image.luma_stride - yuv420_image.width);
-          }
-          y_dst += yuv420_bt601_image.luma_stride;
-          y_src += yuv420_image.luma_stride;
-        }
-      }
-    }
-
-    if (yuv420_bt601_image.chroma_stride == yuv420_image.chroma_stride) {
-      // copy luma
-      uint8_t* ch_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data);
-      uint8_t* ch_src = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
-      memcpy(ch_dst, ch_src, yuv420_bt601_image.chroma_stride * yuv420_image.height);
-    } else {
-      // copy cb & cr
-      uint8_t* cb_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data);
-      uint8_t* cb_src = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
-      uint8_t* cr_dst = cb_dst + (yuv420_bt601_image.chroma_stride * yuv420_bt601_image.height / 2);
-      uint8_t* cr_src = cb_src + (yuv420_image.chroma_stride * yuv420_image.height / 2);
-      for (size_t i = 0; i < yuv420_image.height / 2; i++) {
-        memcpy(cb_dst, cb_src, yuv420_image.width / 2);
-        memcpy(cr_dst, cr_src, yuv420_image.width / 2);
-        if (yuv420_bt601_image.width / 2 != yuv420_bt601_image.chroma_stride) {
-          memset(cb_dst + yuv420_image.width / 2, 0,
-                 yuv420_bt601_image.chroma_stride - yuv420_image.width / 2);
-          memset(cr_dst + yuv420_image.width / 2, 0,
-                 yuv420_bt601_image.chroma_stride - yuv420_image.width / 2);
-        }
-        cb_dst += yuv420_bt601_image.chroma_stride;
-        cb_src += yuv420_image.chroma_stride;
-        cr_dst += yuv420_bt601_image.chroma_stride;
-        cr_src += yuv420_image.chroma_stride;
-      }
-    }
-    JPEGR_CHECK(convertYuv(&yuv420_bt601_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
-  }
-
-  // compress 420 image
-  JpegEncoderHelper jpeg_enc_obj_yuv420;
-  if (!jpeg_enc_obj_yuv420.compressImage(reinterpret_cast<uint8_t*>(yuv420_bt601_image.data),
-                                         reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data),
-                                         yuv420_bt601_image.width, yuv420_bt601_image.height,
-                                         yuv420_bt601_image.luma_stride,
-                                         yuv420_bt601_image.chroma_stride, quality, icc->getData(),
-                                         icc->getLength())) {
-    return ERROR_JPEGR_ENCODE_ERROR;
-  }
-
-  jpegr_compressed_struct jpeg = {.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(),
-                                  .length = static_cast<int>(
-                                          jpeg_enc_obj_yuv420.getCompressedImageSize()),
-                                  .maxLength = static_cast<int>(
-                                          jpeg_enc_obj_yuv420.getCompressedImageSize()),
-                                  .colorGamut = yuv420_image.colorGamut};
-
-  // append gain map, no ICC since JPEG encode already did it
-  JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
-                            &metadata, dest));
-  return NO_ERROR;
-}
-
-/* Encode API-2 */
-status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
-                            jr_uncompressed_ptr yuv420_image_ptr,
-                            jr_compressed_ptr yuv420jpg_image_ptr,
-                            ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
-  // validate input arguments
-  if (yuv420_image_ptr == nullptr) {
-    ALOGE("received nullptr for uncompressed 420 image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
-    ALOGE("received nullptr for compressed jpeg image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (auto ret = areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest);
-      ret != NO_ERROR) {
-    return ret;
-  }
-
-  // clean up input structure for later usage
-  jpegr_uncompressed_struct p010_image = *p010_image_ptr;
-  if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
-  if (!p010_image.chroma_data) {
-    uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
-    p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
-    p010_image.chroma_stride = p010_image.luma_stride;
-  }
-  jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
-  if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
-  if (!yuv420_image.chroma_data) {
-    uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
-    yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height;
-    yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
-  }
-
-  // gain map
-  ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
-  jpegr_uncompressed_struct gainmap_image;
-  JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
-  std::unique_ptr<uint8_t[]> map_data;
-  map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
-
-  // compress gain map
-  JpegEncoderHelper jpeg_enc_obj_gm;
-  JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
-  jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
-                                              .length = static_cast<int>(
-                                                      jpeg_enc_obj_gm.getCompressedImageSize()),
-                                              .maxLength = static_cast<int>(
-                                                      jpeg_enc_obj_gm.getCompressedImageSize()),
-                                              .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-
-  return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest);
-}
-
-/* Encode API-3 */
-status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
-                            jr_compressed_ptr yuv420jpg_image_ptr,
-                            ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
-  // validate input arguments
-  if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
-    ALOGE("received nullptr for compressed jpeg image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (auto ret = areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest); ret != NO_ERROR) {
-    return ret;
-  }
-
-  // clean up input structure for later usage
-  jpegr_uncompressed_struct p010_image = *p010_image_ptr;
-  if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
-  if (!p010_image.chroma_data) {
-    uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
-    p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
-    p010_image.chroma_stride = p010_image.luma_stride;
-  }
-
-  // decode input jpeg, gamut is going to be bt601.
-  JpegDecoderHelper jpeg_dec_obj_yuv420;
-  if (!jpeg_dec_obj_yuv420.decompressImage(yuv420jpg_image_ptr->data,
-                                           yuv420jpg_image_ptr->length)) {
-    return ERROR_JPEGR_DECODE_ERROR;
-  }
-  jpegr_uncompressed_struct yuv420_image{};
-  yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr();
-  yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
-  yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
-  yuv420_image.colorGamut = yuv420jpg_image_ptr->colorGamut;
-  if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
-  if (!yuv420_image.chroma_data) {
-    uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
-    yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height;
-    yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
-  }
-
-  if (p010_image_ptr->width != yuv420_image.width ||
-      p010_image_ptr->height != yuv420_image.height) {
-    return ERROR_JPEGR_RESOLUTION_MISMATCH;
-  }
-
-  // gain map
-  ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
-  jpegr_uncompressed_struct gainmap_image;
-  JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image,
-                              true /* sdr_is_601 */));
-  std::unique_ptr<uint8_t[]> map_data;
-  map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
-
-  // compress gain map
-  JpegEncoderHelper jpeg_enc_obj_gm;
-  JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
-  jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
-                                              .length = static_cast<int>(
-                                                      jpeg_enc_obj_gm.getCompressedImageSize()),
-                                              .maxLength = static_cast<int>(
-                                                      jpeg_enc_obj_gm.getCompressedImageSize()),
-                                              .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-
-  return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest);
-}
-
-/* Encode API-4 */
-status_t JpegR::encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr,
-                            jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata,
-                            jr_compressed_ptr dest) {
-  if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
-    ALOGE("received nullptr for compressed jpeg image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (gainmapjpg_image_ptr == nullptr || gainmapjpg_image_ptr->data == nullptr) {
-    ALOGE("received nullptr for compressed gain map");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (dest == nullptr || dest->data == nullptr) {
-    ALOGE("received nullptr for destination");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-
-  // We just want to check if ICC is present, so don't do a full decode. Note,
-  // this doesn't verify that the ICC is valid.
-  JpegDecoderHelper decoder;
-  std::vector<uint8_t> icc;
-  decoder.getCompressedImageParameters(yuv420jpg_image_ptr->data, yuv420jpg_image_ptr->length,
-                                       /* pWidth */ nullptr, /* pHeight */ nullptr, &icc,
-                                       /* exifData */ nullptr);
-
-  // Add ICC if not already present.
-  if (icc.size() > 0) {
-    JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
-                              /* icc */ nullptr, /* icc size */ 0, metadata, dest));
-  } else {
-    sp<DataStruct> newIcc =
-            IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420jpg_image_ptr->colorGamut);
-    JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
-                              newIcc->getData(), newIcc->getLength(), metadata, dest));
-  }
-
-  return NO_ERROR;
-}
-
-status_t JpegR::getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpeg_image_info_ptr) {
-  if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
-    ALOGE("received nullptr for compressed jpegr image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (jpeg_image_info_ptr == nullptr) {
-    ALOGE("received nullptr for compressed jpegr info struct");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-
-  jpegr_compressed_struct primary_image, gainmap_image;
-  status_t status = extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_image, &gainmap_image);
-  if (status != NO_ERROR && status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
-    return status;
-  }
-
-  JpegDecoderHelper jpeg_dec_obj_hdr;
-  if (!jpeg_dec_obj_hdr.getCompressedImageParameters(primary_image.data, primary_image.length,
-                                                     &jpeg_image_info_ptr->width,
-                                                     &jpeg_image_info_ptr->height,
-                                                     jpeg_image_info_ptr->iccData,
-                                                     jpeg_image_info_ptr->exifData)) {
-    return ERROR_JPEGR_DECODE_ERROR;
-  }
-
-  return status;
-}
-
-/* Decode API */
-status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest,
-                            float max_display_boost, jr_exif_ptr exif,
-                            ultrahdr_output_format output_format,
-                            jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata) {
-  if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
-    ALOGE("received nullptr for compressed jpegr image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (dest == nullptr || dest->data == nullptr) {
-    ALOGE("received nullptr for dest image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (max_display_boost < 1.0f) {
-    ALOGE("received bad value for max_display_boost %f", max_display_boost);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (exif != nullptr && exif->data == nullptr) {
-    ALOGE("received nullptr address for exif data");
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (output_format <= ULTRAHDR_OUTPUT_UNSPECIFIED || output_format > ULTRAHDR_OUTPUT_MAX) {
-    ALOGE("received bad value for output format %d", output_format);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-
-  jpegr_compressed_struct primary_jpeg_image, gainmap_jpeg_image;
-  status_t status =
-          extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_jpeg_image, &gainmap_jpeg_image);
-  if (status != NO_ERROR) {
-    if (output_format != ULTRAHDR_OUTPUT_SDR || status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
-      ALOGE("received invalid compressed jpegr image");
-      return status;
-    }
-  }
-
-  JpegDecoderHelper jpeg_dec_obj_yuv420;
-  if (!jpeg_dec_obj_yuv420.decompressImage(primary_jpeg_image.data, primary_jpeg_image.length,
-                                           (output_format == ULTRAHDR_OUTPUT_SDR))) {
-    return ERROR_JPEGR_DECODE_ERROR;
-  }
-
-  if (output_format == ULTRAHDR_OUTPUT_SDR) {
-    if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
-         jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 4) >
-        jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
-      return ERROR_JPEGR_CALCULATION_ERROR;
-    }
-  } else {
-    if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
-         jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 3 / 2) >
-        jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
-      return ERROR_JPEGR_CALCULATION_ERROR;
-    }
-  }
-
-  if (exif != nullptr) {
-    if (exif->data == nullptr) {
-      return ERROR_JPEGR_INVALID_NULL_PTR;
-    }
-    if (exif->length < jpeg_dec_obj_yuv420.getEXIFSize()) {
-      return ERROR_JPEGR_BUFFER_TOO_SMALL;
-    }
-    memcpy(exif->data, jpeg_dec_obj_yuv420.getEXIFPtr(), jpeg_dec_obj_yuv420.getEXIFSize());
-    exif->length = jpeg_dec_obj_yuv420.getEXIFSize();
-  }
-
-  if (output_format == ULTRAHDR_OUTPUT_SDR) {
-    dest->width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
-    dest->height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
-    memcpy(dest->data, jpeg_dec_obj_yuv420.getDecompressedImagePtr(),
-           dest->width * dest->height * 4);
-    return NO_ERROR;
-  }
-
-  JpegDecoderHelper jpeg_dec_obj_gm;
-  if (!jpeg_dec_obj_gm.decompressImage(gainmap_jpeg_image.data, gainmap_jpeg_image.length)) {
-    return ERROR_JPEGR_DECODE_ERROR;
-  }
-  if ((jpeg_dec_obj_gm.getDecompressedImageWidth() * jpeg_dec_obj_gm.getDecompressedImageHeight()) >
-      jpeg_dec_obj_gm.getDecompressedImageSize()) {
-    return ERROR_JPEGR_CALCULATION_ERROR;
-  }
-
-  jpegr_uncompressed_struct gainmap_image;
-  gainmap_image.data = jpeg_dec_obj_gm.getDecompressedImagePtr();
-  gainmap_image.width = jpeg_dec_obj_gm.getDecompressedImageWidth();
-  gainmap_image.height = jpeg_dec_obj_gm.getDecompressedImageHeight();
-
-  if (gainmap_image_ptr != nullptr) {
-    gainmap_image_ptr->width = gainmap_image.width;
-    gainmap_image_ptr->height = gainmap_image.height;
-    int size = gainmap_image_ptr->width * gainmap_image_ptr->height;
-    gainmap_image_ptr->data = malloc(size);
-    memcpy(gainmap_image_ptr->data, gainmap_image.data, size);
-  }
-
-  ultrahdr_metadata_struct uhdr_metadata;
-  if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_dec_obj_gm.getXMPPtr()),
-                          jpeg_dec_obj_gm.getXMPSize(), &uhdr_metadata)) {
-    return ERROR_JPEGR_INVALID_METADATA;
-  }
-
-  if (metadata != nullptr) {
-    metadata->version = uhdr_metadata.version;
-    metadata->minContentBoost = uhdr_metadata.minContentBoost;
-    metadata->maxContentBoost = uhdr_metadata.maxContentBoost;
-    metadata->gamma = uhdr_metadata.gamma;
-    metadata->offsetSdr = uhdr_metadata.offsetSdr;
-    metadata->offsetHdr = uhdr_metadata.offsetHdr;
-    metadata->hdrCapacityMin = uhdr_metadata.hdrCapacityMin;
-    metadata->hdrCapacityMax = uhdr_metadata.hdrCapacityMax;
-  }
-
-  jpegr_uncompressed_struct yuv420_image;
-  yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr();
-  yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
-  yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
-  yuv420_image.colorGamut = IccHelper::readIccColorGamut(jpeg_dec_obj_yuv420.getICCPtr(),
-                                                         jpeg_dec_obj_yuv420.getICCSize());
-  yuv420_image.luma_stride = yuv420_image.width;
-  uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
-  yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
-  yuv420_image.chroma_stride = yuv420_image.width >> 1;
-
-  JPEGR_CHECK(applyGainMap(&yuv420_image, &gainmap_image, &uhdr_metadata, output_format,
-                           max_display_boost, dest));
-  return NO_ERROR;
-}
-
-status_t JpegR::compressGainMap(jr_uncompressed_ptr gainmap_image_ptr,
-                                JpegEncoderHelper* jpeg_enc_obj_ptr) {
-  if (gainmap_image_ptr == nullptr || jpeg_enc_obj_ptr == nullptr) {
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-
-  // Don't need to convert YUV to Bt601 since single channel
-  if (!jpeg_enc_obj_ptr->compressImage(reinterpret_cast<uint8_t*>(gainmap_image_ptr->data), nullptr,
-                                       gainmap_image_ptr->width, gainmap_image_ptr->height,
-                                       gainmap_image_ptr->luma_stride, 0, kMapCompressQuality,
-                                       nullptr, 0)) {
-    return ERROR_JPEGR_ENCODE_ERROR;
-  }
-
-  return NO_ERROR;
-}
-
-const int kJobSzInRows = 16;
-static_assert(kJobSzInRows > 0 && kJobSzInRows % kMapDimensionScaleFactor == 0,
-              "align job size to kMapDimensionScaleFactor");
-
-class JobQueue {
-public:
-  bool dequeueJob(size_t& rowStart, size_t& rowEnd);
-  void enqueueJob(size_t rowStart, size_t rowEnd);
-  void markQueueForEnd();
-  void reset();
-
-private:
-  bool mQueuedAllJobs = false;
-  std::deque<std::tuple<size_t, size_t>> mJobs;
-  std::mutex mMutex;
-  std::condition_variable mCv;
-};
-
-bool JobQueue::dequeueJob(size_t& rowStart, size_t& rowEnd) {
-  std::unique_lock<std::mutex> lock{mMutex};
-  while (true) {
-    if (mJobs.empty()) {
-      if (mQueuedAllJobs) {
-        return false;
-      } else {
-        mCv.wait_for(lock, std::chrono::milliseconds(100));
-      }
-    } else {
-      auto it = mJobs.begin();
-      rowStart = std::get<0>(*it);
-      rowEnd = std::get<1>(*it);
-      mJobs.erase(it);
-      return true;
-    }
-  }
-  return false;
-}
-
-void JobQueue::enqueueJob(size_t rowStart, size_t rowEnd) {
-  std::unique_lock<std::mutex> lock{mMutex};
-  mJobs.push_back(std::make_tuple(rowStart, rowEnd));
-  lock.unlock();
-  mCv.notify_one();
-}
-
-void JobQueue::markQueueForEnd() {
-  std::unique_lock<std::mutex> lock{mMutex};
-  mQueuedAllJobs = true;
-  lock.unlock();
-  mCv.notify_all();
-}
-
-void JobQueue::reset() {
-  std::unique_lock<std::mutex> lock{mMutex};
-  mJobs.clear();
-  mQueuedAllJobs = false;
-}
-
-status_t JpegR::generateGainMap(jr_uncompressed_ptr yuv420_image_ptr,
-                                jr_uncompressed_ptr p010_image_ptr,
-                                ultrahdr_transfer_function hdr_tf, ultrahdr_metadata_ptr metadata,
-                                jr_uncompressed_ptr dest, bool sdr_is_601) {
-  if (yuv420_image_ptr == nullptr || p010_image_ptr == nullptr || metadata == nullptr ||
-      dest == nullptr || yuv420_image_ptr->data == nullptr ||
-      yuv420_image_ptr->chroma_data == nullptr || p010_image_ptr->data == nullptr ||
-      p010_image_ptr->chroma_data == nullptr) {
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (yuv420_image_ptr->width != p010_image_ptr->width ||
-      yuv420_image_ptr->height != p010_image_ptr->height) {
-    return ERROR_JPEGR_RESOLUTION_MISMATCH;
-  }
-  if (yuv420_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
-      p010_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
-    return ERROR_JPEGR_INVALID_COLORGAMUT;
-  }
-
-  size_t image_width = yuv420_image_ptr->width;
-  size_t image_height = yuv420_image_ptr->height;
-  size_t map_width = image_width / kMapDimensionScaleFactor;
-  size_t map_height = image_height / kMapDimensionScaleFactor;
-
-  dest->data = new uint8_t[map_width * map_height];
-  dest->width = map_width;
-  dest->height = map_height;
-  dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-  dest->luma_stride = map_width;
-  dest->chroma_data = nullptr;
-  dest->chroma_stride = 0;
-  std::unique_ptr<uint8_t[]> map_data;
-  map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
-
-  ColorTransformFn hdrInvOetf = nullptr;
-  float hdr_white_nits;
-  switch (hdr_tf) {
-    case ULTRAHDR_TF_LINEAR:
-      hdrInvOetf = identityConversion;
-      // Note: this will produce clipping if the input exceeds kHlgMaxNits.
-      // TODO: TF LINEAR will be deprecated.
-      hdr_white_nits = kHlgMaxNits;
-      break;
-    case ULTRAHDR_TF_HLG:
-#if USE_HLG_INVOETF_LUT
-      hdrInvOetf = hlgInvOetfLUT;
-#else
-      hdrInvOetf = hlgInvOetf;
-#endif
-      hdr_white_nits = kHlgMaxNits;
-      break;
-    case ULTRAHDR_TF_PQ:
-#if USE_PQ_INVOETF_LUT
-      hdrInvOetf = pqInvOetfLUT;
-#else
-      hdrInvOetf = pqInvOetf;
-#endif
-      hdr_white_nits = kPqMaxNits;
-      break;
-    default:
-      // Should be impossible to hit after input validation.
-      return ERROR_JPEGR_INVALID_TRANS_FUNC;
-  }
-
-  metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits;
-  metadata->minContentBoost = 1.0f;
-  metadata->gamma = 1.0f;
-  metadata->offsetSdr = 0.0f;
-  metadata->offsetHdr = 0.0f;
-  metadata->hdrCapacityMin = 1.0f;
-  metadata->hdrCapacityMax = metadata->maxContentBoost;
-
-  float log2MinBoost = log2(metadata->minContentBoost);
-  float log2MaxBoost = log2(metadata->maxContentBoost);
-
-  ColorTransformFn hdrGamutConversionFn =
-          getHdrConversionFn(yuv420_image_ptr->colorGamut, p010_image_ptr->colorGamut);
-
-  ColorCalculationFn luminanceFn = nullptr;
-  ColorTransformFn sdrYuvToRgbFn = nullptr;
-  switch (yuv420_image_ptr->colorGamut) {
-    case ULTRAHDR_COLORGAMUT_BT709:
-      luminanceFn = srgbLuminance;
-      sdrYuvToRgbFn = srgbYuvToRgb;
-      break;
-    case ULTRAHDR_COLORGAMUT_P3:
-      luminanceFn = p3Luminance;
-      sdrYuvToRgbFn = p3YuvToRgb;
-      break;
-    case ULTRAHDR_COLORGAMUT_BT2100:
-      luminanceFn = bt2100Luminance;
-      sdrYuvToRgbFn = bt2100YuvToRgb;
-      break;
-    case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
-      // Should be impossible to hit after input validation.
-      return ERROR_JPEGR_INVALID_COLORGAMUT;
-  }
-  if (sdr_is_601) {
-    sdrYuvToRgbFn = p3YuvToRgb;
-  }
-
-  ColorTransformFn hdrYuvToRgbFn = nullptr;
-  switch (p010_image_ptr->colorGamut) {
-    case ULTRAHDR_COLORGAMUT_BT709:
-      hdrYuvToRgbFn = srgbYuvToRgb;
-      break;
-    case ULTRAHDR_COLORGAMUT_P3:
-      hdrYuvToRgbFn = p3YuvToRgb;
-      break;
-    case ULTRAHDR_COLORGAMUT_BT2100:
-      hdrYuvToRgbFn = bt2100YuvToRgb;
-      break;
-    case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
-      // Should be impossible to hit after input validation.
-      return ERROR_JPEGR_INVALID_COLORGAMUT;
-  }
-
-  std::mutex mutex;
-  const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
-  size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
-  JobQueue jobQueue;
-
-  std::function<void()> generateMap = [yuv420_image_ptr, p010_image_ptr, metadata, dest, hdrInvOetf,
-                                       hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn,
-                                       hdrYuvToRgbFn, hdr_white_nits, log2MinBoost, log2MaxBoost,
-                                       &jobQueue]() -> void {
-    size_t rowStart, rowEnd;
-    while (jobQueue.dequeueJob(rowStart, rowEnd)) {
-      for (size_t y = rowStart; y < rowEnd; ++y) {
-        for (size_t x = 0; x < dest->width; ++x) {
-          Color sdr_yuv_gamma = sampleYuv420(yuv420_image_ptr, kMapDimensionScaleFactor, x, y);
-          Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma);
-          // We are assuming the SDR input is always sRGB transfer.
-#if USE_SRGB_INVOETF_LUT
-          Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
-#else
-          Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
-#endif
-          float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
-
-          Color hdr_yuv_gamma = sampleP010(p010_image_ptr, kMapDimensionScaleFactor, x, y);
-          Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
-          Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
-          hdr_rgb = hdrGamutConversionFn(hdr_rgb);
-          float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
-
-          size_t pixel_idx = x + y * dest->width;
-          reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
-                  encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost);
-        }
-      }
-    }
-  };
-
-  // generate map
-  std::vector<std::thread> workers;
-  for (int th = 0; th < threads - 1; th++) {
-    workers.push_back(std::thread(generateMap));
-  }
-
-  rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor;
-  for (size_t rowStart = 0; rowStart < map_height;) {
-    size_t rowEnd = std::min(rowStart + rowStep, map_height);
-    jobQueue.enqueueJob(rowStart, rowEnd);
-    rowStart = rowEnd;
-  }
-  jobQueue.markQueueForEnd();
-  generateMap();
-  std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
-
-  map_data.release();
-  return NO_ERROR;
-}
-
-status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr,
-                             jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata,
-                             ultrahdr_output_format output_format, float max_display_boost,
-                             jr_uncompressed_ptr dest) {
-  if (yuv420_image_ptr == nullptr || gainmap_image_ptr == nullptr || metadata == nullptr ||
-      dest == nullptr || yuv420_image_ptr->data == nullptr ||
-      yuv420_image_ptr->chroma_data == nullptr || gainmap_image_ptr->data == nullptr) {
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (metadata->version.compare(kJpegrVersion)) {
-    ALOGE("Unsupported metadata version: %s", metadata->version.c_str());
-    return ERROR_JPEGR_UNSUPPORTED_METADATA;
-  }
-  if (metadata->gamma != 1.0f) {
-    ALOGE("Unsupported metadata gamma: %f", metadata->gamma);
-    return ERROR_JPEGR_UNSUPPORTED_METADATA;
-  }
-  if (metadata->offsetSdr != 0.0f || metadata->offsetHdr != 0.0f) {
-    ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr, metadata->offsetHdr);
-    return ERROR_JPEGR_UNSUPPORTED_METADATA;
-  }
-  if (metadata->hdrCapacityMin != metadata->minContentBoost ||
-      metadata->hdrCapacityMax != metadata->maxContentBoost) {
-    ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin,
-          metadata->hdrCapacityMax);
-    return ERROR_JPEGR_UNSUPPORTED_METADATA;
-  }
-
-  // TODO: remove once map scaling factor is computed based on actual map dims
-  size_t image_width = yuv420_image_ptr->width;
-  size_t image_height = yuv420_image_ptr->height;
-  size_t map_width = image_width / kMapDimensionScaleFactor;
-  size_t map_height = image_height / kMapDimensionScaleFactor;
-  if (map_width != gainmap_image_ptr->width || map_height != gainmap_image_ptr->height) {
-    ALOGE("gain map dimensions and primary image dimensions are not to scale, computed gain map "
-          "resolution is %dx%d, received gain map resolution is %dx%d",
-          (int)map_width, (int)map_height, gainmap_image_ptr->width, gainmap_image_ptr->height);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-
-  dest->width = yuv420_image_ptr->width;
-  dest->height = yuv420_image_ptr->height;
-  ShepardsIDW idwTable(kMapDimensionScaleFactor);
-  float display_boost = std::min(max_display_boost, metadata->maxContentBoost);
-  GainLUT gainLUT(metadata, display_boost);
-
-  JobQueue jobQueue;
-  std::function<void()> applyRecMap = [yuv420_image_ptr, gainmap_image_ptr, metadata, dest,
-                                       &jobQueue, &idwTable, output_format, &gainLUT,
-                                       display_boost]() -> void {
-    size_t width = yuv420_image_ptr->width;
-    size_t height = yuv420_image_ptr->height;
-
-    size_t rowStart, rowEnd;
-    while (jobQueue.dequeueJob(rowStart, rowEnd)) {
-      for (size_t y = rowStart; y < rowEnd; ++y) {
-        for (size_t x = 0; x < width; ++x) {
-          Color yuv_gamma_sdr = getYuv420Pixel(yuv420_image_ptr, x, y);
-          // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients
-          Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr);
-          // We are assuming the SDR base image is always sRGB transfer.
-#if USE_SRGB_INVOETF_LUT
-          Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
-#else
-          Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
-#endif
-          float gain;
-          // TODO: determine map scaling factor based on actual map dims
-          size_t map_scale_factor = kMapDimensionScaleFactor;
-          // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
-          // Currently map_scale_factor is of type size_t, but it could be changed to a float
-          // later.
-          if (map_scale_factor != floorf(map_scale_factor)) {
-            gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y);
-          } else {
-            gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y, idwTable);
-          }
-
-#if USE_APPLY_GAIN_LUT
-          Color rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT);
-#else
-          Color rgb_hdr = applyGain(rgb_sdr, gain, metadata, display_boost);
-#endif
-          rgb_hdr = rgb_hdr / display_boost;
-          size_t pixel_idx = x + y * width;
-
-          switch (output_format) {
-            case ULTRAHDR_OUTPUT_HDR_LINEAR: {
-              uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr);
-              reinterpret_cast<uint64_t*>(dest->data)[pixel_idx] = rgba_f16;
-              break;
-            }
-            case ULTRAHDR_OUTPUT_HDR_HLG: {
-#if USE_HLG_OETF_LUT
-              ColorTransformFn hdrOetf = hlgOetfLUT;
-#else
-              ColorTransformFn hdrOetf = hlgOetf;
-#endif
-              Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
-              uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
-              reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
-              break;
-            }
-            case ULTRAHDR_OUTPUT_HDR_PQ: {
-#if USE_PQ_OETF_LUT
-              ColorTransformFn hdrOetf = pqOetfLUT;
-#else
-              ColorTransformFn hdrOetf = pqOetf;
-#endif
-              Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
-              uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
-              reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
-              break;
-            }
-            default: {
-            }
-              // Should be impossible to hit after input validation.
-          }
-        }
-      }
-    }
-  };
-
-  const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
-  std::vector<std::thread> workers;
-  for (int th = 0; th < threads - 1; th++) {
-    workers.push_back(std::thread(applyRecMap));
-  }
-  const int rowStep = threads == 1 ? yuv420_image_ptr->height : kJobSzInRows;
-  for (int rowStart = 0; rowStart < yuv420_image_ptr->height;) {
-    int rowEnd = std::min(rowStart + rowStep, yuv420_image_ptr->height);
-    jobQueue.enqueueJob(rowStart, rowEnd);
-    rowStart = rowEnd;
-  }
-  jobQueue.markQueueForEnd();
-  applyRecMap();
-  std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
-  return NO_ERROR;
-}
-
-status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,
-                                              jr_compressed_ptr primary_jpg_image_ptr,
-                                              jr_compressed_ptr gainmap_jpg_image_ptr) {
-  if (jpegr_image_ptr == nullptr) {
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-
-  MessageHandler msg_handler;
-  std::shared_ptr<DataSegment> seg =
-          DataSegment::Create(DataRange(0, jpegr_image_ptr->length),
-                              static_cast<const uint8_t*>(jpegr_image_ptr->data),
-                              DataSegment::BufferDispositionPolicy::kDontDelete);
-  DataSegmentDataSource data_source(seg);
-  JpegInfoBuilder jpeg_info_builder;
-  jpeg_info_builder.SetImageLimit(2);
-  JpegScanner jpeg_scanner(&msg_handler);
-  jpeg_scanner.Run(&data_source, &jpeg_info_builder);
-  data_source.Reset();
-
-  if (jpeg_scanner.HasError()) {
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-
-  const auto& jpeg_info = jpeg_info_builder.GetInfo();
-  const auto& image_ranges = jpeg_info.GetImageRanges();
-
-  if (image_ranges.empty()) {
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-
-  if (primary_jpg_image_ptr != nullptr) {
-    primary_jpg_image_ptr->data =
-            static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[0].GetBegin();
-    primary_jpg_image_ptr->length = image_ranges[0].GetLength();
-  }
-
-  if (image_ranges.size() == 1) {
-    return ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND;
-  }
-
-  if (gainmap_jpg_image_ptr != nullptr) {
-    gainmap_jpg_image_ptr->data =
-            static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[1].GetBegin();
-    gainmap_jpg_image_ptr->length = image_ranges[1].GetLength();
-  }
-
-  // TODO: choose primary image and gain map image carefully
-  if (image_ranges.size() > 2) {
-    ALOGW("Number of jpeg images present %d, primary, gain map images may not be correctly chosen",
-          (int)image_ranges.size());
-  }
-
-  return NO_ERROR;
-}
-
-// JPEG/R structure:
-// SOI (ff d8)
-//
-// (Optional, if EXIF package is from outside (Encode API-0 API-1), or if EXIF package presents
-// in the JPEG input (Encode API-2, API-3, API-4))
-// APP1 (ff e1)
-// 2 bytes of length (2 + length of exif package)
-// EXIF package (this includes the first two bytes representing the package length)
-//
-// (Required, XMP package) APP1 (ff e1)
-// 2 bytes of length (2 + 29 + length of xmp package)
-// name space ("http://ns.adobe.com/xap/1.0/\0")
-// XMP
-//
-// (Required, MPF package) APP2 (ff e2)
-// 2 bytes of length
-// MPF
-//
-// (Required) primary image (without the first two bytes (SOI) and EXIF, may have other packages)
-//
-// SOI (ff d8)
-//
-// (Required, XMP package) APP1 (ff e1)
-// 2 bytes of length (2 + 29 + length of xmp package)
-// name space ("http://ns.adobe.com/xap/1.0/\0")
-// XMP
-//
-// (Required) secondary image (the gain map, without the first two bytes (SOI))
-//
-// 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 JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
-                              jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif,
-                              void* pIcc, size_t icc_size, ultrahdr_metadata_ptr metadata,
-                              jr_compressed_ptr dest) {
-  if (primary_jpg_image_ptr == nullptr || gainmap_jpg_image_ptr == nullptr || metadata == nullptr ||
-      dest == nullptr) {
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (metadata->version.compare("1.0")) {
-    ALOGE("received bad value for version: %s", metadata->version.c_str());
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (metadata->maxContentBoost < metadata->minContentBoost) {
-    ALOGE("received bad value for content boost min %f, max %f", metadata->minContentBoost,
-          metadata->maxContentBoost);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (metadata->hdrCapacityMax < metadata->hdrCapacityMin || metadata->hdrCapacityMin < 1.0f) {
-    ALOGE("received bad value for hdr capacity min %f, max %f", metadata->hdrCapacityMin,
-          metadata->hdrCapacityMax);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (metadata->offsetSdr < 0.0f || metadata->offsetHdr < 0.0f) {
-    ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr, metadata->offsetHdr);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (metadata->gamma <= 0.0f) {
-    ALOGE("received bad value for gamma %f", metadata->gamma);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-
-  const string nameSpace = "http://ns.adobe.com/xap/1.0/";
-  const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
-
-  // calculate secondary image length first, because the length will be written into the primary
-  // image xmp
-  const string xmp_secondary = generateXmpForSecondaryImage(*metadata);
-  const int xmp_secondary_length = 2 /* 2 bytes representing the length of the package */
-          + nameSpaceLength          /* 29 bytes length of name space including \0 */
-          + xmp_secondary.size();    /* length of xmp packet */
-  const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */
-          + xmp_secondary_length + gainmap_jpg_image_ptr->length;
-  // primary image
-  const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata);
-  // same as primary
-  const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size();
-
-  // Check if EXIF package presents in the JPEG input.
-  // If so, extract and remove the EXIF package.
-  JpegDecoderHelper decoder;
-  if (!decoder.extractEXIF(primary_jpg_image_ptr->data, primary_jpg_image_ptr->length)) {
-    return ERROR_JPEGR_DECODE_ERROR;
-  }
-  jpegr_exif_struct exif_from_jpg = {.data = nullptr, .length = 0};
-  jpegr_compressed_struct new_jpg_image = {.data = nullptr,
-                                           .length = 0,
-                                           .maxLength = 0,
-                                           .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-  std::unique_ptr<uint8_t[]> dest_data;
-  if (decoder.getEXIFPos() >= 0) {
-    if (pExif != nullptr) {
-      ALOGE("received EXIF from outside while the primary image already contains EXIF");
-      return ERROR_JPEGR_INVALID_INPUT_TYPE;
-    }
-    copyJpegWithoutExif(&new_jpg_image,
-                        primary_jpg_image_ptr,
-                        decoder.getEXIFPos(),
-                        decoder.getEXIFSize());
-    dest_data.reset(reinterpret_cast<uint8_t*>(new_jpg_image.data));
-    exif_from_jpg.data = decoder.getEXIFPtr();
-    exif_from_jpg.length = decoder.getEXIFSize();
-    pExif = &exif_from_jpg;
-  }
-
-  jr_compressed_ptr final_primary_jpg_image_ptr =
-          new_jpg_image.length == 0 ? primary_jpg_image_ptr : &new_jpg_image;
-
-  int pos = 0;
-  // Begin primary image
-  // 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));
-
-  // Write EXIF
-  if (pExif != nullptr) {
-    const int length = 2 + pExif->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, pExif->data, pExif->length, pos));
-  }
-
-  // Prepare and write XMP
-  {
-    const int length = xmp_primary_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, (void*)nameSpace.c_str(), nameSpaceLength, pos));
-    JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos));
-  }
-
-  // Write ICC
-  if (pIcc != nullptr && icc_size > 0) {
-    const int length = icc_size + 2;
-    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::kAPP2, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
-    JPEGR_CHECK(Write(dest, pIcc, icc_size, pos));
-  }
-
-  // Prepare and write MPF
-  {
-    const int length = 2 + calculateMpfSize();
-    const uint8_t lengthH = ((length >> 8) & 0xff);
-    const uint8_t lengthL = (length & 0xff);
-    int primary_image_size = pos + length + final_primary_jpg_image_ptr->length;
-    // between APP2 + package size + signature
-    // ff e2 00 58 4d 50 46 00
-    // 2 + 2 + 4 = 8 (bytes)
-    // and ff d8 sign of the secondary image
-    int secondary_image_offset = primary_image_size - pos - 8;
-    sp<DataStruct> mpf = generateMpf(primary_image_size, 0, /* primary_image_offset */
-                                     secondary_image_size, secondary_image_offset);
-    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
-    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
-    JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos));
-  }
-
-  // Write primary image
-  JPEGR_CHECK(Write(dest, (uint8_t*)final_primary_jpg_image_ptr->data + 2,
-                    final_primary_jpg_image_ptr->length - 2, pos));
-  // Finish primary image
-
-  // Begin secondary image (gain 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));
-
-  // Prepare and write XMP
-  {
-    const int length = xmp_secondary_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, (void*)nameSpace.c_str(), nameSpaceLength, pos));
-    JPEGR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos));
-  }
-
-  // Write secondary image
-  JPEGR_CHECK(Write(dest, (uint8_t*)gainmap_jpg_image_ptr->data + 2,
-                    gainmap_jpg_image_ptr->length - 2, pos));
-
-  // Set back length
-  dest->length = pos;
-
-  // Done!
-  return NO_ERROR;
-}
-
-status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) {
-  if (src == nullptr || dest == nullptr) {
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (src->width != dest->width || src->height != dest->height) {
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  uint16_t* src_y_data = reinterpret_cast<uint16_t*>(src->data);
-  uint8_t* dst_y_data = reinterpret_cast<uint8_t*>(dest->data);
-  for (size_t y = 0; y < src->height; ++y) {
-    uint16_t* src_y_row = src_y_data + y * src->luma_stride;
-    uint8_t* dst_y_row = dst_y_data + y * dest->luma_stride;
-    for (size_t x = 0; x < src->width; ++x) {
-      uint16_t y_uint = src_y_row[x] >> 6;
-      dst_y_row[x] = static_cast<uint8_t>((y_uint >> 2) & 0xff);
-    }
-    if (dest->width != dest->luma_stride) {
-      memset(dst_y_row + dest->width, 0, dest->luma_stride - dest->width);
-    }
-  }
-  uint16_t* src_uv_data = reinterpret_cast<uint16_t*>(src->chroma_data);
-  uint8_t* dst_u_data = reinterpret_cast<uint8_t*>(dest->chroma_data);
-  size_t dst_v_offset = (dest->chroma_stride * dest->height / 2);
-  uint8_t* dst_v_data = dst_u_data + dst_v_offset;
-  for (size_t y = 0; y < src->height / 2; ++y) {
-    uint16_t* src_uv_row = src_uv_data + y * src->chroma_stride;
-    uint8_t* dst_u_row = dst_u_data + y * dest->chroma_stride;
-    uint8_t* dst_v_row = dst_v_data + y * dest->chroma_stride;
-    for (size_t x = 0; x < src->width / 2; ++x) {
-      uint16_t u_uint = src_uv_row[x << 1] >> 6;
-      uint16_t v_uint = src_uv_row[(x << 1) + 1] >> 6;
-      dst_u_row[x] = static_cast<uint8_t>((u_uint >> 2) & 0xff);
-      dst_v_row[x] = static_cast<uint8_t>((v_uint >> 2) & 0xff);
-    }
-    if (dest->width / 2 != dest->chroma_stride) {
-      memset(dst_u_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
-      memset(dst_v_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
-    }
-  }
-  dest->colorGamut = src->colorGamut;
-  return NO_ERROR;
-}
-
-status_t JpegR::convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding,
-                           ultrahdr_color_gamut dest_encoding) {
-  if (image == nullptr) {
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
-      dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
-    return ERROR_JPEGR_INVALID_COLORGAMUT;
-  }
-
-  ColorTransformFn conversionFn = nullptr;
-  switch (src_encoding) {
-    case ULTRAHDR_COLORGAMUT_BT709:
-      switch (dest_encoding) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-          return NO_ERROR;
-        case ULTRAHDR_COLORGAMUT_P3:
-          conversionFn = yuv709To601;
-          break;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-          conversionFn = yuv709To2100;
-          break;
-        default:
-          // Should be impossible to hit after input validation
-          return ERROR_JPEGR_INVALID_COLORGAMUT;
-      }
-      break;
-    case ULTRAHDR_COLORGAMUT_P3:
-      switch (dest_encoding) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-          conversionFn = yuv601To709;
-          break;
-        case ULTRAHDR_COLORGAMUT_P3:
-          return NO_ERROR;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-          conversionFn = yuv601To2100;
-          break;
-        default:
-          // Should be impossible to hit after input validation
-          return ERROR_JPEGR_INVALID_COLORGAMUT;
-      }
-      break;
-    case ULTRAHDR_COLORGAMUT_BT2100:
-      switch (dest_encoding) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-          conversionFn = yuv2100To709;
-          break;
-        case ULTRAHDR_COLORGAMUT_P3:
-          conversionFn = yuv2100To601;
-          break;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-          return NO_ERROR;
-        default:
-          // Should be impossible to hit after input validation
-          return ERROR_JPEGR_INVALID_COLORGAMUT;
-      }
-      break;
-    default:
-      // Should be impossible to hit after input validation
-      return ERROR_JPEGR_INVALID_COLORGAMUT;
-  }
-
-  if (conversionFn == nullptr) {
-    // Should be impossible to hit after input validation
-    return ERROR_JPEGR_INVALID_COLORGAMUT;
-  }
-
-  for (size_t y = 0; y < image->height / 2; ++y) {
-    for (size_t x = 0; x < image->width / 2; ++x) {
-      transformYuv420(image, x, y, conversionFn);
-    }
-  }
-
-  return NO_ERROR;
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/jpegrutils.cpp b/libs/ultrahdr/jpegrutils.cpp
deleted file mode 100644
index c434eb6..0000000
--- a/libs/ultrahdr/jpegrutils.cpp
+++ /dev/null
@@ -1,600 +0,0 @@
-/*
- * 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 <ultrahdr/jpegrutils.h>
-
-#include <algorithm>
-#include <cmath>
-
-#include <image_io/xml/xml_reader.h>
-#include <image_io/xml/xml_writer.h>
-#include <image_io/base/message_handler.h>
-#include <image_io/xml/xml_element_rules.h>
-#include <image_io/xml/xml_handler.h>
-#include <image_io/xml/xml_rule.h>
-#include <utils/Log.h>
-
-using namespace photos_editing_formats::image_io;
-using namespace std;
-
-namespace android::ultrahdr {
-/*
- * Helper function used for generating XMP metadata.
- *
- * @param prefix The prefix part of the name.
- * @param suffix The suffix part of the name.
- * @return A name of the form "prefix:suffix".
- */
-static inline string Name(const string &prefix, const string &suffix) {
-  std::stringstream ss;
-  ss << prefix << ":" << suffix;
-  return ss.str();
-}
-
-DataStruct::DataStruct(int s) {
-    data = malloc(s);
-    length = s;
-    memset(data, 0, s);
-    writePos = 0;
-}
-
-DataStruct::~DataStruct() {
-    if (data != nullptr) {
-        free(data);
-    }
-}
-
-void* DataStruct::getData() {
-    return data;
-}
-
-int DataStruct::getLength() {
-    return length;
-}
-
-int DataStruct::getBytesWritten() {
-    return writePos;
-}
-
-bool DataStruct::write8(uint8_t value) {
-    uint8_t v = value;
-    return write(&v, 1);
-}
-
-bool DataStruct::write16(uint16_t value) {
-    uint16_t v = value;
-    return write(&v, 2);
-}
-bool DataStruct::write32(uint32_t value) {
-    uint32_t v = value;
-    return write(&v, 4);
-}
-
-bool DataStruct::write(const void* src, int size) {
-    if (writePos + size > length) {
-        ALOGE("Writing out of boundary: write position: %d, size: %d, capacity: %d",
-                writePos, size, length);
-        return false;
-    }
-    memcpy((uint8_t*) data + writePos, src, size);
-    writePos += size;
-    return true;
-}
-
-/*
- * Helper function used for writing data to destination.
- */
-status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
-  if (position + length > destination->maxLength) {
-    return ERROR_JPEGR_BUFFER_TOO_SMALL;
-  }
-
-  memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
-  position += length;
-  return NO_ERROR;
-}
-
-// Extremely simple XML Handler - just searches for interesting elements
-class XMPXmlHandler : public XmlHandler {
-public:
-
-    XMPXmlHandler() : XmlHandler() {
-        state = NotStrarted;
-        versionFound = false;
-        minContentBoostFound = false;
-        maxContentBoostFound = false;
-        gammaFound = false;
-        offsetSdrFound = false;
-        offsetHdrFound = false;
-        hdrCapacityMinFound = false;
-        hdrCapacityMaxFound = false;
-        baseRenditionIsHdrFound = false;
-    }
-
-    enum ParseState {
-        NotStrarted,
-        Started,
-        Done
-    };
-
-    virtual DataMatchResult StartElement(const XmlTokenContext& context) {
-        string val;
-        if (context.BuildTokenValue(&val)) {
-            if (!val.compare(containerName)) {
-                state = Started;
-            } else {
-                if (state != Done) {
-                    state = NotStrarted;
-                }
-            }
-        }
-        return context.GetResult();
-    }
-
-    virtual DataMatchResult FinishElement(const XmlTokenContext& context) {
-        if (state == Started) {
-            state = Done;
-            lastAttributeName = "";
-        }
-        return context.GetResult();
-    }
-
-    virtual DataMatchResult AttributeName(const XmlTokenContext& context) {
-        string val;
-        if (state == Started) {
-            if (context.BuildTokenValue(&val)) {
-                if (!val.compare(versionAttrName)) {
-                    lastAttributeName = versionAttrName;
-                } else if (!val.compare(maxContentBoostAttrName)) {
-                    lastAttributeName = maxContentBoostAttrName;
-                } else if (!val.compare(minContentBoostAttrName)) {
-                    lastAttributeName = minContentBoostAttrName;
-                } else if (!val.compare(gammaAttrName)) {
-                    lastAttributeName = gammaAttrName;
-                } else if (!val.compare(offsetSdrAttrName)) {
-                    lastAttributeName = offsetSdrAttrName;
-                } else if (!val.compare(offsetHdrAttrName)) {
-                    lastAttributeName = offsetHdrAttrName;
-                } else if (!val.compare(hdrCapacityMinAttrName)) {
-                    lastAttributeName = hdrCapacityMinAttrName;
-                } else if (!val.compare(hdrCapacityMaxAttrName)) {
-                    lastAttributeName = hdrCapacityMaxAttrName;
-                } else if (!val.compare(baseRenditionIsHdrAttrName)) {
-                    lastAttributeName = baseRenditionIsHdrAttrName;
-                } else {
-                    lastAttributeName = "";
-                }
-            }
-        }
-        return context.GetResult();
-    }
-
-    virtual DataMatchResult AttributeValue(const XmlTokenContext& context) {
-        string val;
-        if (state == Started) {
-            if (context.BuildTokenValue(&val, true)) {
-                if (!lastAttributeName.compare(versionAttrName)) {
-                    versionStr = val;
-                    versionFound = true;
-                } else if (!lastAttributeName.compare(maxContentBoostAttrName)) {
-                    maxContentBoostStr = val;
-                    maxContentBoostFound = true;
-                } else if (!lastAttributeName.compare(minContentBoostAttrName)) {
-                    minContentBoostStr = val;
-                    minContentBoostFound = true;
-                } else if (!lastAttributeName.compare(gammaAttrName)) {
-                    gammaStr = val;
-                    gammaFound = true;
-                } else if (!lastAttributeName.compare(offsetSdrAttrName)) {
-                    offsetSdrStr = val;
-                    offsetSdrFound = true;
-                } else if (!lastAttributeName.compare(offsetHdrAttrName)) {
-                    offsetHdrStr = val;
-                    offsetHdrFound = true;
-                } else if (!lastAttributeName.compare(hdrCapacityMinAttrName)) {
-                    hdrCapacityMinStr = val;
-                    hdrCapacityMinFound = true;
-                } else if (!lastAttributeName.compare(hdrCapacityMaxAttrName)) {
-                    hdrCapacityMaxStr = val;
-                    hdrCapacityMaxFound = true;
-                } else if (!lastAttributeName.compare(baseRenditionIsHdrAttrName)) {
-                    baseRenditionIsHdrStr = val;
-                    baseRenditionIsHdrFound = true;
-                }
-            }
-        }
-        return context.GetResult();
-    }
-
-    bool getVersion(string* version, bool* present) {
-        if (state == Done) {
-            *version = versionStr;
-            *present = versionFound;
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    bool getMaxContentBoost(float* max_content_boost, bool* present) {
-        if (state == Done) {
-            *present = maxContentBoostFound;
-            stringstream ss(maxContentBoostStr);
-            float val;
-            if (ss >> val) {
-                *max_content_boost = exp2(val);
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-    bool getMinContentBoost(float* min_content_boost, bool* present) {
-        if (state == Done) {
-            *present = minContentBoostFound;
-            stringstream ss(minContentBoostStr);
-            float val;
-            if (ss >> val) {
-                *min_content_boost = exp2(val);
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-    bool getGamma(float* gamma, bool* present) {
-        if (state == Done) {
-            *present = gammaFound;
-            stringstream ss(gammaStr);
-            float val;
-            if (ss >> val) {
-                *gamma = val;
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-
-    bool getOffsetSdr(float* offset_sdr, bool* present) {
-        if (state == Done) {
-            *present = offsetSdrFound;
-            stringstream ss(offsetSdrStr);
-            float val;
-            if (ss >> val) {
-                *offset_sdr = val;
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-
-    bool getOffsetHdr(float* offset_hdr, bool* present) {
-        if (state == Done) {
-            *present = offsetHdrFound;
-            stringstream ss(offsetHdrStr);
-            float val;
-            if (ss >> val) {
-                *offset_hdr = val;
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-
-    bool getHdrCapacityMin(float* hdr_capacity_min, bool* present) {
-        if (state == Done) {
-            *present = hdrCapacityMinFound;
-            stringstream ss(hdrCapacityMinStr);
-            float val;
-            if (ss >> val) {
-                *hdr_capacity_min = exp2(val);
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-
-    bool getHdrCapacityMax(float* hdr_capacity_max, bool* present) {
-        if (state == Done) {
-            *present = hdrCapacityMaxFound;
-            stringstream ss(hdrCapacityMaxStr);
-            float val;
-            if (ss >> val) {
-                *hdr_capacity_max = exp2(val);
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-
-    bool getBaseRenditionIsHdr(bool* base_rendition_is_hdr, bool* present) {
-        if (state == Done) {
-            *present = baseRenditionIsHdrFound;
-            if (!baseRenditionIsHdrStr.compare("False")) {
-                *base_rendition_is_hdr = false;
-                return true;
-            } else if (!baseRenditionIsHdrStr.compare("True")) {
-                *base_rendition_is_hdr = true;
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-
-
-private:
-    static const string containerName;
-
-    static const string versionAttrName;
-    string              versionStr;
-    bool                versionFound;
-    static const string maxContentBoostAttrName;
-    string              maxContentBoostStr;
-    bool                maxContentBoostFound;
-    static const string minContentBoostAttrName;
-    string              minContentBoostStr;
-    bool                minContentBoostFound;
-    static const string gammaAttrName;
-    string              gammaStr;
-    bool                gammaFound;
-    static const string offsetSdrAttrName;
-    string              offsetSdrStr;
-    bool                offsetSdrFound;
-    static const string offsetHdrAttrName;
-    string              offsetHdrStr;
-    bool                offsetHdrFound;
-    static const string hdrCapacityMinAttrName;
-    string              hdrCapacityMinStr;
-    bool                hdrCapacityMinFound;
-    static const string hdrCapacityMaxAttrName;
-    string              hdrCapacityMaxStr;
-    bool                hdrCapacityMaxFound;
-    static const string baseRenditionIsHdrAttrName;
-    string              baseRenditionIsHdrStr;
-    bool                baseRenditionIsHdrFound;
-
-    string              lastAttributeName;
-    ParseState          state;
-};
-
-// GContainer XMP constants - URI and namespace prefix
-const string kContainerUri        = "http://ns.google.com/photos/1.0/container/";
-const string kContainerPrefix     = "Container";
-
-// GContainer XMP constants - element and attribute names
-const string kConDirectory            = Name(kContainerPrefix, "Directory");
-const string kConItem                 = Name(kContainerPrefix, "Item");
-
-// GContainer XMP constants - names for XMP handlers
-const string XMPXmlHandler::containerName = "rdf:Description";
-// Item XMP constants - URI and namespace prefix
-const string kItemUri        = "http://ns.google.com/photos/1.0/container/item/";
-const string kItemPrefix     = "Item";
-
-// Item XMP constants - element and attribute names
-const string kItemLength           = Name(kItemPrefix, "Length");
-const string kItemMime             = Name(kItemPrefix, "Mime");
-const string kItemSemantic         = Name(kItemPrefix, "Semantic");
-
-// Item XMP constants - element and attribute values
-const string kSemanticPrimary = "Primary";
-const string kSemanticGainMap = "GainMap";
-const string kMimeImageJpeg   = "image/jpeg";
-
-// GainMap XMP constants - URI and namespace prefix
-const string kGainMapUri      = "http://ns.adobe.com/hdr-gain-map/1.0/";
-const string kGainMapPrefix   = "hdrgm";
-
-// GainMap XMP constants - element and attribute names
-const string kMapVersion            = Name(kGainMapPrefix, "Version");
-const string kMapGainMapMin         = Name(kGainMapPrefix, "GainMapMin");
-const string kMapGainMapMax         = Name(kGainMapPrefix, "GainMapMax");
-const string kMapGamma              = Name(kGainMapPrefix, "Gamma");
-const string kMapOffsetSdr          = Name(kGainMapPrefix, "OffsetSDR");
-const string kMapOffsetHdr          = Name(kGainMapPrefix, "OffsetHDR");
-const string kMapHDRCapacityMin     = Name(kGainMapPrefix, "HDRCapacityMin");
-const string kMapHDRCapacityMax     = Name(kGainMapPrefix, "HDRCapacityMax");
-const string kMapBaseRenditionIsHDR = Name(kGainMapPrefix, "BaseRenditionIsHDR");
-
-// GainMap XMP constants - names for XMP handlers
-const string XMPXmlHandler::versionAttrName = kMapVersion;
-const string XMPXmlHandler::minContentBoostAttrName = kMapGainMapMin;
-const string XMPXmlHandler::maxContentBoostAttrName = kMapGainMapMax;
-const string XMPXmlHandler::gammaAttrName = kMapGamma;
-const string XMPXmlHandler::offsetSdrAttrName = kMapOffsetSdr;
-const string XMPXmlHandler::offsetHdrAttrName = kMapOffsetHdr;
-const string XMPXmlHandler::hdrCapacityMinAttrName = kMapHDRCapacityMin;
-const string XMPXmlHandler::hdrCapacityMaxAttrName = kMapHDRCapacityMax;
-const string XMPXmlHandler::baseRenditionIsHdrAttrName = kMapBaseRenditionIsHDR;
-
-bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, ultrahdr_metadata_struct* metadata) {
-    string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
-
-    if (xmp_size < nameSpace.size()+2) {
-        // Data too short
-        return false;
-    }
-
-    if (strncmp(reinterpret_cast<char*>(xmp_data), nameSpace.c_str(), nameSpace.size())) {
-        // Not correct namespace
-        return false;
-    }
-
-    // Position the pointers to the start of XMP XML portion
-    xmp_data += nameSpace.size()+1;
-    xmp_size -= nameSpace.size()+1;
-    XMPXmlHandler handler;
-
-    // We need to remove tail data until the closing tag. Otherwise parser will throw an error.
-    while(xmp_data[xmp_size-1]!='>' && xmp_size > 1) {
-        xmp_size--;
-    }
-
-    string str(reinterpret_cast<const char*>(xmp_data), xmp_size);
-    MessageHandler msg_handler;
-    unique_ptr<XmlRule> rule(new XmlElementRule);
-    XmlReader reader(&handler, &msg_handler);
-    reader.StartParse(std::move(rule));
-    reader.Parse(str);
-    reader.FinishParse();
-    if (reader.HasErrors()) {
-        // Parse error
-        return false;
-    }
-
-    // Apply default values to any not-present fields, except for Version,
-    // maxContentBoost, and hdrCapacityMax, which are required. Return false if
-    // we encounter a present field that couldn't be parsed, since this
-    // indicates it is invalid (eg. string where there should be a float).
-    bool present = false;
-    if (!handler.getVersion(&metadata->version, &present) || !present) {
-        return false;
-    }
-    if (!handler.getMaxContentBoost(&metadata->maxContentBoost, &present) || !present) {
-        return false;
-    }
-    if (!handler.getHdrCapacityMax(&metadata->hdrCapacityMax, &present) || !present) {
-        return false;
-    }
-    if (!handler.getMinContentBoost(&metadata->minContentBoost, &present)) {
-        if (present) return false;
-        metadata->minContentBoost = 1.0f;
-    }
-    if (!handler.getGamma(&metadata->gamma, &present)) {
-        if (present) return false;
-        metadata->gamma = 1.0f;
-    }
-    if (!handler.getOffsetSdr(&metadata->offsetSdr, &present)) {
-        if (present) return false;
-        metadata->offsetSdr = 1.0f / 64.0f;
-    }
-    if (!handler.getOffsetHdr(&metadata->offsetHdr, &present)) {
-        if (present) return false;
-        metadata->offsetHdr = 1.0f / 64.0f;
-    }
-    if (!handler.getHdrCapacityMin(&metadata->hdrCapacityMin, &present)) {
-        if (present) return false;
-        metadata->hdrCapacityMin = 1.0f;
-    }
-
-    bool base_rendition_is_hdr;
-    if (!handler.getBaseRenditionIsHdr(&base_rendition_is_hdr, &present)) {
-        if (present) return false;
-        base_rendition_is_hdr = false;
-    }
-    if (base_rendition_is_hdr) {
-        ALOGE("Base rendition of HDR is not supported!");
-        return false;
-    }
-
-    return true;
-}
-
-string generateXmpForPrimaryImage(int secondary_image_length, ultrahdr_metadata_struct& metadata) {
-  const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
-  const vector<string> kLiItem({string("rdf:li"), kConItem});
-
-  std::stringstream ss;
-  photos_editing_formats::image_io::XmlWriter writer(ss);
-  writer.StartWritingElement("x:xmpmeta");
-  writer.WriteXmlns("x", "adobe:ns:meta/");
-  writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
-  writer.StartWritingElement("rdf:RDF");
-  writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
-  writer.StartWritingElement("rdf:Description");
-  writer.WriteXmlns(kContainerPrefix, kContainerUri);
-  writer.WriteXmlns(kItemPrefix, kItemUri);
-  writer.WriteXmlns(kGainMapPrefix, kGainMapUri);
-  writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
-
-  writer.StartWritingElements(kConDirSeq);
-
-  size_t item_depth = writer.StartWritingElement("rdf:li");
-  writer.WriteAttributeNameAndValue("rdf:parseType", "Resource");
-  writer.StartWritingElement(kConItem);
-  writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary);
-  writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg);
-  writer.FinishWritingElementsToDepth(item_depth);
-
-  writer.StartWritingElement("rdf:li");
-  writer.WriteAttributeNameAndValue("rdf:parseType", "Resource");
-  writer.StartWritingElement(kConItem);
-  writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticGainMap);
-  writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg);
-  writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
-
-  writer.FinishWriting();
-
-  return ss.str();
-}
-
-string generateXmpForSecondaryImage(ultrahdr_metadata_struct& metadata) {
-  const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
-
-  std::stringstream ss;
-  photos_editing_formats::image_io::XmlWriter writer(ss);
-  writer.StartWritingElement("x:xmpmeta");
-  writer.WriteXmlns("x", "adobe:ns:meta/");
-  writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
-  writer.StartWritingElement("rdf:RDF");
-  writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
-  writer.StartWritingElement("rdf:Description");
-  writer.WriteXmlns(kGainMapPrefix, kGainMapUri);
-  writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
-  writer.WriteAttributeNameAndValue(kMapGainMapMin, log2(metadata.minContentBoost));
-  writer.WriteAttributeNameAndValue(kMapGainMapMax, log2(metadata.maxContentBoost));
-  writer.WriteAttributeNameAndValue(kMapGamma, metadata.gamma);
-  writer.WriteAttributeNameAndValue(kMapOffsetSdr, metadata.offsetSdr);
-  writer.WriteAttributeNameAndValue(kMapOffsetHdr, metadata.offsetHdr);
-  writer.WriteAttributeNameAndValue(kMapHDRCapacityMin, log2(metadata.hdrCapacityMin));
-  writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, log2(metadata.hdrCapacityMax));
-  writer.WriteAttributeNameAndValue(kMapBaseRenditionIsHDR, "False");
-  writer.FinishWriting();
-
-  return ss.str();
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/multipictureformat.cpp b/libs/ultrahdr/multipictureformat.cpp
deleted file mode 100644
index f1679ef..0000000
--- a/libs/ultrahdr/multipictureformat.cpp
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include <ultrahdr/multipictureformat.h>
-#include <ultrahdr/jpegrutils.h>
-
-namespace android::ultrahdr {
-size_t calculateMpfSize() {
-    return sizeof(kMpfSig) +                 // Signature
-            kMpEndianSize +                   // Endianness
-            sizeof(uint32_t) +                // Index IFD Offset
-            sizeof(uint16_t) +                // Tag count
-            kTagSerializedCount * kTagSize +  // 3 tags at 12 bytes each
-            sizeof(uint32_t) +                // Attribute IFD offset
-            kNumPictures * kMPEntrySize;      // MP Entries for each image
-}
-
-sp<DataStruct> generateMpf(int primary_image_size, int primary_image_offset,
-        int secondary_image_size, int secondary_image_offset) {
-    size_t mpf_size = calculateMpfSize();
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(mpf_size);
-
-    dataStruct->write(static_cast<const void*>(kMpfSig), sizeof(kMpfSig));
-#if USE_BIG_ENDIAN
-    dataStruct->write(static_cast<const void*>(kMpBigEndian), kMpEndianSize);
-#else
-    dataStruct->write(static_cast<const void*>(kMpLittleEndian), kMpEndianSize);
-#endif
-
-    // Set the Index IFD offset be the position after the endianness value and this offset.
-    constexpr uint32_t indexIfdOffset =
-            static_cast<uint16_t>(kMpEndianSize + sizeof(kMpfSig));
-    dataStruct->write32(Endian_SwapBE32(indexIfdOffset));
-
-    // We will write 3 tags (version, number of images, MP entries).
-    dataStruct->write16(Endian_SwapBE16(kTagSerializedCount));
-
-    // Write the version tag.
-    dataStruct->write16(Endian_SwapBE16(kVersionTag));
-    dataStruct->write16(Endian_SwapBE16(kVersionType));
-    dataStruct->write32(Endian_SwapBE32(kVersionCount));
-    dataStruct->write(kVersionExpected, kVersionSize);
-
-    // Write the number of images.
-    dataStruct->write16(Endian_SwapBE16(kNumberOfImagesTag));
-    dataStruct->write16(Endian_SwapBE16(kNumberOfImagesType));
-    dataStruct->write32(Endian_SwapBE32(kNumberOfImagesCount));
-    dataStruct->write32(Endian_SwapBE32(kNumPictures));
-
-    // Write the MP entries.
-    dataStruct->write16(Endian_SwapBE16(kMPEntryTag));
-    dataStruct->write16(Endian_SwapBE16(kMPEntryType));
-    dataStruct->write32(Endian_SwapBE32(kMPEntrySize * kNumPictures));
-    const uint32_t mpEntryOffset =
-            static_cast<uint32_t>(dataStruct->getBytesWritten() -  // The bytes written so far
-                                  sizeof(kMpfSig) +   // Excluding the MPF signature
-                                  sizeof(uint32_t) +  // The 4 bytes for this offset
-                                  sizeof(uint32_t));  // The 4 bytes for the attribute IFD offset.
-    dataStruct->write32(Endian_SwapBE32(mpEntryOffset));
-
-    // Write the attribute IFD offset (zero because we don't write it).
-    dataStruct->write32(0);
-
-    // Write the MP entries for primary image
-    dataStruct->write32(
-            Endian_SwapBE32(kMPEntryAttributeFormatJpeg | kMPEntryAttributeTypePrimary));
-    dataStruct->write32(Endian_SwapBE32(primary_image_size));
-    dataStruct->write32(Endian_SwapBE32(primary_image_offset));
-    dataStruct->write16(0);
-    dataStruct->write16(0);
-
-    // Write the MP entries for secondary image
-    dataStruct->write32(Endian_SwapBE32(kMPEntryAttributeFormatJpeg));
-    dataStruct->write32(Endian_SwapBE32(secondary_image_size));
-    dataStruct->write32(Endian_SwapBE32(secondary_image_offset));
-    dataStruct->write16(0);
-    dataStruct->write16(0);
-
-    return dataStruct;
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/tests/AndroidTest.xml b/libs/ultrahdr/tests/AndroidTest.xml
deleted file mode 100644
index 1754a5c..0000000
--- a/libs/ultrahdr/tests/AndroidTest.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2023 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the"License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an"AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Unit test configuration for ultrahdr_unit_test">
-    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
-        <option name="cleanup" value="true" />
-        <option name="push-file" key="ultrahdr_unit_test" value="/data/local/tmp/ultrahdr_unit_test" />
-        <option name="push" value="data/*->/data/local/tmp/" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.GTest" >
-        <option name="native-test-device-path" value="/data/local/tmp" />
-        <option name="module-name" value="ultrahdr_unit_test" />
-    </test>
-</configuration>
diff --git a/libs/ultrahdr/tests/data/jpeg_image.jpg b/libs/ultrahdr/tests/data/jpeg_image.jpg
deleted file mode 100644
index e285742..0000000
--- a/libs/ultrahdr/tests/data/jpeg_image.jpg
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/minnie-318x240.yu12 b/libs/ultrahdr/tests/data/minnie-318x240.yu12
deleted file mode 100644
index 7b2fc71..0000000
--- a/libs/ultrahdr/tests/data/minnie-318x240.yu12
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/minnie-320x240-y.jpg b/libs/ultrahdr/tests/data/minnie-320x240-y.jpg
deleted file mode 100644
index 20b5a2c..0000000
--- a/libs/ultrahdr/tests/data/minnie-320x240-y.jpg
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg b/libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg
deleted file mode 100644
index c7f4538..0000000
--- a/libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/minnie-320x240-yuv.jpg b/libs/ultrahdr/tests/data/minnie-320x240-yuv.jpg
deleted file mode 100644
index 41300f4..0000000
--- a/libs/ultrahdr/tests/data/minnie-320x240-yuv.jpg
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/minnie-320x240.y b/libs/ultrahdr/tests/data/minnie-320x240.y
deleted file mode 100644
index f9d8371..0000000
--- a/libs/ultrahdr/tests/data/minnie-320x240.y
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/minnie-320x240.yu12 b/libs/ultrahdr/tests/data/minnie-320x240.yu12
deleted file mode 100644
index 0d66f53..0000000
--- a/libs/ultrahdr/tests/data/minnie-320x240.yu12
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/raw_p010_image.p010 b/libs/ultrahdr/tests/data/raw_p010_image.p010
deleted file mode 100644
index 01673bf..0000000
--- a/libs/ultrahdr/tests/data/raw_p010_image.p010
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/raw_yuv420_image.yuv420 b/libs/ultrahdr/tests/data/raw_yuv420_image.yuv420
deleted file mode 100644
index c043da6..0000000
--- a/libs/ultrahdr/tests/data/raw_yuv420_image.yuv420
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/gainmapmath_test.cpp b/libs/ultrahdr/tests/gainmapmath_test.cpp
deleted file mode 100644
index 7c2d076..0000000
--- a/libs/ultrahdr/tests/gainmapmath_test.cpp
+++ /dev/null
@@ -1,1359 +0,0 @@
-/*
- * 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 <cmath>
-#include <gtest/gtest.h>
-#include <gmock/gmock.h>
-#include <ultrahdr/gainmapmath.h>
-
-namespace android::ultrahdr {
-
-class GainMapMathTest : public testing::Test {
-public:
-  GainMapMathTest();
-  ~GainMapMathTest();
-
-  float ComparisonEpsilon() { return 1e-4f; }
-  float LuminanceEpsilon() { return 1e-2f; }
-  float YuvConversionEpsilon() { return 1.0f / (255.0f * 2.0f); }
-
-  Color Yuv420(uint8_t y, uint8_t u, uint8_t v) {
-      return {{{ static_cast<float>(y) / 255.0f,
-                 (static_cast<float>(u) - 128.0f) / 255.0f,
-                 (static_cast<float>(v) - 128.0f) / 255.0f }}};
-  }
-
-  Color P010(uint16_t y, uint16_t u, uint16_t v) {
-      return {{{ (static_cast<float>(y) - 64.0f) / 876.0f,
-                 (static_cast<float>(u) - 64.0f) / 896.0f - 0.5f,
-                 (static_cast<float>(v) - 64.0f) / 896.0f - 0.5f }}};
-  }
-
-  float Map(uint8_t e) {
-    return static_cast<float>(e) / 255.0f;
-  }
-
-  Color ColorMin(Color e1, Color e2) {
-    return {{{ fmin(e1.r, e2.r), fmin(e1.g, e2.g), fmin(e1.b, e2.b) }}};
-  }
-
-  Color ColorMax(Color e1, Color e2) {
-    return {{{ fmax(e1.r, e2.r), fmax(e1.g, e2.g), fmax(e1.b, e2.b) }}};
-  }
-
-  Color RgbBlack() { return {{{ 0.0f, 0.0f, 0.0f }}}; }
-  Color RgbWhite() { return {{{ 1.0f, 1.0f, 1.0f }}}; }
-
-  Color RgbRed() { return {{{ 1.0f, 0.0f, 0.0f }}}; }
-  Color RgbGreen() { return {{{ 0.0f, 1.0f, 0.0f }}}; }
-  Color RgbBlue() { return {{{ 0.0f, 0.0f, 1.0f }}}; }
-
-  Color YuvBlack() { return {{{ 0.0f, 0.0f, 0.0f }}}; }
-  Color YuvWhite() { return {{{ 1.0f, 0.0f, 0.0f }}}; }
-
-  Color SrgbYuvRed() { return {{{ 0.2126f, -0.11457f, 0.5f }}}; }
-  Color SrgbYuvGreen() { return {{{ 0.7152f, -0.38543f, -0.45415f }}}; }
-  Color SrgbYuvBlue() { return {{{ 0.0722f, 0.5f, -0.04585f }}}; }
-
-  Color P3YuvRed() { return {{{ 0.299f, -0.16874f, 0.5f }}}; }
-  Color P3YuvGreen() { return {{{ 0.587f, -0.33126f, -0.41869f }}}; }
-  Color P3YuvBlue() { return {{{ 0.114f, 0.5f, -0.08131f }}}; }
-
-  Color Bt2100YuvRed() { return {{{ 0.2627f, -0.13963f, 0.5f }}}; }
-  Color Bt2100YuvGreen() { return {{{ 0.6780f, -0.36037f, -0.45979f }}}; }
-  Color Bt2100YuvBlue() { return {{{ 0.0593f, 0.5f, -0.04021f }}}; }
-
-  float SrgbYuvToLuminance(Color yuv_gamma, ColorCalculationFn luminanceFn) {
-    Color rgb_gamma = srgbYuvToRgb(yuv_gamma);
-    Color rgb = srgbInvOetf(rgb_gamma);
-    float luminance_scaled = luminanceFn(rgb);
-    return luminance_scaled * kSdrWhiteNits;
-  }
-
-  float P3YuvToLuminance(Color yuv_gamma, ColorCalculationFn luminanceFn) {
-    Color rgb_gamma = p3YuvToRgb(yuv_gamma);
-    Color rgb = srgbInvOetf(rgb_gamma);
-    float luminance_scaled = luminanceFn(rgb);
-    return luminance_scaled * kSdrWhiteNits;
-  }
-
-  float Bt2100YuvToLuminance(Color yuv_gamma, ColorTransformFn hdrInvOetf,
-                             ColorTransformFn gamutConversionFn, ColorCalculationFn luminanceFn,
-                             float scale_factor) {
-    Color rgb_gamma = bt2100YuvToRgb(yuv_gamma);
-    Color rgb = hdrInvOetf(rgb_gamma);
-    rgb = gamutConversionFn(rgb);
-    float luminance_scaled = luminanceFn(rgb);
-    return luminance_scaled * scale_factor;
-  }
-
-  Color Recover(Color yuv_gamma, float gain, ultrahdr_metadata_ptr metadata) {
-    Color rgb_gamma = srgbYuvToRgb(yuv_gamma);
-    Color rgb = srgbInvOetf(rgb_gamma);
-    return applyGain(rgb, gain, metadata);
-  }
-
-  jpegr_uncompressed_struct Yuv420Image() {
-    static uint8_t pixels[] = {
-      // Y
-      0x00, 0x10, 0x20, 0x30,
-      0x01, 0x11, 0x21, 0x31,
-      0x02, 0x12, 0x22, 0x32,
-      0x03, 0x13, 0x23, 0x33,
-      // U
-      0xA0, 0xA1,
-      0xA2, 0xA3,
-      // V
-      0xB0, 0xB1,
-      0xB2, 0xB3,
-    };
-    return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709, pixels + 16, 4, 2 };
-  }
-
-  Color (*Yuv420Colors())[4] {
-    static Color colors[4][4] = {
-      {
-        Yuv420(0x00, 0xA0, 0xB0), Yuv420(0x10, 0xA0, 0xB0),
-        Yuv420(0x20, 0xA1, 0xB1), Yuv420(0x30, 0xA1, 0xB1),
-      }, {
-        Yuv420(0x01, 0xA0, 0xB0), Yuv420(0x11, 0xA0, 0xB0),
-        Yuv420(0x21, 0xA1, 0xB1), Yuv420(0x31, 0xA1, 0xB1),
-      }, {
-        Yuv420(0x02, 0xA2, 0xB2), Yuv420(0x12, 0xA2, 0xB2),
-        Yuv420(0x22, 0xA3, 0xB3), Yuv420(0x32, 0xA3, 0xB3),
-      }, {
-        Yuv420(0x03, 0xA2, 0xB2), Yuv420(0x13, 0xA2, 0xB2),
-        Yuv420(0x23, 0xA3, 0xB3), Yuv420(0x33, 0xA3, 0xB3),
-      },
-    };
-    return colors;
-  }
-
-  jpegr_uncompressed_struct P010Image() {
-    static uint16_t pixels[] = {
-      // Y
-      0x00 << 6, 0x10 << 6, 0x20 << 6, 0x30 << 6,
-      0x01 << 6, 0x11 << 6, 0x21 << 6, 0x31 << 6,
-      0x02 << 6, 0x12 << 6, 0x22 << 6, 0x32 << 6,
-      0x03 << 6, 0x13 << 6, 0x23 << 6, 0x33 << 6,
-      // UV
-      0xA0 << 6, 0xB0 << 6, 0xA1 << 6, 0xB1 << 6,
-      0xA2 << 6, 0xB2 << 6, 0xA3 << 6, 0xB3 << 6,
-    };
-    return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709, pixels + 16, 4, 4 };
-  }
-
-  Color (*P010Colors())[4] {
-    static Color colors[4][4] = {
-      {
-        P010(0x00, 0xA0, 0xB0), P010(0x10, 0xA0, 0xB0),
-        P010(0x20, 0xA1, 0xB1), P010(0x30, 0xA1, 0xB1),
-      }, {
-        P010(0x01, 0xA0, 0xB0), P010(0x11, 0xA0, 0xB0),
-        P010(0x21, 0xA1, 0xB1), P010(0x31, 0xA1, 0xB1),
-      }, {
-        P010(0x02, 0xA2, 0xB2), P010(0x12, 0xA2, 0xB2),
-        P010(0x22, 0xA3, 0xB3), P010(0x32, 0xA3, 0xB3),
-      }, {
-        P010(0x03, 0xA2, 0xB2), P010(0x13, 0xA2, 0xB2),
-        P010(0x23, 0xA3, 0xB3), P010(0x33, 0xA3, 0xB3),
-      },
-    };
-    return colors;
-  }
-
-  jpegr_uncompressed_struct MapImage() {
-    static uint8_t pixels[] = {
-      0x00, 0x10, 0x20, 0x30,
-      0x01, 0x11, 0x21, 0x31,
-      0x02, 0x12, 0x22, 0x32,
-      0x03, 0x13, 0x23, 0x33,
-    };
-    return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_UNSPECIFIED };
-  }
-
-  float (*MapValues())[4] {
-    static float values[4][4] = {
-      {
-        Map(0x00), Map(0x10), Map(0x20), Map(0x30),
-      }, {
-        Map(0x01), Map(0x11), Map(0x21), Map(0x31),
-      }, {
-        Map(0x02), Map(0x12), Map(0x22), Map(0x32),
-      }, {
-        Map(0x03), Map(0x13), Map(0x23), Map(0x33),
-      },
-    };
-    return values;
-  }
-
-protected:
-  virtual void SetUp();
-  virtual void TearDown();
-};
-
-GainMapMathTest::GainMapMathTest() {}
-GainMapMathTest::~GainMapMathTest() {}
-
-void GainMapMathTest::SetUp() {}
-void GainMapMathTest::TearDown() {}
-
-#define EXPECT_RGB_EQ(e1, e2)       \
-  EXPECT_FLOAT_EQ((e1).r, (e2).r);  \
-  EXPECT_FLOAT_EQ((e1).g, (e2).g);  \
-  EXPECT_FLOAT_EQ((e1).b, (e2).b)
-
-#define EXPECT_RGB_NEAR(e1, e2)                     \
-  EXPECT_NEAR((e1).r, (e2).r, ComparisonEpsilon()); \
-  EXPECT_NEAR((e1).g, (e2).g, ComparisonEpsilon()); \
-  EXPECT_NEAR((e1).b, (e2).b, ComparisonEpsilon())
-
-#define EXPECT_RGB_CLOSE(e1, e2)                            \
-  EXPECT_NEAR((e1).r, (e2).r, ComparisonEpsilon() * 10.0f); \
-  EXPECT_NEAR((e1).g, (e2).g, ComparisonEpsilon() * 10.0f); \
-  EXPECT_NEAR((e1).b, (e2).b, ComparisonEpsilon() * 10.0f)
-
-#define EXPECT_YUV_EQ(e1, e2)       \
-  EXPECT_FLOAT_EQ((e1).y, (e2).y);  \
-  EXPECT_FLOAT_EQ((e1).u, (e2).u);  \
-  EXPECT_FLOAT_EQ((e1).v, (e2).v)
-
-#define EXPECT_YUV_NEAR(e1, e2)                     \
-  EXPECT_NEAR((e1).y, (e2).y, ComparisonEpsilon()); \
-  EXPECT_NEAR((e1).u, (e2).u, ComparisonEpsilon()); \
-  EXPECT_NEAR((e1).v, (e2).v, ComparisonEpsilon())
-
-#define EXPECT_YUV_BETWEEN(e, min, max)                                           \
-  EXPECT_THAT((e).y, testing::AllOf(testing::Ge((min).y), testing::Le((max).y))); \
-  EXPECT_THAT((e).u, testing::AllOf(testing::Ge((min).u), testing::Le((max).u))); \
-  EXPECT_THAT((e).v, testing::AllOf(testing::Ge((min).v), testing::Le((max).v)))
-
-// TODO: a bunch of these tests can be parameterized.
-
-TEST_F(GainMapMathTest, ColorConstruct) {
-  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
-
-  EXPECT_FLOAT_EQ(e1.r, 0.1f);
-  EXPECT_FLOAT_EQ(e1.g, 0.2f);
-  EXPECT_FLOAT_EQ(e1.b, 0.3f);
-
-  EXPECT_FLOAT_EQ(e1.y, 0.1f);
-  EXPECT_FLOAT_EQ(e1.u, 0.2f);
-  EXPECT_FLOAT_EQ(e1.v, 0.3f);
-}
-
-TEST_F(GainMapMathTest, ColorAddColor) {
-  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
-
-  Color e2 = e1 + e1;
-  EXPECT_FLOAT_EQ(e2.r, e1.r * 2.0f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g * 2.0f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b * 2.0f);
-
-  e2 += e1;
-  EXPECT_FLOAT_EQ(e2.r, e1.r * 3.0f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g * 3.0f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b * 3.0f);
-}
-
-TEST_F(GainMapMathTest, ColorAddFloat) {
-  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
-
-  Color e2 = e1 + 0.1f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r + 0.1f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g + 0.1f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b + 0.1f);
-
-  e2 += 0.1f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r + 0.2f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g + 0.2f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b + 0.2f);
-}
-
-TEST_F(GainMapMathTest, ColorSubtractColor) {
-  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
-
-  Color e2 = e1 - e1;
-  EXPECT_FLOAT_EQ(e2.r, 0.0f);
-  EXPECT_FLOAT_EQ(e2.g, 0.0f);
-  EXPECT_FLOAT_EQ(e2.b, 0.0f);
-
-  e2 -= e1;
-  EXPECT_FLOAT_EQ(e2.r, -e1.r);
-  EXPECT_FLOAT_EQ(e2.g, -e1.g);
-  EXPECT_FLOAT_EQ(e2.b, -e1.b);
-}
-
-TEST_F(GainMapMathTest, ColorSubtractFloat) {
-  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
-
-  Color e2 = e1 - 0.1f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r - 0.1f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g - 0.1f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b - 0.1f);
-
-  e2 -= 0.1f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r - 0.2f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g - 0.2f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b - 0.2f);
-}
-
-TEST_F(GainMapMathTest, ColorMultiplyFloat) {
-  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
-
-  Color e2 = e1 * 2.0f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r * 2.0f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g * 2.0f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b * 2.0f);
-
-  e2 *= 2.0f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r * 4.0f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g * 4.0f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b * 4.0f);
-}
-
-TEST_F(GainMapMathTest, ColorDivideFloat) {
-  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
-
-  Color e2 = e1 / 2.0f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r / 2.0f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g / 2.0f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b / 2.0f);
-
-  e2 /= 2.0f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r / 4.0f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g / 4.0f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b / 4.0f);
-}
-
-TEST_F(GainMapMathTest, SrgbLuminance) {
-  EXPECT_FLOAT_EQ(srgbLuminance(RgbBlack()), 0.0f);
-  EXPECT_FLOAT_EQ(srgbLuminance(RgbWhite()), 1.0f);
-  EXPECT_FLOAT_EQ(srgbLuminance(RgbRed()), 0.2126f);
-  EXPECT_FLOAT_EQ(srgbLuminance(RgbGreen()), 0.7152f);
-  EXPECT_FLOAT_EQ(srgbLuminance(RgbBlue()), 0.0722f);
-}
-
-TEST_F(GainMapMathTest, SrgbYuvToRgb) {
-  Color rgb_black = srgbYuvToRgb(YuvBlack());
-  EXPECT_RGB_NEAR(rgb_black, RgbBlack());
-
-  Color rgb_white = srgbYuvToRgb(YuvWhite());
-  EXPECT_RGB_NEAR(rgb_white, RgbWhite());
-
-  Color rgb_r = srgbYuvToRgb(SrgbYuvRed());
-  EXPECT_RGB_NEAR(rgb_r, RgbRed());
-
-  Color rgb_g = srgbYuvToRgb(SrgbYuvGreen());
-  EXPECT_RGB_NEAR(rgb_g, RgbGreen());
-
-  Color rgb_b = srgbYuvToRgb(SrgbYuvBlue());
-  EXPECT_RGB_NEAR(rgb_b, RgbBlue());
-}
-
-TEST_F(GainMapMathTest, SrgbRgbToYuv) {
-  Color yuv_black = srgbRgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv_black, YuvBlack());
-
-  Color yuv_white = srgbRgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv_white, YuvWhite());
-
-  Color yuv_r = srgbRgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv_r, SrgbYuvRed());
-
-  Color yuv_g = srgbRgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv_g, SrgbYuvGreen());
-
-  Color yuv_b = srgbRgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv_b, SrgbYuvBlue());
-}
-
-TEST_F(GainMapMathTest, SrgbRgbYuvRoundtrip) {
-  Color rgb_black = srgbYuvToRgb(srgbRgbToYuv(RgbBlack()));
-  EXPECT_RGB_NEAR(rgb_black, RgbBlack());
-
-  Color rgb_white = srgbYuvToRgb(srgbRgbToYuv(RgbWhite()));
-  EXPECT_RGB_NEAR(rgb_white, RgbWhite());
-
-  Color rgb_r = srgbYuvToRgb(srgbRgbToYuv(RgbRed()));
-  EXPECT_RGB_NEAR(rgb_r, RgbRed());
-
-  Color rgb_g = srgbYuvToRgb(srgbRgbToYuv(RgbGreen()));
-  EXPECT_RGB_NEAR(rgb_g, RgbGreen());
-
-  Color rgb_b = srgbYuvToRgb(srgbRgbToYuv(RgbBlue()));
-  EXPECT_RGB_NEAR(rgb_b, RgbBlue());
-}
-
-TEST_F(GainMapMathTest, SrgbTransferFunction) {
-  EXPECT_FLOAT_EQ(srgbInvOetf(0.0f), 0.0f);
-  EXPECT_NEAR(srgbInvOetf(0.02f), 0.00154f, ComparisonEpsilon());
-  EXPECT_NEAR(srgbInvOetf(0.04045f), 0.00313f, ComparisonEpsilon());
-  EXPECT_NEAR(srgbInvOetf(0.5f), 0.21404f, ComparisonEpsilon());
-  EXPECT_FLOAT_EQ(srgbInvOetf(1.0f), 1.0f);
-}
-
-TEST_F(GainMapMathTest, P3Luminance) {
-  EXPECT_FLOAT_EQ(p3Luminance(RgbBlack()), 0.0f);
-  EXPECT_FLOAT_EQ(p3Luminance(RgbWhite()), 1.0f);
-  EXPECT_FLOAT_EQ(p3Luminance(RgbRed()), 0.20949f);
-  EXPECT_FLOAT_EQ(p3Luminance(RgbGreen()), 0.72160f);
-  EXPECT_FLOAT_EQ(p3Luminance(RgbBlue()), 0.06891f);
-}
-
-TEST_F(GainMapMathTest, P3YuvToRgb) {
-  Color rgb_black = p3YuvToRgb(YuvBlack());
-  EXPECT_RGB_NEAR(rgb_black, RgbBlack());
-
-  Color rgb_white = p3YuvToRgb(YuvWhite());
-  EXPECT_RGB_NEAR(rgb_white, RgbWhite());
-
-  Color rgb_r = p3YuvToRgb(P3YuvRed());
-  EXPECT_RGB_NEAR(rgb_r, RgbRed());
-
-  Color rgb_g = p3YuvToRgb(P3YuvGreen());
-  EXPECT_RGB_NEAR(rgb_g, RgbGreen());
-
-  Color rgb_b = p3YuvToRgb(P3YuvBlue());
-  EXPECT_RGB_NEAR(rgb_b, RgbBlue());
-}
-
-TEST_F(GainMapMathTest, P3RgbToYuv) {
-  Color yuv_black = p3RgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv_black, YuvBlack());
-
-  Color yuv_white = p3RgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv_white, YuvWhite());
-
-  Color yuv_r = p3RgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv_r, P3YuvRed());
-
-  Color yuv_g = p3RgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv_g, P3YuvGreen());
-
-  Color yuv_b = p3RgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv_b, P3YuvBlue());
-}
-
-TEST_F(GainMapMathTest, P3RgbYuvRoundtrip) {
-  Color rgb_black = p3YuvToRgb(p3RgbToYuv(RgbBlack()));
-  EXPECT_RGB_NEAR(rgb_black, RgbBlack());
-
-  Color rgb_white = p3YuvToRgb(p3RgbToYuv(RgbWhite()));
-  EXPECT_RGB_NEAR(rgb_white, RgbWhite());
-
-  Color rgb_r = p3YuvToRgb(p3RgbToYuv(RgbRed()));
-  EXPECT_RGB_NEAR(rgb_r, RgbRed());
-
-  Color rgb_g = p3YuvToRgb(p3RgbToYuv(RgbGreen()));
-  EXPECT_RGB_NEAR(rgb_g, RgbGreen());
-
-  Color rgb_b = p3YuvToRgb(p3RgbToYuv(RgbBlue()));
-  EXPECT_RGB_NEAR(rgb_b, RgbBlue());
-}
-TEST_F(GainMapMathTest, Bt2100Luminance) {
-  EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlack()), 0.0f);
-  EXPECT_FLOAT_EQ(bt2100Luminance(RgbWhite()), 1.0f);
-  EXPECT_FLOAT_EQ(bt2100Luminance(RgbRed()), 0.2627f);
-  EXPECT_FLOAT_EQ(bt2100Luminance(RgbGreen()), 0.6780f);
-  EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlue()), 0.0593f);
-}
-
-TEST_F(GainMapMathTest, Bt2100YuvToRgb) {
-  Color rgb_black = bt2100YuvToRgb(YuvBlack());
-  EXPECT_RGB_NEAR(rgb_black, RgbBlack());
-
-  Color rgb_white = bt2100YuvToRgb(YuvWhite());
-  EXPECT_RGB_NEAR(rgb_white, RgbWhite());
-
-  Color rgb_r = bt2100YuvToRgb(Bt2100YuvRed());
-  EXPECT_RGB_NEAR(rgb_r, RgbRed());
-
-  Color rgb_g = bt2100YuvToRgb(Bt2100YuvGreen());
-  EXPECT_RGB_NEAR(rgb_g, RgbGreen());
-
-  Color rgb_b = bt2100YuvToRgb(Bt2100YuvBlue());
-  EXPECT_RGB_NEAR(rgb_b, RgbBlue());
-}
-
-TEST_F(GainMapMathTest, Bt2100RgbToYuv) {
-  Color yuv_black = bt2100RgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv_black, YuvBlack());
-
-  Color yuv_white = bt2100RgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv_white, YuvWhite());
-
-  Color yuv_r = bt2100RgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv_r, Bt2100YuvRed());
-
-  Color yuv_g = bt2100RgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv_g, Bt2100YuvGreen());
-
-  Color yuv_b = bt2100RgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv_b, Bt2100YuvBlue());
-}
-
-TEST_F(GainMapMathTest, Bt2100RgbYuvRoundtrip) {
-  Color rgb_black = bt2100YuvToRgb(bt2100RgbToYuv(RgbBlack()));
-  EXPECT_RGB_NEAR(rgb_black, RgbBlack());
-
-  Color rgb_white = bt2100YuvToRgb(bt2100RgbToYuv(RgbWhite()));
-  EXPECT_RGB_NEAR(rgb_white, RgbWhite());
-
-  Color rgb_r = bt2100YuvToRgb(bt2100RgbToYuv(RgbRed()));
-  EXPECT_RGB_NEAR(rgb_r, RgbRed());
-
-  Color rgb_g = bt2100YuvToRgb(bt2100RgbToYuv(RgbGreen()));
-  EXPECT_RGB_NEAR(rgb_g, RgbGreen());
-
-  Color rgb_b = bt2100YuvToRgb(bt2100RgbToYuv(RgbBlue()));
-  EXPECT_RGB_NEAR(rgb_b, RgbBlue());
-}
-
-TEST_F(GainMapMathTest, Bt709ToBt601YuvConversion) {
-  Color yuv_black = srgbRgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv709To601(yuv_black), YuvBlack());
-
-  Color yuv_white = srgbRgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv709To601(yuv_white), YuvWhite());
-
-  Color yuv_r = srgbRgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv709To601(yuv_r), P3YuvRed());
-
-  Color yuv_g = srgbRgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv709To601(yuv_g), P3YuvGreen());
-
-  Color yuv_b = srgbRgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv709To601(yuv_b), P3YuvBlue());
-}
-
-TEST_F(GainMapMathTest, Bt709ToBt2100YuvConversion) {
-  Color yuv_black = srgbRgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv709To2100(yuv_black), YuvBlack());
-
-  Color yuv_white = srgbRgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv709To2100(yuv_white), YuvWhite());
-
-  Color yuv_r = srgbRgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv709To2100(yuv_r), Bt2100YuvRed());
-
-  Color yuv_g = srgbRgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv709To2100(yuv_g), Bt2100YuvGreen());
-
-  Color yuv_b = srgbRgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv709To2100(yuv_b), Bt2100YuvBlue());
-}
-
-TEST_F(GainMapMathTest, Bt601ToBt709YuvConversion) {
-  Color yuv_black = p3RgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv601To709(yuv_black), YuvBlack());
-
-  Color yuv_white = p3RgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv601To709(yuv_white), YuvWhite());
-
-  Color yuv_r = p3RgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv601To709(yuv_r), SrgbYuvRed());
-
-  Color yuv_g = p3RgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv601To709(yuv_g), SrgbYuvGreen());
-
-  Color yuv_b = p3RgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv601To709(yuv_b), SrgbYuvBlue());
-}
-
-TEST_F(GainMapMathTest, Bt601ToBt2100YuvConversion) {
-  Color yuv_black = p3RgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv601To2100(yuv_black), YuvBlack());
-
-  Color yuv_white = p3RgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv601To2100(yuv_white), YuvWhite());
-
-  Color yuv_r = p3RgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv601To2100(yuv_r), Bt2100YuvRed());
-
-  Color yuv_g = p3RgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv601To2100(yuv_g), Bt2100YuvGreen());
-
-  Color yuv_b = p3RgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv601To2100(yuv_b), Bt2100YuvBlue());
-}
-
-TEST_F(GainMapMathTest, Bt2100ToBt709YuvConversion) {
-  Color yuv_black = bt2100RgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv2100To709(yuv_black), YuvBlack());
-
-  Color yuv_white = bt2100RgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv2100To709(yuv_white), YuvWhite());
-
-  Color yuv_r = bt2100RgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv2100To709(yuv_r), SrgbYuvRed());
-
-  Color yuv_g = bt2100RgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv2100To709(yuv_g), SrgbYuvGreen());
-
-  Color yuv_b = bt2100RgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv2100To709(yuv_b), SrgbYuvBlue());
-}
-
-TEST_F(GainMapMathTest, Bt2100ToBt601YuvConversion) {
-  Color yuv_black = bt2100RgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv2100To601(yuv_black), YuvBlack());
-
-  Color yuv_white = bt2100RgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv2100To601(yuv_white), YuvWhite());
-
-  Color yuv_r = bt2100RgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv2100To601(yuv_r), P3YuvRed());
-
-  Color yuv_g = bt2100RgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv2100To601(yuv_g), P3YuvGreen());
-
-  Color yuv_b = bt2100RgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv2100To601(yuv_b), P3YuvBlue());
-}
-
-TEST_F(GainMapMathTest, TransformYuv420) {
-  ColorTransformFn transforms[] = { yuv709To601, yuv709To2100, yuv601To709, yuv601To2100,
-                                    yuv2100To709, yuv2100To601 };
-  for (const ColorTransformFn& transform : transforms) {
-    jpegr_uncompressed_struct input = Yuv420Image();
-
-    size_t out_buf_size = input.width * input.height * 3 / 2;
-    std::unique_ptr<uint8_t[]> out_buf = std::make_unique<uint8_t[]>(out_buf_size);
-    memcpy(out_buf.get(), input.data, out_buf_size);
-    jpegr_uncompressed_struct output = Yuv420Image();
-    output.data = out_buf.get();
-    output.chroma_data = out_buf.get() + input.width * input.height;
-    output.luma_stride = input.width;
-    output.chroma_stride = input.width / 2;
-
-    transformYuv420(&output, 1, 1, transform);
-
-    for (size_t y = 0; y < 4; ++y) {
-      for (size_t x = 0; x < 4; ++x) {
-        // Skip the last chroma sample, which we modified above
-        if (x >= 2 && y >= 2) {
-          continue;
-        }
-
-        // All other pixels should remain unchanged
-        EXPECT_YUV_EQ(getYuv420Pixel(&input, x, y), getYuv420Pixel(&output, x, y));
-      }
-    }
-
-    // modified pixels should be updated as intended by the transformYuv420 algorithm
-    Color in1 = getYuv420Pixel(&input,   2, 2);
-    Color in2 = getYuv420Pixel(&input,   3, 2);
-    Color in3 = getYuv420Pixel(&input,   2, 3);
-    Color in4 = getYuv420Pixel(&input,   3, 3);
-    Color out1 = getYuv420Pixel(&output, 2, 2);
-    Color out2 = getYuv420Pixel(&output, 3, 2);
-    Color out3 = getYuv420Pixel(&output, 2, 3);
-    Color out4 = getYuv420Pixel(&output, 3, 3);
-
-    EXPECT_NEAR(transform(in1).y, out1.y, YuvConversionEpsilon());
-    EXPECT_NEAR(transform(in2).y, out2.y, YuvConversionEpsilon());
-    EXPECT_NEAR(transform(in3).y, out3.y, YuvConversionEpsilon());
-    EXPECT_NEAR(transform(in4).y, out4.y, YuvConversionEpsilon());
-
-    Color expect_uv = (transform(in1) + transform(in2) + transform(in3) + transform(in4)) / 4.0f;
-
-    EXPECT_NEAR(expect_uv.u, out1.u, YuvConversionEpsilon());
-    EXPECT_NEAR(expect_uv.u, out2.u, YuvConversionEpsilon());
-    EXPECT_NEAR(expect_uv.u, out3.u, YuvConversionEpsilon());
-    EXPECT_NEAR(expect_uv.u, out4.u, YuvConversionEpsilon());
-
-    EXPECT_NEAR(expect_uv.v, out1.v, YuvConversionEpsilon());
-    EXPECT_NEAR(expect_uv.v, out2.v, YuvConversionEpsilon());
-    EXPECT_NEAR(expect_uv.v, out3.v, YuvConversionEpsilon());
-    EXPECT_NEAR(expect_uv.v, out4.v, YuvConversionEpsilon());
-  }
-}
-
-TEST_F(GainMapMathTest, HlgOetf) {
-  EXPECT_FLOAT_EQ(hlgOetf(0.0f), 0.0f);
-  EXPECT_NEAR(hlgOetf(0.04167f), 0.35357f, ComparisonEpsilon());
-  EXPECT_NEAR(hlgOetf(0.08333f), 0.5f, ComparisonEpsilon());
-  EXPECT_NEAR(hlgOetf(0.5f), 0.87164f, ComparisonEpsilon());
-  EXPECT_FLOAT_EQ(hlgOetf(1.0f), 1.0f);
-
-  Color e = {{{ 0.04167f, 0.08333f, 0.5f }}};
-  Color e_gamma = {{{ 0.35357f, 0.5f, 0.87164f }}};
-  EXPECT_RGB_NEAR(hlgOetf(e), e_gamma);
-}
-
-TEST_F(GainMapMathTest, HlgInvOetf) {
-  EXPECT_FLOAT_EQ(hlgInvOetf(0.0f), 0.0f);
-  EXPECT_NEAR(hlgInvOetf(0.25f), 0.02083f, ComparisonEpsilon());
-  EXPECT_NEAR(hlgInvOetf(0.5f), 0.08333f, ComparisonEpsilon());
-  EXPECT_NEAR(hlgInvOetf(0.75f), 0.26496f, ComparisonEpsilon());
-  EXPECT_FLOAT_EQ(hlgInvOetf(1.0f), 1.0f);
-
-  Color e_gamma = {{{ 0.25f, 0.5f, 0.75f }}};
-  Color e = {{{ 0.02083f, 0.08333f, 0.26496f }}};
-  EXPECT_RGB_NEAR(hlgInvOetf(e_gamma), e);
-}
-
-TEST_F(GainMapMathTest, HlgTransferFunctionRoundtrip) {
-  EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(0.0f)), 0.0f);
-  EXPECT_NEAR(hlgInvOetf(hlgOetf(0.04167f)), 0.04167f, ComparisonEpsilon());
-  EXPECT_NEAR(hlgInvOetf(hlgOetf(0.08333f)), 0.08333f, ComparisonEpsilon());
-  EXPECT_NEAR(hlgInvOetf(hlgOetf(0.5f)), 0.5f, ComparisonEpsilon());
-  EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(1.0f)), 1.0f);
-}
-
-TEST_F(GainMapMathTest, PqOetf) {
-  EXPECT_FLOAT_EQ(pqOetf(0.0f), 0.0f);
-  EXPECT_NEAR(pqOetf(0.01f), 0.50808f, ComparisonEpsilon());
-  EXPECT_NEAR(pqOetf(0.5f), 0.92655f, ComparisonEpsilon());
-  EXPECT_NEAR(pqOetf(0.99f), 0.99895f, ComparisonEpsilon());
-  EXPECT_FLOAT_EQ(pqOetf(1.0f), 1.0f);
-
-  Color e = {{{ 0.01f, 0.5f, 0.99f }}};
-  Color e_gamma = {{{ 0.50808f, 0.92655f, 0.99895f }}};
-  EXPECT_RGB_NEAR(pqOetf(e), e_gamma);
-}
-
-TEST_F(GainMapMathTest, PqInvOetf) {
-  EXPECT_FLOAT_EQ(pqInvOetf(0.0f), 0.0f);
-  EXPECT_NEAR(pqInvOetf(0.01f), 2.31017e-7f, ComparisonEpsilon());
-  EXPECT_NEAR(pqInvOetf(0.5f), 0.00922f, ComparisonEpsilon());
-  EXPECT_NEAR(pqInvOetf(0.99f), 0.90903f, ComparisonEpsilon());
-  EXPECT_FLOAT_EQ(pqInvOetf(1.0f), 1.0f);
-
-  Color e_gamma = {{{ 0.01f, 0.5f, 0.99f }}};
-  Color e = {{{ 2.31017e-7f, 0.00922f, 0.90903f }}};
-  EXPECT_RGB_NEAR(pqInvOetf(e_gamma), e);
-}
-
-TEST_F(GainMapMathTest, PqInvOetfLUT) {
-    for (int idx = 0; idx < kPqInvOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kPqInvOETFNumEntries - 1);
-      EXPECT_FLOAT_EQ(pqInvOetf(value), pqInvOetfLUT(value));
-    }
-}
-
-TEST_F(GainMapMathTest, HlgInvOetfLUT) {
-    for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kHlgInvOETFNumEntries - 1);
-      EXPECT_FLOAT_EQ(hlgInvOetf(value), hlgInvOetfLUT(value));
-    }
-}
-
-TEST_F(GainMapMathTest, pqOetfLUT) {
-    for (int idx = 0; idx < kPqOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kPqOETFNumEntries - 1);
-      EXPECT_FLOAT_EQ(pqOetf(value), pqOetfLUT(value));
-    }
-}
-
-TEST_F(GainMapMathTest, hlgOetfLUT) {
-    for (int idx = 0; idx < kHlgOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kHlgOETFNumEntries - 1);
-      EXPECT_FLOAT_EQ(hlgOetf(value), hlgOetfLUT(value));
-    }
-}
-
-TEST_F(GainMapMathTest, srgbInvOetfLUT) {
-    for (int idx = 0; idx < kSrgbInvOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kSrgbInvOETFNumEntries - 1);
-      EXPECT_FLOAT_EQ(srgbInvOetf(value), srgbInvOetfLUT(value));
-    }
-}
-
-TEST_F(GainMapMathTest, applyGainLUT) {
-  for (int boost = 1; boost <= 10; boost++) {
-    ultrahdr_metadata_struct metadata = { .maxContentBoost = static_cast<float>(boost),
-                                       .minContentBoost = 1.0f / static_cast<float>(boost) };
-    GainLUT gainLUT(&metadata);
-    GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost);
-    for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
-      EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata),
-                      applyGainLUT(RgbBlack(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata),
-                      applyGainLUT(RgbWhite(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata),
-                      applyGainLUT(RgbRed(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata),
-                      applyGainLUT(RgbGreen(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata),
-                      applyGainLUT(RgbBlue(), value, gainLUT));
-      EXPECT_RGB_EQ(applyGainLUT(RgbBlack(), value, gainLUT),
-                    applyGainLUT(RgbBlack(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbWhite(), value, gainLUT),
-                    applyGainLUT(RgbWhite(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbRed(), value, gainLUT),
-                    applyGainLUT(RgbRed(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbGreen(), value, gainLUT),
-                    applyGainLUT(RgbGreen(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbBlue(), value, gainLUT),
-                    applyGainLUT(RgbBlue(), value, gainLUTWithBoost));
-    }
-  }
-
-  for (int boost = 1; boost <= 10; boost++) {
-    ultrahdr_metadata_struct metadata = { .maxContentBoost = static_cast<float>(boost),
-                                       .minContentBoost = 1.0f };
-    GainLUT gainLUT(&metadata);
-    GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost);
-    for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
-      EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata),
-                      applyGainLUT(RgbBlack(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata),
-                      applyGainLUT(RgbWhite(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata),
-                      applyGainLUT(RgbRed(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata),
-                      applyGainLUT(RgbGreen(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata),
-                      applyGainLUT(RgbBlue(), value, gainLUT));
-      EXPECT_RGB_EQ(applyGainLUT(RgbBlack(), value, gainLUT),
-                    applyGainLUT(RgbBlack(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbWhite(), value, gainLUT),
-                    applyGainLUT(RgbWhite(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbRed(), value, gainLUT),
-                    applyGainLUT(RgbRed(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbGreen(), value, gainLUT),
-                    applyGainLUT(RgbGreen(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbBlue(), value, gainLUT),
-                    applyGainLUT(RgbBlue(), value, gainLUTWithBoost));
-    }
-  }
-
-  for (int boost = 1; boost <= 10; boost++) {
-    ultrahdr_metadata_struct metadata = { .maxContentBoost = static_cast<float>(boost),
-                                       .minContentBoost = 1.0f / pow(static_cast<float>(boost),
-                                                              1.0f / 3.0f) };
-    GainLUT gainLUT(&metadata);
-    GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost);
-    for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1);
-      EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata),
-                      applyGainLUT(RgbBlack(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata),
-                      applyGainLUT(RgbWhite(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata),
-                      applyGainLUT(RgbRed(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata),
-                      applyGainLUT(RgbGreen(), value, gainLUT));
-      EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata),
-                      applyGainLUT(RgbBlue(), value, gainLUT));
-      EXPECT_RGB_EQ(applyGainLUT(RgbBlack(), value, gainLUT),
-                    applyGainLUT(RgbBlack(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbWhite(), value, gainLUT),
-                    applyGainLUT(RgbWhite(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbRed(), value, gainLUT),
-                    applyGainLUT(RgbRed(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbGreen(), value, gainLUT),
-                    applyGainLUT(RgbGreen(), value, gainLUTWithBoost));
-      EXPECT_RGB_EQ(applyGainLUT(RgbBlue(), value, gainLUT),
-                    applyGainLUT(RgbBlue(), value, gainLUTWithBoost));
-    }
-  }
-}
-
-TEST_F(GainMapMathTest, PqTransferFunctionRoundtrip) {
-  EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(0.0f)), 0.0f);
-  EXPECT_NEAR(pqInvOetf(pqOetf(0.01f)), 0.01f, ComparisonEpsilon());
-  EXPECT_NEAR(pqInvOetf(pqOetf(0.5f)), 0.5f, ComparisonEpsilon());
-  EXPECT_NEAR(pqInvOetf(pqOetf(0.99f)), 0.99f, ComparisonEpsilon());
-  EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(1.0f)), 1.0f);
-}
-
-TEST_F(GainMapMathTest, ColorConversionLookup) {
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_UNSPECIFIED),
-            nullptr);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_BT709),
-            identityConversion);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3),
-            p3ToBt709);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_BT2100),
-            bt2100ToBt709);
-
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_UNSPECIFIED),
-            nullptr);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_BT709),
-            bt709ToP3);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_P3),
-            identityConversion);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_BT2100),
-            bt2100ToP3);
-
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_UNSPECIFIED),
-            nullptr);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_BT709),
-            bt709ToBt2100);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_P3),
-            p3ToBt2100);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_BT2100),
-            identityConversion);
-
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_UNSPECIFIED),
-            nullptr);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_BT709),
-            nullptr);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_P3),
-            nullptr);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_BT2100),
-            nullptr);
-}
-
-TEST_F(GainMapMathTest, EncodeGain) {
-  ultrahdr_metadata_struct metadata = { .maxContentBoost = 4.0f,
-                                        .minContentBoost = 1.0f / 4.0f };
-
-  EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 127);
-  EXPECT_EQ(encodeGain(0.0f, 1.0f, &metadata), 127);
-  EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0);
-  EXPECT_EQ(encodeGain(0.5f, 0.0f, &metadata), 0);
-
-  EXPECT_EQ(encodeGain(1.0f, 1.0f, &metadata), 127);
-  EXPECT_EQ(encodeGain(1.0f, 4.0f, &metadata), 255);
-  EXPECT_EQ(encodeGain(1.0f, 5.0f, &metadata), 255);
-  EXPECT_EQ(encodeGain(4.0f, 1.0f, &metadata), 0);
-  EXPECT_EQ(encodeGain(4.0f, 0.5f, &metadata), 0);
-  EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 191);
-  EXPECT_EQ(encodeGain(2.0f, 1.0f, &metadata), 63);
-
-  metadata.maxContentBoost = 2.0f;
-  metadata.minContentBoost = 1.0f / 2.0f;
-
-  EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 255);
-  EXPECT_EQ(encodeGain(2.0f, 1.0f, &metadata), 0);
-  EXPECT_EQ(encodeGain(1.0f, 1.41421f, &metadata), 191);
-  EXPECT_EQ(encodeGain(1.41421f, 1.0f, &metadata), 63);
-
-  metadata.maxContentBoost = 8.0f;
-  metadata.minContentBoost = 1.0f / 8.0f;
-
-  EXPECT_EQ(encodeGain(1.0f, 8.0f, &metadata), 255);
-  EXPECT_EQ(encodeGain(8.0f, 1.0f, &metadata), 0);
-  EXPECT_EQ(encodeGain(1.0f, 2.82843f, &metadata), 191);
-  EXPECT_EQ(encodeGain(2.82843f, 1.0f, &metadata), 63);
-
-  metadata.maxContentBoost = 8.0f;
-  metadata.minContentBoost = 1.0f;
-
-  EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 0);
-  EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0);
-
-  EXPECT_EQ(encodeGain(1.0f, 1.0f, &metadata), 0);
-  EXPECT_EQ(encodeGain(1.0f, 8.0f, &metadata), 255);
-  EXPECT_EQ(encodeGain(1.0f, 4.0f, &metadata), 170);
-  EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 85);
-
-  metadata.maxContentBoost = 8.0f;
-  metadata.minContentBoost = 0.5f;
-
-  EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 63);
-  EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0);
-
-  EXPECT_EQ(encodeGain(1.0f, 1.0f, &metadata), 63);
-  EXPECT_EQ(encodeGain(1.0f, 8.0f, &metadata), 255);
-  EXPECT_EQ(encodeGain(1.0f, 4.0f, &metadata), 191);
-  EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 127);
-  EXPECT_EQ(encodeGain(1.0f, 0.7071f, &metadata), 31);
-  EXPECT_EQ(encodeGain(1.0f, 0.5f, &metadata), 0);
-}
-
-TEST_F(GainMapMathTest, ApplyGain) {
-  ultrahdr_metadata_struct metadata = { .maxContentBoost = 4.0f,
-                                        .minContentBoost = 1.0f / 4.0f };
-  float displayBoost = metadata.maxContentBoost;
-
-  EXPECT_RGB_NEAR(applyGain(RgbBlack(), 0.0f, &metadata), RgbBlack());
-  EXPECT_RGB_NEAR(applyGain(RgbBlack(), 0.5f, &metadata), RgbBlack());
-  EXPECT_RGB_NEAR(applyGain(RgbBlack(), 1.0f, &metadata), RgbBlack());
-
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 4.0f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.0f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite());
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.0f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 4.0f);
-
-  metadata.maxContentBoost = 2.0f;
-  metadata.minContentBoost = 1.0f / 2.0f;
-
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 1.41421f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite());
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 1.41421f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 2.0f);
-
-  metadata.maxContentBoost = 8.0f;
-  metadata.minContentBoost = 1.0f / 8.0f;
-
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 8.0f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.82843f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite());
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.82843f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
-
-  metadata.maxContentBoost = 8.0f;
-  metadata.minContentBoost = 1.0f;
-
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite());
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f / 3.0f, &metadata), RgbWhite() * 2.0f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 2.0f / 3.0f, &metadata), RgbWhite() * 4.0f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
-
-  metadata.maxContentBoost = 8.0f;
-  metadata.minContentBoost = 0.5f;
-
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite());
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite() * 2.0f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 4.0f);
-  EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f);
-
-  Color e = {{{ 0.0f, 0.5f, 1.0f }}};
-  metadata.maxContentBoost = 4.0f;
-  metadata.minContentBoost = 1.0f / 4.0f;
-
-  EXPECT_RGB_NEAR(applyGain(e, 0.0f, &metadata), e / 4.0f);
-  EXPECT_RGB_NEAR(applyGain(e, 0.25f, &metadata), e / 2.0f);
-  EXPECT_RGB_NEAR(applyGain(e, 0.5f, &metadata), e);
-  EXPECT_RGB_NEAR(applyGain(e, 0.75f, &metadata), e * 2.0f);
-  EXPECT_RGB_NEAR(applyGain(e, 1.0f, &metadata), e * 4.0f);
-
-  EXPECT_RGB_EQ(applyGain(RgbBlack(), 1.0f, &metadata),
-                applyGain(RgbBlack(), 1.0f, &metadata, displayBoost));
-  EXPECT_RGB_EQ(applyGain(RgbWhite(), 1.0f, &metadata),
-                applyGain(RgbWhite(), 1.0f, &metadata, displayBoost));
-  EXPECT_RGB_EQ(applyGain(RgbRed(), 1.0f, &metadata),
-                applyGain(RgbRed(), 1.0f, &metadata, displayBoost));
-  EXPECT_RGB_EQ(applyGain(RgbGreen(), 1.0f, &metadata),
-                applyGain(RgbGreen(), 1.0f, &metadata, displayBoost));
-  EXPECT_RGB_EQ(applyGain(RgbBlue(), 1.0f, &metadata),
-                applyGain(RgbBlue(), 1.0f, &metadata, displayBoost));
-  EXPECT_RGB_EQ(applyGain(e, 1.0f, &metadata),
-                applyGain(e, 1.0f, &metadata, displayBoost));
-}
-
-TEST_F(GainMapMathTest, GetYuv420Pixel) {
-  jpegr_uncompressed_struct image = Yuv420Image();
-  Color (*colors)[4] = Yuv420Colors();
-
-  for (size_t y = 0; y < 4; ++y) {
-    for (size_t x = 0; x < 4; ++x) {
-      EXPECT_YUV_NEAR(getYuv420Pixel(&image, x, y), colors[y][x]);
-    }
-  }
-}
-
-TEST_F(GainMapMathTest, GetP010Pixel) {
-  jpegr_uncompressed_struct image = P010Image();
-  Color (*colors)[4] = P010Colors();
-
-  for (size_t y = 0; y < 4; ++y) {
-    for (size_t x = 0; x < 4; ++x) {
-      EXPECT_YUV_NEAR(getP010Pixel(&image, x, y), colors[y][x]);
-    }
-  }
-}
-
-TEST_F(GainMapMathTest, SampleYuv420) {
-  jpegr_uncompressed_struct image = Yuv420Image();
-  Color (*colors)[4] = Yuv420Colors();
-
-  static const size_t kMapScaleFactor = 2;
-  for (size_t y = 0; y < 4 / kMapScaleFactor; ++y) {
-    for (size_t x = 0; x < 4 / kMapScaleFactor; ++x) {
-      Color min = {{{ 1.0f, 1.0f, 1.0f }}};
-      Color max = {{{ -1.0f, -1.0f, -1.0f }}};
-
-      for (size_t dy = 0; dy < kMapScaleFactor; ++dy) {
-        for (size_t dx = 0; dx < kMapScaleFactor; ++dx) {
-          Color e = colors[y * kMapScaleFactor + dy][x * kMapScaleFactor + dx];
-          min = ColorMin(min, e);
-          max = ColorMax(max, e);
-        }
-      }
-
-      // Instead of reimplementing the sampling algorithm, confirm that the
-      // sample output is within the range of the min and max of the nearest
-      // points.
-      EXPECT_YUV_BETWEEN(sampleYuv420(&image, kMapScaleFactor, x, y), min, max);
-    }
-  }
-}
-
-TEST_F(GainMapMathTest, SampleP010) {
-  jpegr_uncompressed_struct image = P010Image();
-  Color (*colors)[4] = P010Colors();
-
-  static const size_t kMapScaleFactor = 2;
-  for (size_t y = 0; y < 4 / kMapScaleFactor; ++y) {
-    for (size_t x = 0; x < 4 / kMapScaleFactor; ++x) {
-      Color min = {{{ 1.0f, 1.0f, 1.0f }}};
-      Color max = {{{ -1.0f, -1.0f, -1.0f }}};
-
-      for (size_t dy = 0; dy < kMapScaleFactor; ++dy) {
-        for (size_t dx = 0; dx < kMapScaleFactor; ++dx) {
-          Color e = colors[y * kMapScaleFactor + dy][x * kMapScaleFactor + dx];
-          min = ColorMin(min, e);
-          max = ColorMax(max, e);
-        }
-      }
-
-      // Instead of reimplementing the sampling algorithm, confirm that the
-      // sample output is within the range of the min and max of the nearest
-      // points.
-      EXPECT_YUV_BETWEEN(sampleP010(&image, kMapScaleFactor, x, y), min, max);
-    }
-  }
-}
-
-TEST_F(GainMapMathTest, SampleMap) {
-  jpegr_uncompressed_struct image = MapImage();
-  float (*values)[4] = MapValues();
-
-  static const size_t kMapScaleFactor = 2;
-  ShepardsIDW idwTable(kMapScaleFactor);
-  for (size_t y = 0; y < 4 * kMapScaleFactor; ++y) {
-    for (size_t x = 0; x < 4 * kMapScaleFactor; ++x) {
-      size_t x_base = x / kMapScaleFactor;
-      size_t y_base = y / kMapScaleFactor;
-
-      float min = 1.0f;
-      float max = -1.0f;
-
-      min = fmin(min, values[y_base][x_base]);
-      max = fmax(max, values[y_base][x_base]);
-      if (y_base + 1 < 4) {
-        min = fmin(min, values[y_base + 1][x_base]);
-        max = fmax(max, values[y_base + 1][x_base]);
-      }
-      if (x_base + 1 < 4) {
-        min = fmin(min, values[y_base][x_base + 1]);
-        max = fmax(max, values[y_base][x_base + 1]);
-      }
-      if (y_base + 1 < 4 && x_base + 1 < 4) {
-        min = fmin(min, values[y_base + 1][x_base + 1]);
-        max = fmax(max, values[y_base + 1][x_base + 1]);
-      }
-
-      // Instead of reimplementing the sampling algorithm, confirm that the
-      // sample output is within the range of the min and max of the nearest
-      // points.
-      EXPECT_THAT(sampleMap(&image, kMapScaleFactor, x, y),
-                  testing::AllOf(testing::Ge(min), testing::Le(max)));
-      EXPECT_EQ(sampleMap(&image, kMapScaleFactor, x, y, idwTable),
-                sampleMap(&image, kMapScaleFactor, x, y));
-    }
-  }
-}
-
-TEST_F(GainMapMathTest, ColorToRgba1010102) {
-  EXPECT_EQ(colorToRgba1010102(RgbBlack()), 0x3 << 30);
-  EXPECT_EQ(colorToRgba1010102(RgbWhite()), 0xFFFFFFFF);
-  EXPECT_EQ(colorToRgba1010102(RgbRed()), 0x3 << 30 | 0x3ff);
-  EXPECT_EQ(colorToRgba1010102(RgbGreen()), 0x3 << 30 | 0x3ff << 10);
-  EXPECT_EQ(colorToRgba1010102(RgbBlue()), 0x3 << 30 | 0x3ff << 20);
-
-  Color e_gamma = {{{ 0.1f, 0.2f, 0.3f }}};
-  EXPECT_EQ(colorToRgba1010102(e_gamma),
-            0x3 << 30
-          | static_cast<uint32_t>(0.1f * static_cast<float>(0x3ff))
-          | static_cast<uint32_t>(0.2f * static_cast<float>(0x3ff)) << 10
-          | static_cast<uint32_t>(0.3f * static_cast<float>(0x3ff)) << 20);
-}
-
-TEST_F(GainMapMathTest, ColorToRgbaF16) {
-  EXPECT_EQ(colorToRgbaF16(RgbBlack()), ((uint64_t) 0x3C00) << 48);
-  EXPECT_EQ(colorToRgbaF16(RgbWhite()), 0x3C003C003C003C00);
-  EXPECT_EQ(colorToRgbaF16(RgbRed()),   (((uint64_t) 0x3C00) << 48) | ((uint64_t) 0x3C00));
-  EXPECT_EQ(colorToRgbaF16(RgbGreen()), (((uint64_t) 0x3C00) << 48) | (((uint64_t) 0x3C00) << 16));
-  EXPECT_EQ(colorToRgbaF16(RgbBlue()),  (((uint64_t) 0x3C00) << 48) | (((uint64_t) 0x3C00) << 32));
-
-  Color e_gamma = {{{ 0.1f, 0.2f, 0.3f }}};
-  EXPECT_EQ(colorToRgbaF16(e_gamma), 0x3C0034CD32662E66);
-}
-
-TEST_F(GainMapMathTest, Float32ToFloat16) {
-  EXPECT_EQ(floatToHalf(0.1f), 0x2E66);
-  EXPECT_EQ(floatToHalf(0.0f), 0x0);
-  EXPECT_EQ(floatToHalf(1.0f), 0x3C00);
-  EXPECT_EQ(floatToHalf(-1.0f), 0xBC00);
-  EXPECT_EQ(floatToHalf(0x1.fffffep127f), 0x7FFF);  // float max
-  EXPECT_EQ(floatToHalf(-0x1.fffffep127f), 0xFFFF);  // float min
-  EXPECT_EQ(floatToHalf(0x1.0p-126f), 0x0);  // float zero
-}
-
-TEST_F(GainMapMathTest, GenerateMapLuminanceSrgb) {
-  EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), srgbLuminance),
-                  0.0f);
-  EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), srgbLuminance),
-                  kSdrWhiteNits);
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), srgbLuminance),
-              srgbLuminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon());
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), srgbLuminance),
-              srgbLuminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon());
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), srgbLuminance),
-              srgbLuminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
-}
-
-TEST_F(GainMapMathTest, GenerateMapLuminanceSrgbP3) {
-  EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), p3Luminance),
-                  0.0f);
-  EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), p3Luminance),
-                  kSdrWhiteNits);
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), p3Luminance),
-              p3Luminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon());
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), p3Luminance),
-              p3Luminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon());
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), p3Luminance),
-              p3Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
-}
-
-TEST_F(GainMapMathTest, GenerateMapLuminanceSrgbBt2100) {
-  EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), bt2100Luminance),
-                  0.0f);
-  EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), bt2100Luminance),
-                  kSdrWhiteNits);
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), bt2100Luminance),
-              bt2100Luminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon());
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), bt2100Luminance),
-              bt2100Luminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon());
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), bt2100Luminance),
-              bt2100Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
-}
-
-TEST_F(GainMapMathTest, GenerateMapLuminanceHlg) {
-  EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), hlgInvOetf, identityConversion,
-                                       bt2100Luminance, kHlgMaxNits),
-                  0.0f);
-  EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvWhite(), hlgInvOetf, identityConversion,
-                                       bt2100Luminance, kHlgMaxNits),
-                  kHlgMaxNits);
-  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvRed(), hlgInvOetf, identityConversion,
-                                   bt2100Luminance, kHlgMaxNits),
-              bt2100Luminance(RgbRed()) * kHlgMaxNits, LuminanceEpsilon());
-  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvGreen(), hlgInvOetf, identityConversion,
-                                   bt2100Luminance, kHlgMaxNits),
-              bt2100Luminance(RgbGreen()) * kHlgMaxNits, LuminanceEpsilon());
-  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvBlue(), hlgInvOetf, identityConversion,
-                                   bt2100Luminance, kHlgMaxNits),
-              bt2100Luminance(RgbBlue()) * kHlgMaxNits, LuminanceEpsilon());
-}
-
-TEST_F(GainMapMathTest, GenerateMapLuminancePq) {
-  EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), pqInvOetf, identityConversion,
-                                       bt2100Luminance, kPqMaxNits),
-                  0.0f);
-  EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvWhite(), pqInvOetf, identityConversion,
-                                       bt2100Luminance, kPqMaxNits),
-                  kPqMaxNits);
-  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvRed(), pqInvOetf, identityConversion,
-                                       bt2100Luminance, kPqMaxNits),
-              bt2100Luminance(RgbRed()) * kPqMaxNits, LuminanceEpsilon());
-  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvGreen(), pqInvOetf, identityConversion,
-                                       bt2100Luminance, kPqMaxNits),
-              bt2100Luminance(RgbGreen()) * kPqMaxNits, LuminanceEpsilon());
-  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvBlue(), pqInvOetf, identityConversion,
-                                       bt2100Luminance, kPqMaxNits),
-              bt2100Luminance(RgbBlue()) * kPqMaxNits, LuminanceEpsilon());
-}
-
-TEST_F(GainMapMathTest, ApplyMap) {
-  ultrahdr_metadata_struct metadata = { .maxContentBoost = 8.0f,
-                                     .minContentBoost = 1.0f / 8.0f };
-
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata),
-                RgbWhite() * 8.0f);
-  EXPECT_RGB_EQ(Recover(YuvBlack(), 1.0f, &metadata),
-                RgbBlack());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 1.0f, &metadata),
-                  RgbRed() * 8.0f);
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 1.0f, &metadata),
-                  RgbGreen() * 8.0f);
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 1.0f, &metadata),
-                  RgbBlue() * 8.0f);
-
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.75f, &metadata),
-                RgbWhite() * sqrt(8.0f));
-  EXPECT_RGB_EQ(Recover(YuvBlack(), 0.75f, &metadata),
-                RgbBlack());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.75f, &metadata),
-                  RgbRed() * sqrt(8.0f));
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.75f, &metadata),
-                  RgbGreen() * sqrt(8.0f));
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.75f, &metadata),
-                  RgbBlue() * sqrt(8.0f));
-
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, &metadata),
-                RgbWhite());
-  EXPECT_RGB_EQ(Recover(YuvBlack(), 0.5f, &metadata),
-                RgbBlack());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.5f, &metadata),
-                  RgbRed());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.5f, &metadata),
-                  RgbGreen());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.5f, &metadata),
-                  RgbBlue());
-
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.25f, &metadata),
-                RgbWhite() / sqrt(8.0f));
-  EXPECT_RGB_EQ(Recover(YuvBlack(), 0.25f, &metadata),
-                RgbBlack());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.25f, &metadata),
-                  RgbRed() / sqrt(8.0f));
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.25f, &metadata),
-                  RgbGreen() / sqrt(8.0f));
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.25f, &metadata),
-                  RgbBlue() / sqrt(8.0f));
-
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata),
-                RgbWhite() / 8.0f);
-  EXPECT_RGB_EQ(Recover(YuvBlack(), 0.0f, &metadata),
-                RgbBlack());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.0f, &metadata),
-                  RgbRed() / 8.0f);
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.0f, &metadata),
-                  RgbGreen() / 8.0f);
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.0f, &metadata),
-                  RgbBlue() / 8.0f);
-
-  metadata.maxContentBoost = 8.0f;
-  metadata.minContentBoost = 1.0f;
-
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata),
-                RgbWhite() * 8.0f);
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 2.0f / 3.0f, &metadata),
-                RgbWhite() * 4.0f);
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f / 3.0f, &metadata),
-                RgbWhite() * 2.0f);
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata),
-                RgbWhite());
-
-  metadata.maxContentBoost = 8.0f;
-  metadata.minContentBoost = 0.5f;;
-
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata),
-                RgbWhite() * 8.0f);
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.75, &metadata),
-                RgbWhite() * 4.0f);
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, &metadata),
-                RgbWhite() * 2.0f);
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.25f, &metadata),
-                RgbWhite());
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata),
-                RgbWhite() / 2.0f);
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/tests/icchelper_test.cpp b/libs/ultrahdr/tests/icchelper_test.cpp
deleted file mode 100644
index ff61c08..0000000
--- a/libs/ultrahdr/tests/icchelper_test.cpp
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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 <gtest/gtest.h>
-#include <ultrahdr/icc.h>
-#include <ultrahdr/ultrahdr.h>
-#include <utils/Log.h>
-
-namespace android::ultrahdr {
-
-class IccHelperTest : public testing::Test {
-public:
-    IccHelperTest();
-    ~IccHelperTest();
-protected:
-    virtual void SetUp();
-    virtual void TearDown();
-};
-
-IccHelperTest::IccHelperTest() {}
-
-IccHelperTest::~IccHelperTest() {}
-
-void IccHelperTest::SetUp() {}
-
-void IccHelperTest::TearDown() {}
-
-TEST_F(IccHelperTest, iccWriteThenRead) {
-    sp<DataStruct> iccBt709 = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
-                                                         ULTRAHDR_COLORGAMUT_BT709);
-    ASSERT_NE(iccBt709->getLength(), 0);
-    ASSERT_NE(iccBt709->getData(), nullptr);
-    EXPECT_EQ(IccHelper::readIccColorGamut(iccBt709->getData(), iccBt709->getLength()),
-              ULTRAHDR_COLORGAMUT_BT709);
-
-    sp<DataStruct> iccP3 = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_P3);
-    ASSERT_NE(iccP3->getLength(), 0);
-    ASSERT_NE(iccP3->getData(), nullptr);
-    EXPECT_EQ(IccHelper::readIccColorGamut(iccP3->getData(), iccP3->getLength()),
-              ULTRAHDR_COLORGAMUT_P3);
-
-    sp<DataStruct> iccBt2100 = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
-                                                          ULTRAHDR_COLORGAMUT_BT2100);
-    ASSERT_NE(iccBt2100->getLength(), 0);
-    ASSERT_NE(iccBt2100->getData(), nullptr);
-    EXPECT_EQ(IccHelper::readIccColorGamut(iccBt2100->getData(), iccBt2100->getLength()),
-              ULTRAHDR_COLORGAMUT_BT2100);
-}
-
-TEST_F(IccHelperTest, iccEndianness) {
-    sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_BT709);
-    size_t profile_size = icc->getLength() - kICCIdentifierSize;
-
-    uint8_t* icc_bytes = reinterpret_cast<uint8_t*>(icc->getData()) + kICCIdentifierSize;
-    uint32_t encoded_size = static_cast<uint32_t>(icc_bytes[0]) << 24 |
-                            static_cast<uint32_t>(icc_bytes[1]) << 16 |
-                            static_cast<uint32_t>(icc_bytes[2]) << 8 |
-                            static_cast<uint32_t>(icc_bytes[3]);
-
-    EXPECT_EQ(static_cast<size_t>(encoded_size), profile_size);
-}
-
-}  // namespace android::ultrahdr
-
diff --git a/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp b/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp
deleted file mode 100644
index af0d59e..0000000
--- a/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * 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 <gtest/gtest.h>
-#include <ultrahdr/icc.h>
-#include <ultrahdr/jpegdecoderhelper.h>
-#include <utils/Log.h>
-
-#include <fcntl.h>
-
-namespace android::ultrahdr {
-
-// No ICC or EXIF
-#define YUV_IMAGE "/data/local/tmp/minnie-320x240-yuv.jpg"
-#define YUV_IMAGE_SIZE 20193
-// Has ICC and EXIF
-#define YUV_ICC_IMAGE "/data/local/tmp/minnie-320x240-yuv-icc.jpg"
-#define YUV_ICC_IMAGE_SIZE 34266
-// No ICC or EXIF
-#define GREY_IMAGE "/data/local/tmp/minnie-320x240-y.jpg"
-#define GREY_IMAGE_SIZE 20193
-
-#define IMAGE_WIDTH 320
-#define IMAGE_HEIGHT 240
-
-class JpegDecoderHelperTest : public testing::Test {
-public:
-    struct Image {
-        std::unique_ptr<uint8_t[]> buffer;
-        size_t size;
-    };
-    JpegDecoderHelperTest();
-    ~JpegDecoderHelperTest();
-
-protected:
-    virtual void SetUp();
-    virtual void TearDown();
-
-    Image mYuvImage, mYuvIccImage, mGreyImage;
-};
-
-JpegDecoderHelperTest::JpegDecoderHelperTest() {}
-
-JpegDecoderHelperTest::~JpegDecoderHelperTest() {}
-
-static size_t getFileSize(int fd) {
-    struct stat st;
-    if (fstat(fd, &st) < 0) {
-        ALOGW("%s : fstat failed", __func__);
-        return 0;
-    }
-    return st.st_size; // bytes
-}
-
-static bool loadFile(const char filename[], JpegDecoderHelperTest::Image* result) {
-    int fd = open(filename, O_CLOEXEC);
-    if (fd < 0) {
-        return false;
-    }
-    int length = getFileSize(fd);
-    if (length == 0) {
-        close(fd);
-        return false;
-    }
-    result->buffer.reset(new uint8_t[length]);
-    if (read(fd, result->buffer.get(), length) != static_cast<ssize_t>(length)) {
-        close(fd);
-        return false;
-    }
-    close(fd);
-    return true;
-}
-
-void JpegDecoderHelperTest::SetUp() {
-    if (!loadFile(YUV_IMAGE, &mYuvImage)) {
-        FAIL() << "Load file " << YUV_IMAGE << " failed";
-    }
-    mYuvImage.size = YUV_IMAGE_SIZE;
-    if (!loadFile(YUV_ICC_IMAGE, &mYuvIccImage)) {
-        FAIL() << "Load file " << YUV_ICC_IMAGE << " failed";
-    }
-    mYuvIccImage.size = YUV_ICC_IMAGE_SIZE;
-    if (!loadFile(GREY_IMAGE, &mGreyImage)) {
-        FAIL() << "Load file " << GREY_IMAGE << " failed";
-    }
-    mGreyImage.size = GREY_IMAGE_SIZE;
-}
-
-void JpegDecoderHelperTest::TearDown() {}
-
-TEST_F(JpegDecoderHelperTest, decodeYuvImage) {
-    JpegDecoderHelper decoder;
-    EXPECT_TRUE(decoder.decompressImage(mYuvImage.buffer.get(), mYuvImage.size));
-    ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0));
-    EXPECT_EQ(IccHelper::readIccColorGamut(decoder.getICCPtr(), decoder.getICCSize()),
-              ULTRAHDR_COLORGAMUT_UNSPECIFIED);
-}
-
-TEST_F(JpegDecoderHelperTest, decodeYuvIccImage) {
-    JpegDecoderHelper decoder;
-    EXPECT_TRUE(decoder.decompressImage(mYuvIccImage.buffer.get(), mYuvIccImage.size));
-    ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0));
-    EXPECT_EQ(IccHelper::readIccColorGamut(decoder.getICCPtr(), decoder.getICCSize()),
-              ULTRAHDR_COLORGAMUT_BT709);
-}
-
-TEST_F(JpegDecoderHelperTest, decodeGreyImage) {
-    JpegDecoderHelper decoder;
-    EXPECT_TRUE(decoder.decompressImage(mGreyImage.buffer.get(), mGreyImage.size));
-    ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0));
-}
-
-TEST_F(JpegDecoderHelperTest, getCompressedImageParameters) {
-    size_t width = 0, height = 0;
-    std::vector<uint8_t> icc, exif;
-
-    JpegDecoderHelper decoder;
-    EXPECT_TRUE(decoder.getCompressedImageParameters(mYuvImage.buffer.get(), mYuvImage.size, &width,
-                                                     &height, &icc, &exif));
-
-    EXPECT_EQ(width, IMAGE_WIDTH);
-    EXPECT_EQ(height, IMAGE_HEIGHT);
-    EXPECT_EQ(icc.size(), 0);
-    EXPECT_EQ(exif.size(), 0);
-}
-
-TEST_F(JpegDecoderHelperTest, getCompressedImageParametersIcc) {
-    size_t width = 0, height = 0;
-    std::vector<uint8_t> icc, exif;
-
-    JpegDecoderHelper decoder;
-    EXPECT_TRUE(decoder.getCompressedImageParameters(mYuvIccImage.buffer.get(), mYuvIccImage.size,
-                                                     &width, &height, &icc, &exif));
-
-    EXPECT_EQ(width, IMAGE_WIDTH);
-    EXPECT_EQ(height, IMAGE_HEIGHT);
-    EXPECT_GT(icc.size(), 0);
-    EXPECT_GT(exif.size(), 0);
-
-    EXPECT_EQ(IccHelper::readIccColorGamut(icc.data(), icc.size()), ULTRAHDR_COLORGAMUT_BT709);
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/tests/jpegencoderhelper_test.cpp b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp
deleted file mode 100644
index af54eb2..0000000
--- a/libs/ultrahdr/tests/jpegencoderhelper_test.cpp
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * 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 <ultrahdr/jpegencoderhelper.h>
-#include <gtest/gtest.h>
-#include <utils/Log.h>
-
-#include <fcntl.h>
-
-namespace android::ultrahdr {
-
-#define ALIGNED_IMAGE "/data/local/tmp/minnie-320x240.yu12"
-#define ALIGNED_IMAGE_WIDTH 320
-#define ALIGNED_IMAGE_HEIGHT 240
-#define SINGLE_CHANNEL_IMAGE "/data/local/tmp/minnie-320x240.y"
-#define SINGLE_CHANNEL_IMAGE_WIDTH ALIGNED_IMAGE_WIDTH
-#define SINGLE_CHANNEL_IMAGE_HEIGHT ALIGNED_IMAGE_HEIGHT
-#define UNALIGNED_IMAGE "/data/local/tmp/minnie-318x240.yu12"
-#define UNALIGNED_IMAGE_WIDTH 318
-#define UNALIGNED_IMAGE_HEIGHT 240
-#define JPEG_QUALITY 90
-
-class JpegEncoderHelperTest : public testing::Test {
-public:
-    struct Image {
-        std::unique_ptr<uint8_t[]> buffer;
-        size_t width;
-        size_t height;
-    };
-    JpegEncoderHelperTest();
-    ~JpegEncoderHelperTest();
-
-protected:
-    virtual void SetUp();
-    virtual void TearDown();
-
-    Image mAlignedImage, mUnalignedImage, mSingleChannelImage;
-};
-
-JpegEncoderHelperTest::JpegEncoderHelperTest() {}
-
-JpegEncoderHelperTest::~JpegEncoderHelperTest() {}
-
-static size_t getFileSize(int fd) {
-    struct stat st;
-    if (fstat(fd, &st) < 0) {
-        ALOGW("%s : fstat failed", __func__);
-        return 0;
-    }
-    return st.st_size; // bytes
-}
-
-static bool loadFile(const char filename[], JpegEncoderHelperTest::Image* result) {
-    int fd = open(filename, O_CLOEXEC);
-    if (fd < 0) {
-        return false;
-    }
-    int length = getFileSize(fd);
-    if (length == 0) {
-        close(fd);
-        return false;
-    }
-    result->buffer.reset(new uint8_t[length]);
-    if (read(fd, result->buffer.get(), length) != static_cast<ssize_t>(length)) {
-        close(fd);
-        return false;
-    }
-    close(fd);
-    return true;
-}
-
-void JpegEncoderHelperTest::SetUp() {
-    if (!loadFile(ALIGNED_IMAGE, &mAlignedImage)) {
-        FAIL() << "Load file " << ALIGNED_IMAGE << " failed";
-    }
-    mAlignedImage.width = ALIGNED_IMAGE_WIDTH;
-    mAlignedImage.height = ALIGNED_IMAGE_HEIGHT;
-    if (!loadFile(UNALIGNED_IMAGE, &mUnalignedImage)) {
-        FAIL() << "Load file " << UNALIGNED_IMAGE << " failed";
-    }
-    mUnalignedImage.width = UNALIGNED_IMAGE_WIDTH;
-    mUnalignedImage.height = UNALIGNED_IMAGE_HEIGHT;
-    if (!loadFile(SINGLE_CHANNEL_IMAGE, &mSingleChannelImage)) {
-        FAIL() << "Load file " << SINGLE_CHANNEL_IMAGE << " failed";
-    }
-    mSingleChannelImage.width = SINGLE_CHANNEL_IMAGE_WIDTH;
-    mSingleChannelImage.height = SINGLE_CHANNEL_IMAGE_HEIGHT;
-}
-
-void JpegEncoderHelperTest::TearDown() {}
-
-TEST_F(JpegEncoderHelperTest, encodeAlignedImage) {
-    JpegEncoderHelper encoder;
-    EXPECT_TRUE(encoder.compressImage(mAlignedImage.buffer.get(),
-                                      mAlignedImage.buffer.get() +
-                                              mAlignedImage.width * mAlignedImage.height,
-                                      mAlignedImage.width, mAlignedImage.height,
-                                      mAlignedImage.width, mAlignedImage.width / 2, JPEG_QUALITY,
-                                      NULL, 0));
-    ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
-}
-
-TEST_F(JpegEncoderHelperTest, encodeUnalignedImage) {
-    JpegEncoderHelper encoder;
-    EXPECT_TRUE(encoder.compressImage(mUnalignedImage.buffer.get(),
-                                      mUnalignedImage.buffer.get() +
-                                              mUnalignedImage.width * mUnalignedImage.height,
-                                      mUnalignedImage.width, mUnalignedImage.height,
-                                      mUnalignedImage.width, mUnalignedImage.width / 2,
-                                      JPEG_QUALITY, NULL, 0));
-    ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
-}
-
-TEST_F(JpegEncoderHelperTest, encodeSingleChannelImage) {
-    JpegEncoderHelper encoder;
-    EXPECT_TRUE(encoder.compressImage(mSingleChannelImage.buffer.get(), nullptr,
-                                      mSingleChannelImage.width, mSingleChannelImage.height,
-                                      mSingleChannelImage.width, 0, JPEG_QUALITY, NULL, 0));
-    ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/tests/jpegr_test.cpp b/libs/ultrahdr/tests/jpegr_test.cpp
deleted file mode 100644
index 5fa758e..0000000
--- a/libs/ultrahdr/tests/jpegr_test.cpp
+++ /dev/null
@@ -1,2035 +0,0 @@
-/*
- * 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 <sys/time.h>
-#include <fstream>
-#include <iostream>
-
-#include <ultrahdr/gainmapmath.h>
-#include <ultrahdr/jpegr.h>
-#include <ultrahdr/jpegrutils.h>
-
-#include <gtest/gtest.h>
-#include <utils/Log.h>
-
-//#define DUMP_OUTPUT
-
-namespace android::ultrahdr {
-
-// resources used by unit tests
-const char* kYCbCrP010FileName = "/data/local/tmp/raw_p010_image.p010";
-const char* kYCbCr420FileName = "/data/local/tmp/raw_yuv420_image.yuv420";
-const char* kSdrJpgFileName = "/data/local/tmp/jpeg_image.jpg";
-const int kImageWidth = 1280;
-const int kImageHeight = 720;
-const int kQuality = 90;
-
-// Wrapper to describe the input type
-typedef enum {
-  YCbCr_p010 = 0,
-  YCbCr_420 = 1,
-} UhdrInputFormat;
-
-/**
- * Wrapper class for raw resource
- * Sample usage:
- *   UhdrUnCompressedStructWrapper rawImg(width, height, YCbCr_p010);
- *   rawImg.setImageColorGamut(colorGamut));
- *   rawImg.setImageStride(strideLuma, strideChroma); // optional
- *   rawImg.setChromaMode(false); // optional
- *   rawImg.allocateMemory();
- *   rawImg.loadRawResource(kYCbCrP010FileName);
- */
-class UhdrUnCompressedStructWrapper {
-public:
-  UhdrUnCompressedStructWrapper(uint32_t width, uint32_t height, UhdrInputFormat format);
-  ~UhdrUnCompressedStructWrapper() = default;
-
-  bool setChromaMode(bool isChromaContiguous);
-  bool setImageStride(int lumaStride, int chromaStride);
-  bool setImageColorGamut(ultrahdr_color_gamut colorGamut);
-  bool allocateMemory();
-  bool loadRawResource(const char* fileName);
-  jr_uncompressed_ptr getImageHandle();
-
-private:
-  std::unique_ptr<uint8_t[]> mLumaData;
-  std::unique_ptr<uint8_t[]> mChromaData;
-  jpegr_uncompressed_struct mImg;
-  UhdrInputFormat mFormat;
-  bool mIsChromaContiguous;
-};
-
-/**
- * Wrapper class for compressed resource
- * Sample usage:
- *   UhdrCompressedStructWrapper jpgImg(width, height);
- *   rawImg.allocateMemory();
- */
-class UhdrCompressedStructWrapper {
-public:
-  UhdrCompressedStructWrapper(uint32_t width, uint32_t height);
-  ~UhdrCompressedStructWrapper() = default;
-
-  bool allocateMemory();
-  jr_compressed_ptr getImageHandle();
-
-private:
-  std::unique_ptr<uint8_t[]> mData;
-  jpegr_compressed_struct mImg{};
-  uint32_t mWidth;
-  uint32_t mHeight;
-};
-
-UhdrUnCompressedStructWrapper::UhdrUnCompressedStructWrapper(uint32_t width, uint32_t height,
-                                                             UhdrInputFormat format) {
-  mImg.data = nullptr;
-  mImg.width = width;
-  mImg.height = height;
-  mImg.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-  mImg.chroma_data = nullptr;
-  mImg.luma_stride = 0;
-  mImg.chroma_stride = 0;
-  mFormat = format;
-  mIsChromaContiguous = true;
-}
-
-bool UhdrUnCompressedStructWrapper::setChromaMode(bool isChromaContiguous) {
-  if (mLumaData.get() != nullptr) {
-    std::cerr << "Object has sailed, no further modifications are allowed" << std::endl;
-    return false;
-  }
-  mIsChromaContiguous = isChromaContiguous;
-  return true;
-}
-
-bool UhdrUnCompressedStructWrapper::setImageStride(int lumaStride, int chromaStride) {
-  if (mLumaData.get() != nullptr) {
-    std::cerr << "Object has sailed, no further modifications are allowed" << std::endl;
-    return false;
-  }
-  if (lumaStride != 0) {
-    if (lumaStride < mImg.width) {
-      std::cerr << "Bad luma stride received" << std::endl;
-      return false;
-    }
-    mImg.luma_stride = lumaStride;
-  }
-  if (chromaStride != 0) {
-    if (mFormat == YCbCr_p010 && chromaStride < mImg.width) {
-      std::cerr << "Bad chroma stride received for format YCbCrP010" << std::endl;
-      return false;
-    }
-    if (mFormat == YCbCr_420 && chromaStride < (mImg.width >> 1)) {
-      std::cerr << "Bad chroma stride received for format YCbCr420" << std::endl;
-      return false;
-    }
-    mImg.chroma_stride = chromaStride;
-  }
-  return true;
-}
-
-bool UhdrUnCompressedStructWrapper::setImageColorGamut(ultrahdr_color_gamut colorGamut) {
-  if (mLumaData.get() != nullptr) {
-    std::cerr << "Object has sailed, no further modifications are allowed" << std::endl;
-    return false;
-  }
-  mImg.colorGamut = colorGamut;
-  return true;
-}
-
-bool UhdrUnCompressedStructWrapper::allocateMemory() {
-  if (mImg.width == 0 || (mImg.width % 2 != 0) || mImg.height == 0 || (mImg.height % 2 != 0) ||
-      (mFormat != YCbCr_p010 && mFormat != YCbCr_420)) {
-    std::cerr << "Object in bad state, mem alloc failed" << std::endl;
-    return false;
-  }
-  int lumaStride = mImg.luma_stride == 0 ? mImg.width : mImg.luma_stride;
-  int lumaSize = lumaStride * mImg.height * (mFormat == YCbCr_p010 ? 2 : 1);
-  int chromaSize = (mImg.height >> 1) * (mFormat == YCbCr_p010 ? 2 : 1);
-  if (mIsChromaContiguous) {
-    chromaSize *= lumaStride;
-  } else {
-    if (mImg.chroma_stride == 0) {
-      std::cerr << "Object in bad state, mem alloc failed" << std::endl;
-      return false;
-    }
-    if (mFormat == YCbCr_p010) {
-      chromaSize *= mImg.chroma_stride;
-    } else {
-      chromaSize *= (mImg.chroma_stride * 2);
-    }
-  }
-  if (mIsChromaContiguous) {
-    mLumaData = std::make_unique<uint8_t[]>(lumaSize + chromaSize);
-    mImg.data = mLumaData.get();
-    mImg.chroma_data = nullptr;
-  } else {
-    mLumaData = std::make_unique<uint8_t[]>(lumaSize);
-    mImg.data = mLumaData.get();
-    mChromaData = std::make_unique<uint8_t[]>(chromaSize);
-    mImg.chroma_data = mChromaData.get();
-  }
-  return true;
-}
-
-bool UhdrUnCompressedStructWrapper::loadRawResource(const char* fileName) {
-  if (!mImg.data) {
-    std::cerr << "memory is not allocated, read not possible" << std::endl;
-    return false;
-  }
-  std::ifstream ifd(fileName, std::ios::binary | std::ios::ate);
-  if (ifd.good()) {
-    int bpp = mFormat == YCbCr_p010 ? 2 : 1;
-    int size = ifd.tellg();
-    int length = mImg.width * mImg.height * bpp * 3 / 2; // 2x2 subsampling
-    if (size < length) {
-      std::cerr << "requested to read " << length << " bytes from file : " << fileName
-                << ", file contains only " << length << " bytes" << std::endl;
-      return false;
-    }
-    ifd.seekg(0, std::ios::beg);
-    int lumaStride = mImg.luma_stride == 0 ? mImg.width : mImg.luma_stride;
-    char* mem = static_cast<char*>(mImg.data);
-    for (int i = 0; i < mImg.height; i++) {
-      ifd.read(mem, mImg.width * bpp);
-      mem += lumaStride * bpp;
-    }
-    if (!mIsChromaContiguous) {
-      mem = static_cast<char*>(mImg.chroma_data);
-    }
-    int chromaStride;
-    if (mIsChromaContiguous) {
-      chromaStride = mFormat == YCbCr_p010 ? lumaStride : lumaStride / 2;
-    } else {
-      if (mFormat == YCbCr_p010) {
-        chromaStride = mImg.chroma_stride == 0 ? lumaStride : mImg.chroma_stride;
-      } else {
-        chromaStride = mImg.chroma_stride == 0 ? (lumaStride / 2) : mImg.chroma_stride;
-      }
-    }
-    if (mFormat == YCbCr_p010) {
-      for (int i = 0; i < mImg.height / 2; i++) {
-        ifd.read(mem, mImg.width * 2);
-        mem += chromaStride * 2;
-      }
-    } else {
-      for (int i = 0; i < mImg.height / 2; i++) {
-        ifd.read(mem, (mImg.width / 2));
-        mem += chromaStride;
-      }
-      for (int i = 0; i < mImg.height / 2; i++) {
-        ifd.read(mem, (mImg.width / 2));
-        mem += chromaStride;
-      }
-    }
-    return true;
-  }
-  std::cerr << "unable to open file : " << fileName << std::endl;
-  return false;
-}
-
-jr_uncompressed_ptr UhdrUnCompressedStructWrapper::getImageHandle() {
-  return &mImg;
-}
-
-UhdrCompressedStructWrapper::UhdrCompressedStructWrapper(uint32_t width, uint32_t height) {
-  mWidth = width;
-  mHeight = height;
-}
-
-bool UhdrCompressedStructWrapper::allocateMemory() {
-  if (mWidth == 0 || (mWidth % 2 != 0) || mHeight == 0 || (mHeight % 2 != 0)) {
-    std::cerr << "Object in bad state, mem alloc failed" << std::endl;
-    return false;
-  }
-  int maxLength = std::max(8 * 1024 /* min size 8kb */, (int)(mWidth * mHeight * 3 * 2));
-  mData = std::make_unique<uint8_t[]>(maxLength);
-  mImg.data = mData.get();
-  mImg.length = 0;
-  mImg.maxLength = maxLength;
-  return true;
-}
-
-jr_compressed_ptr UhdrCompressedStructWrapper::getImageHandle() {
-  return &mImg;
-}
-
-static bool writeFile(const char* filename, void*& result, int length) {
-  std::ofstream ofd(filename, std::ios::binary);
-  if (ofd.is_open()) {
-    ofd.write(static_cast<char*>(result), length);
-    return true;
-  }
-  std::cerr << "unable to write to file : " << filename << std::endl;
-  return false;
-}
-
-static bool readFile(const char* fileName, void*& result, int maxLength, int& length) {
-  std::ifstream ifd(fileName, std::ios::binary | std::ios::ate);
-  if (ifd.good()) {
-    length = ifd.tellg();
-    if (length > maxLength) {
-      std::cerr << "not enough space to read file" << std::endl;
-      return false;
-    }
-    ifd.seekg(0, std::ios::beg);
-    ifd.read(static_cast<char*>(result), length);
-    return true;
-  }
-  std::cerr << "unable to read file : " << fileName << std::endl;
-  return false;
-}
-
-void decodeJpegRImg(jr_compressed_ptr img, [[maybe_unused]] const char* outFileName) {
-  std::vector<uint8_t> iccData(0);
-  std::vector<uint8_t> exifData(0);
-  jpegr_info_struct info{0, 0, &iccData, &exifData};
-  JpegR jpegHdr;
-  ASSERT_EQ(OK, jpegHdr.getJPEGRInfo(img, &info));
-  ASSERT_EQ(kImageWidth, info.width);
-  ASSERT_EQ(kImageHeight, info.height);
-  size_t outSize = info.width * info.height * 8;
-  std::unique_ptr<uint8_t[]> data = std::make_unique<uint8_t[]>(outSize);
-  jpegr_uncompressed_struct destImage{};
-  destImage.data = data.get();
-  ASSERT_EQ(OK, jpegHdr.decodeJPEGR(img, &destImage));
-  ASSERT_EQ(kImageWidth, destImage.width);
-  ASSERT_EQ(kImageHeight, destImage.height);
-#ifdef DUMP_OUTPUT
-  if (!writeFile(outFileName, destImage.data, outSize)) {
-    std::cerr << "unable to write output file" << std::endl;
-  }
-#endif
-}
-
-// ============================================================================
-// Unit Tests
-// ============================================================================
-
-// Test Encode API-0 invalid arguments
-TEST(JpegRTest, EncodeAPI0WithInvalidArgs) {
-  JpegR uHdrLib;
-
-  UhdrCompressedStructWrapper jpgImg(16, 16);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-
-  // test quality factor and transfer function
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), -1, nullptr),
-              OK)
-            << "fail, API allows bad jpeg quality factor";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), 101, nullptr),
-              OK)
-            << "fail, API allows bad jpeg quality factor";
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(
-                                          ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(-10),
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-  }
-
-  // test dest
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr, kQuality,
-                                  nullptr),
-              OK)
-            << "fail, API allows nullptr dest";
-    UhdrCompressedStructWrapper jpgImg2(16, 16);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr dest";
-  }
-
-  // test p010 input
-  {
-    ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr p010 image";
-
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr p010 image";
-  }
-
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(
-            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1)));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-  }
-
-  {
-    const int kWidth = 32, kHeight = 32;
-    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    auto rawImgP010 = rawImg.getImageHandle();
-
-    rawImgP010->width = kWidth - 1;
-    rawImgP010->height = kHeight;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight - 1;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = 0;
-    rawImgP010->height = kHeight;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = 0;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad luma stride";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth + 64;
-    rawImgP010->chroma_data = rawImgP010->data;
-    rawImgP010->chroma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad chroma stride";
-  }
-}
-
-/* Test Encode API-1 invalid arguments */
-TEST(JpegRTest, EncodeAPI1WithInvalidArgs) {
-  JpegR uHdrLib;
-
-  UhdrCompressedStructWrapper jpgImg(16, 16);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-
-  // test quality factor and transfer function
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), -1, nullptr),
-              OK)
-            << "fail, API allows bad jpeg quality factor";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), 101, nullptr),
-              OK)
-            << "fail, API allows bad jpeg quality factor";
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(
-                                          ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(-10),
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-  }
-
-  // test dest
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr, kQuality,
-                                  nullptr),
-              OK)
-            << "fail, API allows nullptr dest";
-    UhdrCompressedStructWrapper jpgImg2(16, 16);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr dest";
-  }
-
-  // test p010 input
-  {
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr p010 image";
-
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr p010 image";
-  }
-
-  {
-    const int kWidth = 32, kHeight = 32;
-    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    auto rawImgP010 = rawImg.getImageHandle();
-    UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    auto rawImg420 = rawImg2.getImageHandle();
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut =
-            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-
-    rawImgP010->width = kWidth - 1;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight - 1;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = 0;
-    rawImgP010->height = kHeight;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = 0;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad luma stride";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth + 64;
-    rawImgP010->chroma_data = rawImgP010->data;
-    rawImgP010->chroma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad chroma stride";
-  }
-
-  // test 420 input
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr 420 image";
-
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr 420 image";
-  }
-  {
-    const int kWidth = 32, kHeight = 32;
-    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    auto rawImgP010 = rawImg.getImageHandle();
-    UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    auto rawImg420 = rawImg2.getImageHandle();
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad 420 color gamut";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->colorGamut =
-            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad 420 color gamut";
-
-    rawImg420->width = kWidth - 1;
-    rawImg420->height = kHeight;
-    rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image width for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight - 1;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image height for 420";
-
-    rawImg420->width = 0;
-    rawImg420->height = kHeight;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image width for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = 0;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image height for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->luma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad luma stride for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->luma_stride = 0;
-    rawImg420->chroma_data = rawImgP010->data;
-    rawImg420->chroma_stride = kWidth / 2 - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad chroma stride for 420";
-  }
-}
-
-/* Test Encode API-2 invalid arguments */
-TEST(JpegRTest, EncodeAPI2WithInvalidArgs) {
-  JpegR uHdrLib;
-
-  UhdrCompressedStructWrapper jpgImg(16, 16);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-
-  // test quality factor and transfer function
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(
-                                          ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(-10),
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-  }
-
-  // test dest
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr),
-              OK)
-            << "fail, API allows nullptr dest";
-    UhdrCompressedStructWrapper jpgImg2(16, 16);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr dest";
-  }
-
-  // test compressed image
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), nullptr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr for compressed image";
-    UhdrCompressedStructWrapper jpgImg2(16, 16);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr for compressed image";
-  }
-
-  // test p010 input
-  {
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, rawImg2.getImageHandle(), jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr p010 image";
-
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr p010 image";
-  }
-
-  {
-    const int kWidth = 32, kHeight = 32;
-    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    auto rawImgP010 = rawImg.getImageHandle();
-    UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    auto rawImg420 = rawImg2.getImageHandle();
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut =
-            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-
-    rawImgP010->width = kWidth - 1;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight - 1;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = 0;
-    rawImgP010->height = kHeight;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = 0;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad luma stride";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth + 64;
-    rawImgP010->chroma_data = rawImgP010->data;
-    rawImgP010->chroma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad chroma stride";
-  }
-
-  // test 420 input
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr 420 image";
-
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr 420 image";
-  }
-  {
-    const int kWidth = 32, kHeight = 32;
-    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    auto rawImgP010 = rawImg.getImageHandle();
-    UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    auto rawImg420 = rawImg2.getImageHandle();
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad 420 color gamut";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->colorGamut =
-            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad 420 color gamut";
-
-    rawImg420->width = kWidth - 1;
-    rawImg420->height = kHeight;
-    rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image width for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight - 1;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image height for 420";
-
-    rawImg420->width = 0;
-    rawImg420->height = kHeight;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image width for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = 0;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image height for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->luma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad luma stride for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->luma_stride = 0;
-    rawImg420->chroma_data = rawImgP010->data;
-    rawImg420->chroma_stride = kWidth / 2 - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad chroma stride for 420";
-  }
-}
-
-/* Test Encode API-3 invalid arguments */
-TEST(JpegRTest, EncodeAPI3WithInvalidArgs) {
-  JpegR uHdrLib;
-
-  UhdrCompressedStructWrapper jpgImg(16, 16);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-
-  // test quality factor and transfer function
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(
-                                          ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(-10),
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-  }
-
-  // test dest
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr),
-              OK)
-            << "fail, API allows nullptr dest";
-    UhdrCompressedStructWrapper jpgImg2(16, 16);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr dest";
-  }
-
-  // test compressed image
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr for compressed image";
-    UhdrCompressedStructWrapper jpgImg2(16, 16);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr for compressed image";
-  }
-
-  // test p010 input
-  {
-    ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr p010 image";
-
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr p010 image";
-  }
-
-  {
-    const int kWidth = 32, kHeight = 32;
-    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    auto rawImgP010 = rawImg.getImageHandle();
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut =
-            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-
-    rawImgP010->width = kWidth - 1;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight - 1;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = 0;
-    rawImgP010->height = kHeight;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = 0;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad luma stride";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth + 64;
-    rawImgP010->chroma_data = rawImgP010->data;
-    rawImgP010->chroma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad chroma stride";
-  }
-}
-
-/* Test Encode API-4 invalid arguments */
-TEST(JpegRTest, EncodeAPI4WithInvalidArgs) {
-  UhdrCompressedStructWrapper jpgImg(16, 16);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-  UhdrCompressedStructWrapper jpgImg2(16, 16);
-  JpegR uHdrLib;
-
-  // test dest
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), nullptr, nullptr),
-            OK)
-          << "fail, API allows nullptr dest";
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), nullptr,
-                                jpgImg2.getImageHandle()),
-            OK)
-          << "fail, API allows nullptr dest";
-
-  // test primary image
-  ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, jpgImg.getImageHandle(), nullptr, jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows nullptr primary image";
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg2.getImageHandle(), jpgImg.getImageHandle(), nullptr,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows nullptr primary image";
-
-  // test gain map
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), nullptr, nullptr, jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows nullptr gain map image";
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg2.getImageHandle(), nullptr,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows nullptr gain map image";
-
-  // test metadata
-  ultrahdr_metadata_struct good_metadata;
-  good_metadata.version = "1.0";
-  good_metadata.minContentBoost = 1.0f;
-  good_metadata.maxContentBoost = 2.0f;
-  good_metadata.gamma = 1.0f;
-  good_metadata.offsetSdr = 0.0f;
-  good_metadata.offsetHdr = 0.0f;
-  good_metadata.hdrCapacityMin = 1.0f;
-  good_metadata.hdrCapacityMax = 2.0f;
-
-  ultrahdr_metadata_struct metadata = good_metadata;
-  metadata.version = "1.1";
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows bad metadata version";
-
-  metadata = good_metadata;
-  metadata.minContentBoost = 3.0f;
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows bad metadata content boost";
-
-  metadata = good_metadata;
-  metadata.gamma = -0.1f;
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows bad metadata gamma";
-
-  metadata = good_metadata;
-  metadata.offsetSdr = -0.1f;
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows bad metadata offset sdr";
-
-  metadata = good_metadata;
-  metadata.offsetHdr = -0.1f;
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows bad metadata offset hdr";
-
-  metadata = good_metadata;
-  metadata.hdrCapacityMax = 0.5f;
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows bad metadata hdr capacity max";
-
-  metadata = good_metadata;
-  metadata.hdrCapacityMin = 0.5f;
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows bad metadata hdr capacity min";
-}
-
-/* Test Decode API invalid arguments */
-TEST(JpegRTest, DecodeAPIWithInvalidArgs) {
-  JpegR uHdrLib;
-
-  UhdrCompressedStructWrapper jpgImg(16, 16);
-  jpegr_uncompressed_struct destImage{};
-  size_t outSize = 16 * 16 * 8;
-  std::unique_ptr<uint8_t[]> data = std::make_unique<uint8_t[]>(outSize);
-  destImage.data = data.get();
-
-  // test jpegr image
-  ASSERT_NE(uHdrLib.decodeJPEGR(nullptr, &destImage), OK)
-          << "fail, API allows nullptr for jpegr img";
-  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage), OK)
-          << "fail, API allows nullptr for jpegr img";
-  ASSERT_TRUE(jpgImg.allocateMemory());
-
-  // test dest image
-  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), nullptr), OK)
-          << "fail, API allows nullptr for dest";
-  destImage.data = nullptr;
-  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage), OK)
-          << "fail, API allows nullptr for dest";
-  destImage.data = data.get();
-
-  // test max display boost
-  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, 0.5), OK)
-          << "fail, API allows invalid max display boost";
-
-  // test output format
-  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, FLT_MAX, nullptr,
-                                static_cast<ultrahdr_output_format>(-1)),
-            OK)
-          << "fail, API allows invalid output format";
-  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, FLT_MAX, nullptr,
-                                static_cast<ultrahdr_output_format>(ULTRAHDR_OUTPUT_MAX + 1)),
-            OK)
-          << "fail, API allows invalid output format";
-}
-
-TEST(JpegRTest, writeXmpThenRead) {
-  ultrahdr_metadata_struct metadata_expected;
-  metadata_expected.version = "1.0";
-  metadata_expected.maxContentBoost = 1.25f;
-  metadata_expected.minContentBoost = 0.75f;
-  metadata_expected.gamma = 1.0f;
-  metadata_expected.offsetSdr = 0.0f;
-  metadata_expected.offsetHdr = 0.0f;
-  metadata_expected.hdrCapacityMin = 1.0f;
-  metadata_expected.hdrCapacityMax = metadata_expected.maxContentBoost;
-  const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
-  const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
-
-  std::string xmp = generateXmpForSecondaryImage(metadata_expected);
-
-  std::vector<uint8_t> xmpData;
-  xmpData.reserve(nameSpaceLength + xmp.size());
-  xmpData.insert(xmpData.end(), reinterpret_cast<const uint8_t*>(nameSpace.c_str()),
-                 reinterpret_cast<const uint8_t*>(nameSpace.c_str()) + nameSpaceLength);
-  xmpData.insert(xmpData.end(), reinterpret_cast<const uint8_t*>(xmp.c_str()),
-                 reinterpret_cast<const uint8_t*>(xmp.c_str()) + xmp.size());
-
-  ultrahdr_metadata_struct metadata_read;
-  EXPECT_TRUE(getMetadataFromXMP(xmpData.data(), xmpData.size(), &metadata_read));
-  EXPECT_FLOAT_EQ(metadata_expected.maxContentBoost, metadata_read.maxContentBoost);
-  EXPECT_FLOAT_EQ(metadata_expected.minContentBoost, metadata_read.minContentBoost);
-  EXPECT_FLOAT_EQ(metadata_expected.gamma, metadata_read.gamma);
-  EXPECT_FLOAT_EQ(metadata_expected.offsetSdr, metadata_read.offsetSdr);
-  EXPECT_FLOAT_EQ(metadata_expected.offsetHdr, metadata_read.offsetHdr);
-  EXPECT_FLOAT_EQ(metadata_expected.hdrCapacityMin, metadata_read.hdrCapacityMin);
-  EXPECT_FLOAT_EQ(metadata_expected.hdrCapacityMax, metadata_read.hdrCapacityMax);
-}
-
-class JpegRAPIEncodeAndDecodeTest
-      : public ::testing::TestWithParam<std::tuple<ultrahdr_color_gamut, ultrahdr_color_gamut>> {
-public:
-  JpegRAPIEncodeAndDecodeTest()
-        : mP010ColorGamut(std::get<0>(GetParam())), mYuv420ColorGamut(std::get<1>(GetParam())){};
-
-  const ultrahdr_color_gamut mP010ColorGamut;
-  const ultrahdr_color_gamut mYuv420ColorGamut;
-};
-
-/* Test Encode API-0 and Decode */
-TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI0AndDecodeTest) {
-  // reference encode
-  UhdrUnCompressedStructWrapper rawImg(kImageWidth, kImageHeight, YCbCr_p010);
-  ASSERT_TRUE(rawImg.setImageColorGamut(mP010ColorGamut));
-  ASSERT_TRUE(rawImg.allocateMemory());
-  ASSERT_TRUE(rawImg.loadRawResource(kYCbCrP010FileName));
-  UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-  JpegR uHdrLib;
-  ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                jpgImg.getImageHandle(), kQuality, nullptr),
-            OK);
-  // encode with luma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2.setImageStride(kImageWidth + 18, 0));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2.setImageStride(kImageWidth + 18, kImageWidth + 28));
-    ASSERT_TRUE(rawImg2.setChromaMode(false));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2.setImageStride(0, kImageWidth + 34));
-    ASSERT_TRUE(rawImg2.setChromaMode(false));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set but no chroma ptr
-  {
-    UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2.setImageStride(kImageWidth, kImageWidth + 38));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-
-  auto jpg1 = jpgImg.getImageHandle();
-#ifdef DUMP_OUTPUT
-  if (!writeFile("encode_api0_output.jpeg", jpg1->data, jpg1->length)) {
-    std::cerr << "unable to write output file" << std::endl;
-  }
-#endif
-
-  ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api0_output.rgb"));
-}
-
-/* Test Encode API-1 and Decode */
-TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI1AndDecodeTest) {
-  UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
-  ASSERT_TRUE(rawImgP010.setImageColorGamut(mP010ColorGamut));
-  ASSERT_TRUE(rawImgP010.allocateMemory());
-  ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
-  UhdrUnCompressedStructWrapper rawImg420(kImageWidth, kImageHeight, YCbCr_420);
-  ASSERT_TRUE(rawImg420.setImageColorGamut(mYuv420ColorGamut));
-  ASSERT_TRUE(rawImg420.allocateMemory());
-  ASSERT_TRUE(rawImg420.loadRawResource(kYCbCr420FileName));
-  UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-  JpegR uHdrLib;
-  ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg420.getImageHandle(),
-                                ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                jpgImg.getImageHandle(), kQuality, nullptr),
-            OK);
-  // encode with luma stride set p010
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, 0));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set p010
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, kImageWidth + 256));
-    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with chroma stride set p010
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(0, kImageWidth + 64));
-    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set but no chroma ptr p010
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 64, kImageWidth + 256));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma stride set 420
-  {
-    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
-    ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 14, 0));
-    ASSERT_TRUE(rawImg2420.allocateMemory());
-    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set 420
-  {
-    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
-    ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 46, kImageWidth / 2 + 34));
-    ASSERT_TRUE(rawImg2420.setChromaMode(false));
-    ASSERT_TRUE(rawImg2420.allocateMemory());
-    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with chroma stride set 420
-  {
-    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
-    ASSERT_TRUE(rawImg2420.setImageStride(0, kImageWidth / 2 + 38));
-    ASSERT_TRUE(rawImg2420.setChromaMode(false));
-    ASSERT_TRUE(rawImg2420.allocateMemory());
-    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set but no chroma ptr 420
-  {
-    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
-    ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 26, kImageWidth / 2 + 44));
-    ASSERT_TRUE(rawImg2420.allocateMemory());
-    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-
-  auto jpg1 = jpgImg.getImageHandle();
-
-#ifdef DUMP_OUTPUT
-  if (!writeFile("encode_api1_output.jpeg", jpg1->data, jpg1->length)) {
-    std::cerr << "unable to write output file" << std::endl;
-  }
-#endif
-
-  ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api1_output.rgb"));
-}
-
-/* Test Encode API-2 and Decode */
-TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI2AndDecodeTest) {
-  UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
-  ASSERT_TRUE(rawImgP010.setImageColorGamut(mP010ColorGamut));
-  ASSERT_TRUE(rawImgP010.allocateMemory());
-  ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
-  UhdrUnCompressedStructWrapper rawImg420(kImageWidth, kImageHeight, YCbCr_420);
-  ASSERT_TRUE(rawImg420.setImageColorGamut(mYuv420ColorGamut));
-  ASSERT_TRUE(rawImg420.allocateMemory());
-  ASSERT_TRUE(rawImg420.loadRawResource(kYCbCr420FileName));
-  UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-  UhdrCompressedStructWrapper jpgSdr(kImageWidth, kImageHeight);
-  ASSERT_TRUE(jpgSdr.allocateMemory());
-  auto sdr = jpgSdr.getImageHandle();
-  ASSERT_TRUE(readFile(kSdrJpgFileName, sdr->data, sdr->maxLength, sdr->length));
-  JpegR uHdrLib;
-  ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg420.getImageHandle(), sdr,
-                                ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                jpgImg.getImageHandle()),
-            OK);
-  // encode with luma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, 0));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, kImageWidth + 256));
-    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(0, kImageWidth + 64));
-    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
-    ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 128, 0));
-    ASSERT_TRUE(rawImg2420.allocateMemory());
-    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
-    ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 128, kImageWidth + 256));
-    ASSERT_TRUE(rawImg2420.setChromaMode(false));
-    ASSERT_TRUE(rawImg2420.allocateMemory());
-    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
-    ASSERT_TRUE(rawImg2420.setImageStride(0, kImageWidth + 64));
-    ASSERT_TRUE(rawImg2420.setChromaMode(false));
-    ASSERT_TRUE(rawImg2420.allocateMemory());
-    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-
-  auto jpg1 = jpgImg.getImageHandle();
-
-#ifdef DUMP_OUTPUT
-  if (!writeFile("encode_api2_output.jpeg", jpg1->data, jpg1->length)) {
-    std::cerr << "unable to write output file" << std::endl;
-  }
-#endif
-
-  ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api2_output.rgb"));
-}
-
-/* Test Encode API-3 and Decode */
-TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI3AndDecodeTest) {
-  UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
-  ASSERT_TRUE(rawImgP010.setImageColorGamut(mP010ColorGamut));
-  ASSERT_TRUE(rawImgP010.allocateMemory());
-  ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
-  UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-  UhdrCompressedStructWrapper jpgSdr(kImageWidth, kImageHeight);
-  ASSERT_TRUE(jpgSdr.allocateMemory());
-  auto sdr = jpgSdr.getImageHandle();
-  ASSERT_TRUE(readFile(kSdrJpgFileName, sdr->data, sdr->maxLength, sdr->length));
-  JpegR uHdrLib;
-  ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), sdr,
-                                ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                jpgImg.getImageHandle()),
-            OK);
-  // encode with luma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, 0));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, kImageWidth + 256));
-    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(0, kImageWidth + 64));
-    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set and no chroma ptr
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 32, kImageWidth + 256));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-
-  auto jpg1 = jpgImg.getImageHandle();
-
-#ifdef DUMP_OUTPUT
-  if (!writeFile("encode_api3_output.jpeg", jpg1->data, jpg1->length)) {
-    std::cerr << "unable to write output file" << std::endl;
-  }
-#endif
-
-  ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api3_output.rgb"));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-        JpegRAPIParameterizedTests, JpegRAPIEncodeAndDecodeTest,
-        ::testing::Combine(::testing::Values(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3,
-                                             ULTRAHDR_COLORGAMUT_BT2100),
-                           ::testing::Values(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3,
-                                             ULTRAHDR_COLORGAMUT_BT2100)));
-
-// ============================================================================
-// Profiling
-// ============================================================================
-
-class Profiler {
-public:
-  void timerStart() { gettimeofday(&mStartingTime, nullptr); }
-
-  void timerStop() { gettimeofday(&mEndingTime, nullptr); }
-
-  int64_t elapsedTime() {
-    struct timeval elapsedMicroseconds;
-    elapsedMicroseconds.tv_sec = mEndingTime.tv_sec - mStartingTime.tv_sec;
-    elapsedMicroseconds.tv_usec = mEndingTime.tv_usec - mStartingTime.tv_usec;
-    return elapsedMicroseconds.tv_sec * 1000000 + elapsedMicroseconds.tv_usec;
-  }
-
-private:
-  struct timeval mStartingTime;
-  struct timeval mEndingTime;
-};
-
-class JpegRBenchmark : public JpegR {
-public:
-  void BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr p010Image,
-                                ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr map);
-  void BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map,
-                             ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr dest);
-
-private:
-  const int kProfileCount = 10;
-};
-
-void JpegRBenchmark::BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image,
-                                              jr_uncompressed_ptr p010Image,
-                                              ultrahdr_metadata_ptr metadata,
-                                              jr_uncompressed_ptr map) {
-  ASSERT_EQ(yuv420Image->width, p010Image->width);
-  ASSERT_EQ(yuv420Image->height, p010Image->height);
-  Profiler profileGenerateMap;
-  profileGenerateMap.timerStart();
-  for (auto i = 0; i < kProfileCount; i++) {
-    ASSERT_EQ(OK,
-              generateGainMap(yuv420Image, p010Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                              metadata, map));
-    if (i != kProfileCount - 1) delete[] static_cast<uint8_t*>(map->data);
-  }
-  profileGenerateMap.timerStop();
-  ALOGE("Generate Gain Map:- Res = %i x %i, time = %f ms", yuv420Image->width, yuv420Image->height,
-        profileGenerateMap.elapsedTime() / (kProfileCount * 1000.f));
-}
-
-void JpegRBenchmark::BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map,
-                                           ultrahdr_metadata_ptr metadata,
-                                           jr_uncompressed_ptr dest) {
-  Profiler profileRecMap;
-  profileRecMap.timerStart();
-  for (auto i = 0; i < kProfileCount; i++) {
-    ASSERT_EQ(OK,
-              applyGainMap(yuv420Image, map, metadata, ULTRAHDR_OUTPUT_HDR_HLG,
-                           metadata->maxContentBoost /* displayBoost */, dest));
-  }
-  profileRecMap.timerStop();
-  ALOGE("Apply Gain Map:- Res = %i x %i, time = %f ms", yuv420Image->width, yuv420Image->height,
-        profileRecMap.elapsedTime() / (kProfileCount * 1000.f));
-}
-
-TEST(JpegRTest, ProfileGainMapFuncs) {
-  UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
-  ASSERT_TRUE(rawImgP010.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-  ASSERT_TRUE(rawImgP010.allocateMemory());
-  ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
-  UhdrUnCompressedStructWrapper rawImg420(kImageWidth, kImageHeight, YCbCr_420);
-  ASSERT_TRUE(rawImg420.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-  ASSERT_TRUE(rawImg420.allocateMemory());
-  ASSERT_TRUE(rawImg420.loadRawResource(kYCbCr420FileName));
-  ultrahdr_metadata_struct metadata = {.version = "1.0"};
-  jpegr_uncompressed_struct map = {.data = NULL,
-                                   .width = 0,
-                                   .height = 0,
-                                   .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-  {
-    auto rawImg = rawImgP010.getImageHandle();
-    if (rawImg->luma_stride == 0) rawImg->luma_stride = rawImg->width;
-    if (!rawImg->chroma_data) {
-      uint16_t* data = reinterpret_cast<uint16_t*>(rawImg->data);
-      rawImg->chroma_data = data + rawImg->luma_stride * rawImg->height;
-      rawImg->chroma_stride = rawImg->luma_stride;
-    }
-  }
-  {
-    auto rawImg = rawImg420.getImageHandle();
-    if (rawImg->luma_stride == 0) rawImg->luma_stride = rawImg->width;
-    if (!rawImg->chroma_data) {
-      uint8_t* data = reinterpret_cast<uint8_t*>(rawImg->data);
-      rawImg->chroma_data = data + rawImg->luma_stride * rawImg->height;
-      rawImg->chroma_stride = rawImg->luma_stride / 2;
-    }
-  }
-
-  JpegRBenchmark benchmark;
-  ASSERT_NO_FATAL_FAILURE(benchmark.BenchmarkGenerateGainMap(rawImg420.getImageHandle(),
-                                                             rawImgP010.getImageHandle(), &metadata,
-                                                             &map));
-
-  const int dstSize = kImageWidth * kImageWidth * 4;
-  auto bufferDst = std::make_unique<uint8_t[]>(dstSize);
-  jpegr_uncompressed_struct dest = {.data = bufferDst.get(),
-                                    .width = 0,
-                                    .height = 0,
-                                    .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-
-  ASSERT_NO_FATAL_FAILURE(
-          benchmark.BenchmarkApplyGainMap(rawImg420.getImageHandle(), &map, &metadata, &dest));
-}
-
-} // namespace android::ultrahdr
diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp
index af0bcff..ec7b190 100644
--- a/opengl/libs/EGL/Loader.cpp
+++ b/opengl/libs/EGL/Loader.cpp
@@ -184,6 +184,14 @@
         }
     }
 
+    // Return true if app requests to use ANGLE, but ANGLE is not loaded.
+    // Difference with the case above is on devices that don't have an ANGLE apk installed,
+    // ANGLE namespace is not set. In that case if ANGLE in system partition is not loaded,
+    // we should unload the system driver first, and then load ANGLE from system partition.
+    if (!cnx->angleLoaded && android::GraphicsEnv::getInstance().shouldUseAngle()) {
+        return true;
+    }
+
     // Return true if native GLES drivers should be used and ANGLE is already loaded.
     if (android::GraphicsEnv::getInstance().shouldUseNativeDriver() && cnx->angleLoaded) {
         return true;
diff --git a/services/gpuservice/GpuService.cpp b/services/gpuservice/GpuService.cpp
index 79f22c1..a481c62 100644
--- a/services/gpuservice/GpuService.cpp
+++ b/services/gpuservice/GpuService.cpp
@@ -72,6 +72,9 @@
 };
 
 GpuService::~GpuService() {
+    mGpuMem->stop();
+    mGpuWork->stop();
+
     mGpuWorkAsyncInitThread->join();
     mGpuMemAsyncInitThread->join();
 }
diff --git a/services/gpuservice/gpumem/GpuMem.cpp b/services/gpuservice/gpumem/GpuMem.cpp
index 141fe02..d0783df 100644
--- a/services/gpuservice/gpumem/GpuMem.cpp
+++ b/services/gpuservice/gpumem/GpuMem.cpp
@@ -61,6 +61,7 @@
             return;
         }
         // Retry until GPU driver loaded or timeout.
+        if (mStop.load()) return;
         sleep(1);
     }
 
diff --git a/services/gpuservice/gpumem/include/gpumem/GpuMem.h b/services/gpuservice/gpumem/include/gpumem/GpuMem.h
index 9aa74d6..16b201f 100644
--- a/services/gpuservice/gpumem/include/gpumem/GpuMem.h
+++ b/services/gpuservice/gpumem/include/gpumem/GpuMem.h
@@ -34,6 +34,7 @@
     // dumpsys interface
     void dump(const Vector<String16>& args, std::string* result);
     bool isInitialized() { return mInitialized.load(); }
+    void stop() { mStop.store(true); }
 
     // Traverse the gpu memory total map to feed the callback function.
     void traverseGpuMemTotals(const std::function<void(int64_t ts, uint32_t gpuId, uint32_t pid,
@@ -48,6 +49,10 @@
 
     // indicate whether ebpf has been initialized
     std::atomic<bool> mInitialized = false;
+
+    // whether initialization should be stopped
+    std::atomic<bool> mStop = false;
+
     // bpf map for GPU memory total data
     android::bpf::BpfMapRO<uint64_t, uint64_t> mGpuMemTotalMap;
 
diff --git a/services/gpuservice/gpuwork/GpuWork.cpp b/services/gpuservice/gpuwork/GpuWork.cpp
index fd70323..1a744ab 100644
--- a/services/gpuservice/gpuwork/GpuWork.cpp
+++ b/services/gpuservice/gpuwork/GpuWork.cpp
@@ -243,6 +243,7 @@
             return false;
         }
         // Retry until GPU driver loaded or timeout.
+        if (mStop.load()) return false;
         sleep(1);
         errno = 0;
     }
diff --git a/services/gpuservice/gpuwork/include/gpuwork/GpuWork.h b/services/gpuservice/gpuwork/include/gpuwork/GpuWork.h
index cece999..e70da54 100644
--- a/services/gpuservice/gpuwork/include/gpuwork/GpuWork.h
+++ b/services/gpuservice/gpuwork/include/gpuwork/GpuWork.h
@@ -40,6 +40,7 @@
     ~GpuWork();
 
     void initialize();
+    void stop() { mStop.store(true); }
 
     // Dumps the GPU work information.
     void dump(const Vector<String16>& args, std::string* result);
@@ -47,7 +48,7 @@
 private:
     // Attaches tracepoint |tracepoint_group|/|tracepoint_name| to BPF program at path
     // |program_path|. The tracepoint is also enabled.
-    static bool attachTracepoint(const char* program_path, const char* tracepoint_group,
+    bool attachTracepoint(const char* program_path, const char* tracepoint_group,
                                  const char* tracepoint_name);
 
     // Native atom puller callback registered in statsd.
@@ -80,6 +81,9 @@
     // Indicates whether our eBPF components have been initialized.
     std::atomic<bool> mInitialized = false;
 
+    // Indicates whether eBPF initialization should be stopped.
+    std::atomic<bool> mStop = false;
+
     // A thread that periodically checks whether |mGpuWorkMap| is nearly full
     // and, if so, clears it.
     std::thread mMapClearerThread;
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index d244b1a..a03055f 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -107,6 +107,7 @@
         "libutils",
         "libstatspull",
         "libstatssocket",
+        "packagemanager_aidl-cpp",
         "server_configurable_flags",
     ],
     static_libs: [
@@ -270,5 +271,6 @@
         "FrameworksServicesTests",
         "CtsSecurityTestCases",
         "CtsSecurityBulletinHostTestCases",
+        "monkey_test",
     ],
 }
diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp
index 6ccd9e7..417c1f3 100644
--- a/services/inputflinger/InputCommonConverter.cpp
+++ b/services/inputflinger/InputCommonConverter.cpp
@@ -20,6 +20,9 @@
 
 namespace android {
 
+const static ui::Transform kIdentityTransform;
+const static std::array<uint8_t, 32> kInvalidHmac{};
+
 static common::Source getSource(uint32_t source) {
     static_assert(static_cast<common::Source>(AINPUT_SOURCE_UNKNOWN) == common::Source::UNKNOWN,
                   "SOURCE_UNKNOWN mismatch");
@@ -337,4 +340,31 @@
     return event;
 }
 
+MotionEvent toMotionEvent(const NotifyMotionArgs& args, const ui::Transform* transform,
+                          const ui::Transform* rawTransform, const std::array<uint8_t, 32>* hmac) {
+    if (transform == nullptr) transform = &kIdentityTransform;
+    if (rawTransform == nullptr) rawTransform = &kIdentityTransform;
+    if (hmac == nullptr) hmac = &kInvalidHmac;
+
+    MotionEvent event;
+    event.initialize(args.id, args.deviceId, args.source, args.displayId, *hmac, args.action,
+                     args.actionButton, args.flags, args.edgeFlags, args.metaState,
+                     args.buttonState, args.classification, *transform, args.xPrecision,
+                     args.yPrecision, args.xCursorPosition, args.yCursorPosition, *rawTransform,
+                     args.downTime, args.eventTime, args.getPointerCount(),
+                     args.pointerProperties.data(), args.pointerCoords.data());
+    return event;
+}
+
+KeyEvent toKeyEvent(const NotifyKeyArgs& args, int32_t repeatCount,
+                    const std::array<uint8_t, 32>* hmac) {
+    if (hmac == nullptr) hmac = &kInvalidHmac;
+
+    KeyEvent event;
+    event.initialize(args.id, args.deviceId, args.source, args.displayId, *hmac, args.action,
+                     args.flags, args.keyCode, args.scanCode, args.metaState, repeatCount,
+                     args.downTime, args.eventTime);
+    return event;
+}
+
 } // namespace android
diff --git a/services/inputflinger/InputCommonConverter.h b/services/inputflinger/InputCommonConverter.h
index 4d3b768..0d4cbb0 100644
--- a/services/inputflinger/InputCommonConverter.h
+++ b/services/inputflinger/InputCommonConverter.h
@@ -16,16 +16,25 @@
 
 #pragma once
 
+#include "InputListener.h"
+
 #include <aidl/android/hardware/input/common/Axis.h>
 #include <aidl/android/hardware/input/common/MotionEvent.h>
-#include "InputListener.h"
+#include <input/Input.h>
 
 namespace android {
 
-/**
- * Convert from framework's NotifyMotionArgs to hidl's common::MotionEvent
- */
+/** Convert from framework's NotifyMotionArgs to hidl's common::MotionEvent. */
 ::aidl::android::hardware::input::common::MotionEvent notifyMotionArgsToHalMotionEvent(
         const NotifyMotionArgs& args);
 
+/** Convert from NotifyMotionArgs to MotionEvent. */
+MotionEvent toMotionEvent(const NotifyMotionArgs&, const ui::Transform* transform = nullptr,
+                          const ui::Transform* rawTransform = nullptr,
+                          const std::array<uint8_t, 32>* hmac = nullptr);
+
+/** Convert from NotifyKeyArgs to KeyEvent. */
+KeyEvent toKeyEvent(const NotifyKeyArgs&, int32_t repeatCount = 0,
+                    const std::array<uint8_t, 32>* hmac = nullptr);
+
 } // namespace android
diff --git a/services/inputflinger/InputFilterCallbacks.cpp b/services/inputflinger/InputFilterCallbacks.cpp
index 6c31442..a9bdbec 100644
--- a/services/inputflinger/InputFilterCallbacks.cpp
+++ b/services/inputflinger/InputFilterCallbacks.cpp
@@ -19,9 +19,10 @@
 #include "InputFilterCallbacks.h"
 #include <aidl/com/android/server/inputflinger/BnInputThread.h>
 #include <android/binder_auto_utils.h>
+#include <utils/Looper.h>
 #include <utils/StrongPointer.h>
-#include <utils/Thread.h>
 #include <functional>
+#include "InputThread.h"
 
 namespace android {
 
@@ -38,36 +39,37 @@
 
 using namespace aidl::com::android::server::inputflinger;
 
-class InputFilterThreadImpl : public Thread {
-public:
-    explicit InputFilterThreadImpl(std::function<void()> loop)
-          : Thread(/*canCallJava=*/true), mThreadLoop(loop) {}
-
-    ~InputFilterThreadImpl() {}
-
-private:
-    std::function<void()> mThreadLoop;
-
-    bool threadLoop() override {
-        mThreadLoop();
-        return true;
-    }
-};
-
 class InputFilterThread : public BnInputThread {
 public:
     InputFilterThread(std::shared_ptr<IInputThreadCallback> callback) : mCallback(callback) {
-        mThread = sp<InputFilterThreadImpl>::make([this]() { loopOnce(); });
-        mThread->run("InputFilterThread", ANDROID_PRIORITY_URGENT_DISPLAY);
+        mLooper = sp<Looper>::make(/*allowNonCallbacks=*/false);
+        mThread = std::make_unique<InputThread>(
+                "InputFilter", [this]() { loopOnce(); }, [this]() { mLooper->wake(); });
     }
 
     ndk::ScopedAStatus finish() override {
-        mThread->requestExit();
+        if (mThread && mThread->isCallingThread()) {
+            ALOGE("InputFilterThread cannot be stopped on itself!");
+            return ndk::ScopedAStatus::fromStatus(INVALID_OPERATION);
+        }
+        mThread.reset();
+        return ndk::ScopedAStatus::ok();
+    }
+
+    ndk::ScopedAStatus sleepUntil(nsecs_t when) override {
+        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+        mLooper->pollOnce(toMillisecondTimeoutDelay(now, when));
+        return ndk::ScopedAStatus::ok();
+    }
+
+    ndk::ScopedAStatus wake() override {
+        mLooper->wake();
         return ndk::ScopedAStatus::ok();
     }
 
 private:
-    sp<Thread> mThread;
+    sp<Looper> mLooper;
+    std::unique_ptr<InputThread> mThread;
     std::shared_ptr<IInputThreadCallback> mCallback;
 
     void loopOnce() { LOG_ALWAYS_FATAL_IF(!mCallback->loopOnce().isOk()); }
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 1aa1077..4dc2737 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -147,6 +147,7 @@
                    << args.dump();
     }
 
+    mMouseDevices.emplace(args.deviceId);
     auto [displayId, pc] = ensureMouseControllerLocked(args.displayId);
     NotifyMotionArgs newArgs(args);
     newArgs.displayId = displayId;
@@ -178,6 +179,7 @@
 }
 
 NotifyMotionArgs PointerChoreographer::processTouchpadEventLocked(const NotifyMotionArgs& args) {
+    mMouseDevices.emplace(args.deviceId);
     auto [displayId, pc] = ensureMouseControllerLocked(args.displayId);
 
     NotifyMotionArgs newArgs(args);
@@ -405,8 +407,10 @@
     const int32_t displayId = getTargetMouseDisplayLocked(associatedDisplayId);
 
     auto it = mMousePointersByDisplay.find(displayId);
-    LOG_ALWAYS_FATAL_IF(it == mMousePointersByDisplay.end(),
-                        "There is no mouse controller created for display %d", displayId);
+    if (it == mMousePointersByDisplay.end()) {
+        it = mMousePointersByDisplay.emplace(displayId, getMouseControllerConstructor(displayId))
+                     .first;
+    }
 
     return {displayId, *it->second};
 }
@@ -430,8 +434,15 @@
     // Mark the displayIds or deviceIds of PointerControllers currently needed, and create
     // new PointerControllers if necessary.
     for (const auto& info : mInputDeviceInfos) {
+        if (!info.isEnabled()) {
+            // If device is disabled, we should not keep it, and should not show pointer for
+            // disabled mouse device.
+            continue;
+        }
         const uint32_t sources = info.getSources();
-        if (isMouseOrTouchpad(sources)) {
+        const bool isKnownMouse = mMouseDevices.count(info.getId()) != 0;
+
+        if (isMouseOrTouchpad(sources) || isKnownMouse) {
             const int32_t displayId = getTargetMouseDisplayLocked(info.getAssociatedDisplayId());
             mouseDisplaysToKeep.insert(displayId);
             // For mice, show the cursor immediately when the device is first connected or
@@ -439,8 +450,8 @@
             auto [mousePointerIt, isNewMousePointer] =
                     mMousePointersByDisplay.try_emplace(displayId,
                                                         getMouseControllerConstructor(displayId));
-            auto [_, isNewMouseDevice] = mMouseDevices.emplace(info.getId());
-            if ((isNewMouseDevice || isNewMousePointer) && canUnfadeOnDisplay(displayId)) {
+            mMouseDevices.emplace(info.getId());
+            if ((!isKnownMouse || isNewMousePointer) && canUnfadeOnDisplay(displayId)) {
                 mousePointerIt->second->unfade(PointerControllerInterface::Transition::IMMEDIATE);
             }
         }
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index 293ad66..a4dd909 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -1,10 +1,10 @@
 {
   "presubmit": [
     {
-      "name": "CtsWindowManagerDeviceWindow",
+      "name": "CtsWindowManagerDeviceInput",
       "options": [
         {
-          "include-filter": "android.server.wm.window.WindowInputTests"
+          "include-filter": "android.server.wm.input.WindowInputTests"
         }
       ]
     },
@@ -146,6 +146,9 @@
           "include-filter": "android.security.cts.Poc19_03#testPocBug_115739809"
         }
       ]
+    },
+    {
+      "name": "monkey_test"
     }
   ],
   "postsubmit": [
@@ -284,6 +287,9 @@
     },
     {
       "name": "CtsInputHostTestCases"
+    },
+    {
+      "name": "monkey_test"
     }
   ],
   "staged-platinum-postsubmit": [
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl
index 2f6b8fc..cc0592e 100644
--- a/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl
@@ -21,6 +21,13 @@
   * infrastructure.
   *
   * <p>
+  * Earlier, we used rust thread park()/unpark() to put the thread to sleep and wake up from sleep.
+  * But that caused some breakages after migrating the rust system crates to 2021 edition. Since,
+  * the threads are created in C++, it was more reliable to rely on C++ side of the implementation
+  * to implement the sleep and wake functions.
+  * </p>
+  *
+  * <p>
   * NOTE: Tried using rust provided threading infrastructure but that uses std::thread which doesn't
   * have JNI support and can't call into Java policy that we use currently. libutils provided
   * Thread.h also recommends against using std::thread and using the provided infrastructure that
@@ -33,6 +40,16 @@
     /** Finish input thread (if not running, this call does nothing) */
     void finish();
 
+    /** Wakes up the thread (if sleeping) */
+    void wake();
+
+    /**
+      * Puts the thread to sleep until a future time provided.
+      *
+      * NOTE: The thread can be awaken before the provided time using {@link wake()} function.
+      */
+    void sleepUntil(long whenNanos);
+
     /** Callbacks from C++ to call into inputflinger rust components */
     interface IInputThreadCallback {
         /**
diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp
index 6d71acc..29aa3c3 100644
--- a/services/inputflinger/dispatcher/Android.bp
+++ b/services/inputflinger/dispatcher/Android.bp
@@ -59,6 +59,8 @@
     srcs: [":libinputdispatcher_sources"],
     shared_libs: [
         "libbase",
+        "libbinder",
+        "libbinder_ndk",
         "libcrypto",
         "libcutils",
         "libinput",
@@ -69,6 +71,7 @@
         "libutils",
         "libstatspull",
         "libstatssocket",
+        "packagemanager_aidl-cpp",
         "server_configurable_flags",
     ],
     static_libs: [
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index 0246d60..c8bc87f 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -23,6 +23,7 @@
 
 #include <android-base/stringprintf.h>
 #include <cutils/atomic.h>
+#include <ftl/enum.h>
 #include <inttypes.h>
 
 using android::base::StringPrintf;
@@ -261,9 +262,10 @@
 std::string SensorEntry::getDescription() const {
     std::string msg;
     msg += StringPrintf("SensorEntry(deviceId=%d, source=%s, sensorType=%s, "
-                        "accuracy=0x%08x, hwTimestamp=%" PRId64,
+                        "accuracy=%s, hwTimestamp=%" PRId64,
                         deviceId, inputEventSourceToString(source).c_str(),
-                        ftl::enum_string(sensorType).c_str(), accuracy, hwTimestamp);
+                        ftl::enum_string(sensorType).c_str(), ftl::enum_string(accuracy).c_str(),
+                        hwTimestamp);
 
     if (IS_DEBUGGABLE_BUILD) {
         for (size_t i = 0; i < values.size(); i++) {
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 4389d65..de841ba 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -88,13 +88,12 @@
 }
 
 // Create the input tracing backend that writes to perfetto from a single thread.
-std::unique_ptr<trace::InputTracingBackendInterface> createInputTracingBackendIfEnabled(
-        trace::impl::PerfettoBackend::GetPackageUid getPackageUid) {
+std::unique_ptr<trace::InputTracingBackendInterface> createInputTracingBackendIfEnabled() {
     if (!isInputTracingEnabled()) {
         return nullptr;
     }
     return std::make_unique<trace::impl::ThreadedBackend<trace::impl::PerfettoBackend>>(
-            trace::impl::PerfettoBackend(getPackageUid));
+            trace::impl::PerfettoBackend());
 }
 
 template <class Entry>
@@ -904,9 +903,7 @@
 // --- InputDispatcher ---
 
 InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy)
-      : InputDispatcher(policy, createInputTracingBackendIfEnabled([&policy](std::string pkg) {
-                            return policy.getPackageUid(pkg);
-                        })) {}
+      : InputDispatcher(policy, createInputTracingBackendIfEnabled()) {}
 
 InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy,
                                  std::unique_ptr<trace::InputTracingBackendInterface> traceBackend)
@@ -2455,7 +2452,7 @@
         if (newTouchedWindowHandle == nullptr) {
             ALOGD("No new touched window at (%.1f, %.1f) in display %" PRId32, x, y, displayId);
             // Try to assign the pointer to the first foreground window we find, if there is one.
-            newTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle();
+            newTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle(entry.deviceId);
         }
 
         // Verify targeted injection.
@@ -2619,11 +2616,11 @@
 
         // Check whether touches should slip outside of the current foreground window.
         if (maskedAction == AMOTION_EVENT_ACTION_MOVE && entry.getPointerCount() == 1 &&
-            tempTouchState.isSlippery()) {
+            tempTouchState.isSlippery(entry.deviceId)) {
             const auto [x, y] = resolveTouchedPosition(entry);
             const bool isStylus = isPointerFromStylus(entry, /*pointerIndex=*/0);
             sp<WindowInfoHandle> oldTouchedWindowHandle =
-                    tempTouchState.getFirstForegroundWindowHandle();
+                    tempTouchState.getFirstForegroundWindowHandle(entry.deviceId);
             LOG_ALWAYS_FATAL_IF(oldTouchedWindowHandle == nullptr);
             sp<WindowInfoHandle> newTouchedWindowHandle =
                     findTouchedWindowAtLocked(displayId, x, y, isStylus);
@@ -2741,7 +2738,7 @@
     // has a different UID, then we will not reveal coordinate information to this window.
     if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
         sp<WindowInfoHandle> foregroundWindowHandle =
-                tempTouchState.getFirstForegroundWindowHandle();
+                tempTouchState.getFirstForegroundWindowHandle(entry.deviceId);
         if (foregroundWindowHandle) {
             const auto foregroundWindowUid = foregroundWindowHandle->getInfo()->ownerUid;
             for (InputTarget& target : targets) {
@@ -3054,7 +3051,11 @@
                    << ", windowInfo->globalScaleFactor=" << windowInfo->globalScaleFactor;
     }
 
-    it->addPointers(pointerIds, windowInfo->transform);
+    Result<void> result = it->addPointers(pointerIds, windowInfo->transform);
+    if (!result.ok()) {
+        logDispatchStateLocked();
+        LOG(FATAL) << result.error().message();
+    }
 }
 
 void InputDispatcher::addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets,
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index c2d2b7b..02bc368 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -338,9 +338,8 @@
         // it's unlikely that those two streams would be consistent with each other. Therefore,
         // cancel the previous gesture if the display id changes.
         if (motionEntry.displayId != lastMemento.displayId) {
-            LOG(INFO) << "Canceling stream: last displayId was "
-                      << inputEventSourceToString(lastMemento.displayId) << " and new event is "
-                      << motionEntry;
+            LOG(INFO) << "Canceling stream: last displayId was " << lastMemento.displayId
+                      << " and new event is " << motionEntry;
             return true;
         }
 
diff --git a/services/inputflinger/dispatcher/InputTarget.cpp b/services/inputflinger/dispatcher/InputTarget.cpp
index 35ad858..f9a2855 100644
--- a/services/inputflinger/dispatcher/InputTarget.cpp
+++ b/services/inputflinger/dispatcher/InputTarget.cpp
@@ -22,6 +22,8 @@
 #include <inttypes.h>
 #include <string>
 
+using android::base::Error;
+using android::base::Result;
 using android::base::StringPrintf;
 
 namespace android::inputdispatcher {
@@ -35,28 +37,29 @@
 InputTarget::InputTarget(const std::shared_ptr<Connection>& connection, ftl::Flags<Flags> flags)
       : connection(connection), flags(flags) {}
 
-void InputTarget::addPointers(std::bitset<MAX_POINTER_ID + 1> newPointerIds,
-                              const ui::Transform& transform) {
+Result<void> InputTarget::addPointers(std::bitset<MAX_POINTER_ID + 1> newPointerIds,
+                                      const ui::Transform& transform) {
     // The pointerIds can be empty, but still a valid InputTarget. This can happen when there is no
     // valid pointer property from the input event.
     if (newPointerIds.none()) {
         setDefaultPointerTransform(transform);
-        return;
+        return {};
     }
 
     // Ensure that the new set of pointers doesn't overlap with the current set of pointers.
     if ((getPointerIds() & newPointerIds).any()) {
-        LOG(FATAL) << __func__ << " - overlap with incoming pointers "
-                   << bitsetToString(newPointerIds) << " in " << *this;
+        return Error() << __func__ << " - overlap with incoming pointers "
+                       << bitsetToString(newPointerIds) << " in " << *this;
     }
 
     for (auto& [existingTransform, existingPointers] : mPointerTransforms) {
         if (transform == existingTransform) {
             existingPointers |= newPointerIds;
-            return;
+            return {};
         }
     }
     mPointerTransforms.emplace_back(transform, newPointerIds);
+    return {};
 }
 
 void InputTarget::setDefaultPointerTransform(const ui::Transform& transform) {
diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h
index 058639a..60a75ee 100644
--- a/services/inputflinger/dispatcher/InputTarget.h
+++ b/services/inputflinger/dispatcher/InputTarget.h
@@ -92,7 +92,8 @@
     InputTarget() = default;
     InputTarget(const std::shared_ptr<Connection>&, ftl::Flags<Flags> = {});
 
-    void addPointers(std::bitset<MAX_POINTER_ID + 1> pointerIds, const ui::Transform& transform);
+    android::base::Result<void> addPointers(std::bitset<MAX_POINTER_ID + 1> pointerIds,
+                                            const ui::Transform& transform);
     void setDefaultPointerTransform(const ui::Transform& transform);
 
     /**
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index 0caa5e1..296c334 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -180,9 +180,11 @@
     clearWindowsWithoutPointers();
 }
 
-sp<WindowInfoHandle> TouchState::getFirstForegroundWindowHandle() const {
-    for (size_t i = 0; i < windows.size(); i++) {
-        const TouchedWindow& window = windows[i];
+sp<WindowInfoHandle> TouchState::getFirstForegroundWindowHandle(DeviceId deviceId) const {
+    for (const auto& window : windows) {
+        if (!window.hasTouchingPointers(deviceId)) {
+            continue;
+        }
         if (window.targetFlags.test(InputTarget::Flags::FOREGROUND)) {
             return window.windowHandle;
         }
@@ -190,10 +192,13 @@
     return nullptr;
 }
 
-bool TouchState::isSlippery() const {
+bool TouchState::isSlippery(DeviceId deviceId) const {
     // Must have exactly one foreground window.
     bool haveSlipperyForegroundWindow = false;
     for (const TouchedWindow& window : windows) {
+        if (!window.hasTouchingPointers(deviceId)) {
+            continue;
+        }
         if (window.targetFlags.test(InputTarget::Flags::FOREGROUND)) {
             if (haveSlipperyForegroundWindow ||
                 !window.windowHandle->getInfo()->inputConfig.test(
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 559a3fd..9ddb4e2 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -64,8 +64,8 @@
     // set to false.
     void cancelPointersForNonPilferingWindows();
 
-    sp<android::gui::WindowInfoHandle> getFirstForegroundWindowHandle() const;
-    bool isSlippery() const;
+    sp<android::gui::WindowInfoHandle> getFirstForegroundWindowHandle(DeviceId deviceId) const;
+    bool isSlippery(DeviceId deviceId) const;
     sp<android::gui::WindowInfoHandle> getWallpaperWindow() const;
     const TouchedWindow& getTouchedWindow(
             const sp<android::gui::WindowInfoHandle>& windowHandle) const;
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
index 91a3e3f..62c2b02 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -163,9 +163,6 @@
     virtual void notifyDeviceInteraction(DeviceId deviceId, nsecs_t timestamp,
                                          const std::set<gui::Uid>& uids) = 0;
 
-    /* Get the UID associated with the given package. */
-    virtual gui::Uid getPackageUid(std::string package) = 0;
-
 private:
     // Additional key latency in case a connection is still processing some motion events.
     // This will help with the case when a user touched a button that opens a new window,
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp
index 415b696..4931a5f 100644
--- a/services/inputflinger/dispatcher/trace/InputTracer.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp
@@ -89,13 +89,6 @@
     const auto& info = *target.windowHandle->getInfo();
     const bool isSensitiveTarget =
             info.inputConfig.test(gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING);
-
-    // All FLAG_SECURE targets must be marked as sensitive for tracing.
-    if (info.layoutParamsFlags.test(gui::WindowInfo::Flag::SECURE) && !isSensitiveTarget) {
-        LOG(FATAL)
-                << "Input target with FLAG_SECURE does not set InputConfig::SENSITIVE_FOR_TRACING: "
-                << info;
-    }
     return {target.windowHandle->getInfo()->ownerUid, isSensitiveTarget};
 }
 
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
index 9c39743..9b9633a 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
@@ -21,8 +21,10 @@
 #include "AndroidInputEventProtoConverter.h"
 
 #include <android-base/logging.h>
+#include <binder/IServiceManager.h>
 #include <perfetto/trace/android/android_input_event.pbzero.h>
 #include <private/android_filesystem_config.h>
+#include <utils/String16.h>
 
 namespace android::inputdispatcher::trace::impl {
 
@@ -41,6 +43,34 @@
     }
 }
 
+sp<content::pm::IPackageManagerNative> getPackageManager() {
+    sp<IServiceManager> serviceManager = defaultServiceManager();
+    if (!serviceManager) {
+        LOG(ERROR) << __func__ << ": unable to access native ServiceManager";
+        return nullptr;
+    }
+
+    sp<IBinder> binder = serviceManager->waitForService(String16("package_native"));
+    auto packageManager = interface_cast<content::pm::IPackageManagerNative>(binder);
+    if (!packageManager) {
+        LOG(ERROR) << ": unable to access native PackageManager";
+        return nullptr;
+    }
+    return packageManager;
+}
+
+gui::Uid getPackageUid(const sp<content::pm::IPackageManagerNative>& pm,
+                       const std::string& package) {
+    int32_t outUid = -1;
+    if (auto status = pm->getPackageUid(package, /*flags=*/0, AID_SYSTEM, &outUid);
+        !status.isOk()) {
+        LOG(INFO) << "Failed to get package UID from native package manager for package '"
+                  << package << "': " << status;
+        return gui::Uid::INVALID;
+    }
+    return gui::Uid{static_cast<uid_t>(outUid)};
+}
+
 } // namespace
 
 // --- PerfettoBackend::InputEventDataSource ---
@@ -67,18 +97,24 @@
     InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { ctx.Flush(); });
 }
 
-void PerfettoBackend::InputEventDataSource::initializeUidMap(GetPackageUid getPackageUid) {
+void PerfettoBackend::InputEventDataSource::initializeUidMap() {
     if (mUidMap.has_value()) {
         return;
     }
 
     mUidMap = {{}};
+    auto packageManager = PerfettoBackend::sPackageManagerProvider();
+    if (!packageManager) {
+        LOG(ERROR) << "Failed to initialize UID map: Could not get native package manager";
+        return;
+    }
+
     for (const auto& rule : mConfig.rules) {
         for (const auto& package : rule.matchAllPackages) {
-            mUidMap->emplace(package, getPackageUid(package));
+            mUidMap->emplace(package, getPackageUid(packageManager, package));
         }
         for (const auto& package : rule.matchAnyPackages) {
-            mUidMap->emplace(package, getPackageUid(package));
+            mUidMap->emplace(package, getPackageUid(packageManager, package));
         }
     }
 }
@@ -149,17 +185,22 @@
 
 // --- PerfettoBackend ---
 
+bool PerfettoBackend::sUseInProcessBackendForTest{false};
+
+std::function<sp<content::pm::IPackageManagerNative>()> PerfettoBackend::sPackageManagerProvider{
+        &getPackageManager};
+
 std::once_flag PerfettoBackend::sDataSourceRegistrationFlag{};
 
 std::atomic<int32_t> PerfettoBackend::sNextInstanceId{1};
 
-PerfettoBackend::PerfettoBackend(GetPackageUid getPackagesForUid)
-      : mGetPackageUid(getPackagesForUid) {
+PerfettoBackend::PerfettoBackend() {
     // Use a once-flag to ensure that the data source is only registered once per boot, since
     // we never unregister the InputEventDataSource.
     std::call_once(sDataSourceRegistrationFlag, []() {
         perfetto::TracingInitArgs args;
-        args.backends = perfetto::kSystemBackend;
+        args.backends = sUseInProcessBackendForTest ? perfetto::kInProcessBackend
+                                                    : perfetto::kSystemBackend;
         perfetto::Tracing::Initialize(args);
 
         // Register our custom data source for input event tracing.
@@ -175,7 +216,10 @@
                                        const TracedEventMetadata& metadata) {
     InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
         auto dataSource = ctx.GetDataSourceLocked();
-        dataSource->initializeUidMap(mGetPackageUid);
+        if (!dataSource.valid()) {
+            return;
+        }
+        dataSource->initializeUidMap();
         if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) {
             return;
         }
@@ -196,7 +240,10 @@
                                     const TracedEventMetadata& metadata) {
     InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
         auto dataSource = ctx.GetDataSourceLocked();
-        dataSource->initializeUidMap(mGetPackageUid);
+        if (!dataSource.valid()) {
+            return;
+        }
+        dataSource->initializeUidMap();
         if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) {
             return;
         }
@@ -217,7 +264,10 @@
                                           const TracedEventMetadata& metadata) {
     InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
         auto dataSource = ctx.GetDataSourceLocked();
-        dataSource->initializeUidMap(mGetPackageUid);
+        if (!dataSource.valid()) {
+            return;
+        }
+        dataSource->initializeUidMap();
         if (!dataSource->getFlags().test(TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH)) {
             return;
         }
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
index e945066..d0bab06 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
@@ -20,6 +20,7 @@
 
 #include "InputTracingPerfettoBackendConfig.h"
 
+#include <android/content/pm/IPackageManagerNative.h>
 #include <ftl/flags.h>
 #include <perfetto/tracing.h>
 #include <mutex>
@@ -49,9 +50,10 @@
  */
 class PerfettoBackend : public InputTracingBackendInterface {
 public:
-    using GetPackageUid = std::function<gui::Uid(std::string)>;
+    static bool sUseInProcessBackendForTest;
+    static std::function<sp<content::pm::IPackageManagerNative>()> sPackageManagerProvider;
 
-    explicit PerfettoBackend(GetPackageUid);
+    explicit PerfettoBackend();
     ~PerfettoBackend() override = default;
 
     void traceKeyEvent(const TracedKeyEvent&, const TracedEventMetadata&) override;
@@ -61,6 +63,7 @@
 private:
     // Implementation of the perfetto data source.
     // Each instance of the InputEventDataSource represents a different tracing session.
+    // Its lifecycle is controlled by perfetto.
     class InputEventDataSource : public perfetto::DataSource<InputEventDataSource> {
     public:
         explicit InputEventDataSource();
@@ -69,7 +72,7 @@
         void OnStart(const StartArgs&) override;
         void OnStop(const StopArgs&) override;
 
-        void initializeUidMap(GetPackageUid);
+        void initializeUidMap();
         bool shouldIgnoreTracedInputEvent(const EventType&) const;
         inline ftl::Flags<TraceFlag> getFlags() const { return mConfig.flags; }
         TraceLevel resolveTraceLevel(const TracedEventMetadata&) const;
@@ -83,10 +86,6 @@
         std::optional<std::map<std::string, gui::Uid>> mUidMap;
     };
 
-    // TODO(b/330360505): Query the native package manager directly from the data source,
-    //   and remove this.
-    GetPackageUid mGetPackageUid;
-
     static std::once_flag sDataSourceRegistrationFlag;
     static std::atomic<int32_t> sNextInstanceId;
 };
diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
index 77d09cb..3c3c15a 100644
--- a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
@@ -57,6 +57,7 @@
                                                 const TracedEventMetadata& metadata) {
     std::scoped_lock lock(mLock);
     mQueue.emplace_back(event, metadata);
+    setIdleStatus(false);
     mThreadWakeCondition.notify_all();
 }
 
@@ -65,6 +66,7 @@
                                              const TracedEventMetadata& metadata) {
     std::scoped_lock lock(mLock);
     mQueue.emplace_back(event, metadata);
+    setIdleStatus(false);
     mThreadWakeCondition.notify_all();
 }
 
@@ -73,6 +75,7 @@
                                                    const TracedEventMetadata& metadata) {
     std::scoped_lock lock(mLock);
     mQueue.emplace_back(dispatchArgs, metadata);
+    setIdleStatus(false);
     mThreadWakeCondition.notify_all();
 }
 
@@ -84,10 +87,15 @@
         std::unique_lock lock(mLock);
         base::ScopedLockAssertion assumeLocked(mLock);
 
+        if (mQueue.empty()) {
+            setIdleStatus(true);
+        }
+
         // Wait until we need to process more events or exit.
         mThreadWakeCondition.wait(lock,
                                   [&]() REQUIRES(mLock) { return mThreadExit || !mQueue.empty(); });
         if (mThreadExit) {
+            setIdleStatus(true);
             return;
         }
 
@@ -109,6 +117,36 @@
     entries.clear();
 }
 
+template <typename Backend>
+std::function<void()> ThreadedBackend<Backend>::getIdleWaiterForTesting() {
+    std::scoped_lock lock(mLock);
+    if (!mIdleWaiter) {
+        mIdleWaiter = std::make_shared<IdleWaiter>();
+    }
+
+    // Return a lambda that holds a strong reference to the idle waiter, whose lifetime can extend
+    // beyond this threaded backend object.
+    return [idleWaiter = mIdleWaiter]() {
+        std::unique_lock idleLock(idleWaiter->idleLock);
+        base::ScopedLockAssertion assumeLocked(idleWaiter->idleLock);
+        idleWaiter->threadIdleCondition.wait(idleLock, [&]() REQUIRES(idleWaiter->idleLock) {
+            return idleWaiter->isIdle;
+        });
+    };
+}
+
+template <typename Backend>
+void ThreadedBackend<Backend>::setIdleStatus(bool isIdle) {
+    if (!mIdleWaiter) {
+        return;
+    }
+    std::scoped_lock idleLock(mIdleWaiter->idleLock);
+    mIdleWaiter->isIdle = isIdle;
+    if (isIdle) {
+        mIdleWaiter->threadIdleCondition.notify_all();
+    }
+}
+
 // Explicit template instantiation for the PerfettoBackend.
 template class ThreadedBackend<PerfettoBackend>;
 
diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.h b/services/inputflinger/dispatcher/trace/ThreadedBackend.h
index 650a87e..52a84c4 100644
--- a/services/inputflinger/dispatcher/trace/ThreadedBackend.h
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.h
@@ -42,6 +42,9 @@
     void traceMotionEvent(const TracedMotionEvent&, const TracedEventMetadata&) override;
     void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventMetadata&) override;
 
+    /** Returns a function that, when called, will block until the tracing thread is idle. */
+    std::function<void()> getIdleWaiterForTesting();
+
 private:
     std::mutex mLock;
     bool mThreadExit GUARDED_BY(mLock){false};
@@ -52,12 +55,21 @@
                       TracedEventMetadata>;
     std::vector<TraceEntry> mQueue GUARDED_BY(mLock);
 
+    struct IdleWaiter {
+        std::mutex idleLock;
+        std::condition_variable threadIdleCondition;
+        bool isIdle GUARDED_BY(idleLock){false};
+    };
+    // The lazy-initialized object used to wait for the tracing thread to idle.
+    std::shared_ptr<IdleWaiter> mIdleWaiter GUARDED_BY(mLock);
+
     // InputThread stops when its destructor is called. Initialize it last so that it is the
     // first thing to be destructed. This will guarantee the thread will not access other
     // members that have already been destructed.
     InputThread mTracerThread;
 
     void threadLoop();
+    void setIdleStatus(bool isIdle) REQUIRES(mLock);
 };
 
 } // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 79c8a4b..77e672c 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -111,9 +111,13 @@
     // Used to determine which DisplayViewport should be tied to which InputDevice.
     std::unordered_map<std::string, uint8_t> portAssociations;
 
-    // The associations between input device physical port locations and display unique ids.
+    // The associations between input device ports and display unique ids.
     // Used to determine which DisplayViewport should be tied to which InputDevice.
-    std::unordered_map<std::string, std::string> uniqueIdAssociations;
+    std::unordered_map<std::string, std::string> uniqueIdAssociationsByPort;
+
+    // The associations between input device descriptor and display unique ids.
+    // Used to determine which DisplayViewport should be tied to which InputDevice.
+    std::unordered_map<std::string, std::string> uniqueIdAssociationsByDescriptor;
 
     // The associations between input device ports device types.
     // This is used to determine which device type and source should be tied to which InputDevice.
diff --git a/services/inputflinger/include/NotifyArgsBuilders.h b/services/inputflinger/include/NotifyArgsBuilders.h
index 8ffbc11..1bd5595 100644
--- a/services/inputflinger/include/NotifyArgsBuilders.h
+++ b/services/inputflinger/include/NotifyArgsBuilders.h
@@ -30,8 +30,11 @@
 
 class MotionArgsBuilder {
 public:
-    MotionArgsBuilder(int32_t action, int32_t source) {
+    MotionArgsBuilder(int32_t action, int32_t source) : mEventId(InputEvent::nextId()) {
         mAction = action;
+        if (mAction == AMOTION_EVENT_ACTION_CANCEL) {
+            addFlag(AMOTION_EVENT_FLAG_CANCELED);
+        }
         mSource = source;
         mEventTime = systemTime(SYSTEM_TIME_MONOTONIC);
         mDownTime = mEventTime;
@@ -97,7 +100,7 @@
         return *this;
     }
 
-    NotifyMotionArgs build() {
+    NotifyMotionArgs build() const {
         std::vector<PointerProperties> pointerProperties;
         std::vector<PointerCoords> pointerCoords;
         for (const PointerBuilder& pointer : mPointers) {
@@ -106,19 +109,17 @@
         }
 
         // Set mouse cursor position for the most common cases to avoid boilerplate.
+        float resolvedCursorX = mRawXCursorPosition;
+        float resolvedCursorY = mRawYCursorPosition;
         if (mSource == AINPUT_SOURCE_MOUSE &&
             !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition) &&
             BitSet64::hasBit(pointerCoords[0].bits, AMOTION_EVENT_AXIS_X) &&
             BitSet64::hasBit(pointerCoords[0].bits, AMOTION_EVENT_AXIS_Y)) {
-            mRawXCursorPosition = pointerCoords[0].getX();
-            mRawYCursorPosition = pointerCoords[0].getY();
+            resolvedCursorX = pointerCoords[0].getX();
+            resolvedCursorY = pointerCoords[0].getY();
         }
 
-        if (mAction == AMOTION_EVENT_ACTION_CANCEL) {
-            addFlag(AMOTION_EVENT_FLAG_CANCELED);
-        }
-
-        return {InputEvent::nextId(),
+        return {mEventId,
                 mEventTime,
                 /*readTime=*/mEventTime,
                 mDeviceId,
@@ -137,13 +138,14 @@
                 pointerCoords.data(),
                 /*xPrecision=*/0,
                 /*yPrecision=*/0,
-                mRawXCursorPosition,
-                mRawYCursorPosition,
+                resolvedCursorX,
+                resolvedCursorY,
                 mDownTime,
                 /*videoFrames=*/{}};
     }
 
 private:
+    const int32_t mEventId;
     int32_t mAction;
     int32_t mDeviceId{DEFAULT_DEVICE_ID};
     uint32_t mSource;
@@ -163,7 +165,7 @@
 
 class KeyArgsBuilder {
 public:
-    KeyArgsBuilder(int32_t action, int32_t source) {
+    KeyArgsBuilder(int32_t action, int32_t source) : mEventId(InputEvent::nextId()) {
         mAction = action;
         mSource = source;
         mEventTime = systemTime(SYSTEM_TIME_MONOTONIC);
@@ -206,7 +208,7 @@
     }
 
     NotifyKeyArgs build() const {
-        return {InputEvent::nextId(),
+        return {mEventId,
                 mEventTime,
                 /*readTime=*/mEventTime,
                 mDeviceId,
@@ -222,6 +224,7 @@
     }
 
 private:
+    const int32_t mEventId;
     int32_t mAction;
     int32_t mDeviceId = DEFAULT_DEVICE_ID;
     uint32_t mSource;
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 782c84a..44dfd15 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -77,11 +77,11 @@
 
         // If a device is associated with a specific display but there is no
         // associated DisplayViewport, don't enable the device.
-        if (enable && (mAssociatedDisplayPort || mAssociatedDisplayUniqueId) &&
+        if (enable && (mAssociatedDisplayPort || mAssociatedDisplayUniqueIdByPort) &&
             !mAssociatedViewport) {
             const std::string desc = mAssociatedDisplayPort
                     ? "port " + std::to_string(*mAssociatedDisplayPort)
-                    : "uniqueId " + *mAssociatedDisplayUniqueId;
+                    : "uniqueId " + *mAssociatedDisplayUniqueIdByPort;
             ALOGW("Cannot enable input device %s because it is associated "
                   "with %s, but the corresponding viewport is not found",
                   getName().c_str(), desc.c_str());
@@ -124,9 +124,15 @@
     } else {
         dump += "<none>\n";
     }
-    dump += StringPrintf(INDENT2 "AssociatedDisplayUniqueId: ");
-    if (mAssociatedDisplayUniqueId) {
-        dump += StringPrintf("%s\n", mAssociatedDisplayUniqueId->c_str());
+    dump += StringPrintf(INDENT2 "AssociatedDisplayUniqueIdByPort: ");
+    if (mAssociatedDisplayUniqueIdByPort) {
+        dump += StringPrintf("%s\n", mAssociatedDisplayUniqueIdByPort->c_str());
+    } else {
+        dump += "<none>\n";
+    }
+    dump += StringPrintf(INDENT2 "AssociatedDisplayUniqueIdByDescriptor: ");
+    if (mAssociatedDisplayUniqueIdByDescriptor) {
+        dump += StringPrintf("%s\n", mAssociatedDisplayUniqueIdByDescriptor->c_str());
     } else {
         dump += "<none>\n";
     }
@@ -269,8 +275,28 @@
 
             // In most situations, no port or name will be specified.
             mAssociatedDisplayPort = std::nullopt;
-            mAssociatedDisplayUniqueId = std::nullopt;
+            mAssociatedDisplayUniqueIdByPort = std::nullopt;
             mAssociatedViewport = std::nullopt;
+            // Find the display port that corresponds to the current input device descriptor
+            const std::string& inputDeviceDescriptor = mIdentifier.descriptor;
+            if (!inputDeviceDescriptor.empty()) {
+                const std::unordered_map<std::string, uint8_t>& ports =
+                        readerConfig.portAssociations;
+                const auto& displayPort = ports.find(inputDeviceDescriptor);
+                if (displayPort != ports.end()) {
+                    mAssociatedDisplayPort = std::make_optional(displayPort->second);
+                } else {
+                    const std::unordered_map<std::string, std::string>&
+                            displayUniqueIdsByDescriptor =
+                                    readerConfig.uniqueIdAssociationsByDescriptor;
+                    const auto& displayUniqueIdByDescriptor =
+                            displayUniqueIdsByDescriptor.find(inputDeviceDescriptor);
+                    if (displayUniqueIdByDescriptor != displayUniqueIdsByDescriptor.end()) {
+                        mAssociatedDisplayUniqueIdByDescriptor =
+                                displayUniqueIdByDescriptor->second;
+                    }
+                }
+            }
             // Find the display port that corresponds to the current input port.
             const std::string& inputPort = mIdentifier.location;
             if (!inputPort.empty()) {
@@ -280,11 +306,11 @@
                 if (displayPort != ports.end()) {
                     mAssociatedDisplayPort = std::make_optional(displayPort->second);
                 } else {
-                    const std::unordered_map<std::string, std::string>& displayUniqueIds =
-                            readerConfig.uniqueIdAssociations;
-                    const auto& displayUniqueId = displayUniqueIds.find(inputPort);
-                    if (displayUniqueId != displayUniqueIds.end()) {
-                        mAssociatedDisplayUniqueId = displayUniqueId->second;
+                    const std::unordered_map<std::string, std::string>& displayUniqueIdsByPort =
+                            readerConfig.uniqueIdAssociationsByPort;
+                    const auto& displayUniqueIdByPort = displayUniqueIdsByPort.find(inputPort);
+                    if (displayUniqueIdByPort != displayUniqueIdsByPort.end()) {
+                        mAssociatedDisplayUniqueIdByPort = displayUniqueIdByPort->second;
                     }
                 }
             }
@@ -299,13 +325,21 @@
                           "but the corresponding viewport is not found.",
                           getName().c_str(), *mAssociatedDisplayPort);
                 }
-            } else if (mAssociatedDisplayUniqueId != std::nullopt) {
-                mAssociatedViewport =
-                        readerConfig.getDisplayViewportByUniqueId(*mAssociatedDisplayUniqueId);
+            } else if (mAssociatedDisplayUniqueIdByDescriptor != std::nullopt) {
+                mAssociatedViewport = readerConfig.getDisplayViewportByUniqueId(
+                        *mAssociatedDisplayUniqueIdByDescriptor);
                 if (!mAssociatedViewport) {
                     ALOGW("Input device %s should be associated with display %s but the "
                           "corresponding viewport cannot be found",
-                          getName().c_str(), mAssociatedDisplayUniqueId->c_str());
+                          getName().c_str(), mAssociatedDisplayUniqueIdByDescriptor->c_str());
+                }
+            } else if (mAssociatedDisplayUniqueIdByPort != std::nullopt) {
+                mAssociatedViewport = readerConfig.getDisplayViewportByUniqueId(
+                        *mAssociatedDisplayUniqueIdByPort);
+                if (!mAssociatedViewport) {
+                    ALOGW("Input device %s should be associated with display %s but the "
+                          "corresponding viewport cannot be found",
+                          getName().c_str(), mAssociatedDisplayUniqueIdByPort->c_str());
                 }
             }
 
@@ -409,7 +443,7 @@
     InputDeviceInfo outDeviceInfo;
     outDeviceInfo.initialize(mId, mGeneration, mControllerNumber, mIdentifier, mAlias, mIsExternal,
                              mHasMic, getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE),
-                             {mShouldSmoothScroll});
+                             {mShouldSmoothScroll}, isEnabled());
 
     for_each_mapper(
             [&outDeviceInfo](InputMapper& mapper) { mapper.populateDeviceInfo(outDeviceInfo); });
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 0719b0c..feb4071 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -63,8 +63,11 @@
     inline std::optional<uint8_t> getAssociatedDisplayPort() const {
         return mAssociatedDisplayPort;
     }
-    inline std::optional<std::string> getAssociatedDisplayUniqueId() const {
-        return mAssociatedDisplayUniqueId;
+    inline std::optional<std::string> getAssociatedDisplayUniqueIdByPort() const {
+        return mAssociatedDisplayUniqueIdByPort;
+    }
+    inline std::optional<std::string> getAssociatedDisplayUniqueIdByDescriptor() const {
+        return mAssociatedDisplayUniqueIdByDescriptor;
     }
     inline std::optional<std::string> getDeviceTypeAssociation() const {
         return mAssociatedDeviceType;
@@ -194,7 +197,8 @@
     bool mIsWaking;
     bool mIsExternal;
     std::optional<uint8_t> mAssociatedDisplayPort;
-    std::optional<std::string> mAssociatedDisplayUniqueId;
+    std::optional<std::string> mAssociatedDisplayUniqueIdByPort;
+    std::optional<std::string> mAssociatedDisplayUniqueIdByDescriptor;
     std::optional<std::string> mAssociatedDeviceType;
     std::optional<DisplayViewport> mAssociatedViewport;
     bool mHasMic;
@@ -449,8 +453,11 @@
     inline std::optional<uint8_t> getAssociatedDisplayPort() const {
         return mDevice.getAssociatedDisplayPort();
     }
-    inline std::optional<std::string> getAssociatedDisplayUniqueId() const {
-        return mDevice.getAssociatedDisplayUniqueId();
+    inline std::optional<std::string> getAssociatedDisplayUniqueIdByPort() const {
+        return mDevice.getAssociatedDisplayUniqueIdByPort();
+    }
+    inline std::optional<std::string> getAssociatedDisplayUniqueIdByDescriptor() const {
+        return mDevice.getAssociatedDisplayUniqueIdByDescriptor();
     }
     inline std::optional<std::string> getDeviceTypeAssociation() const {
         return mDevice.getDeviceTypeAssociation();
diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp
index 0692dbb..b6c5c98 100644
--- a/services/inputflinger/reader/mapper/InputMapper.cpp
+++ b/services/inputflinger/reader/mapper/InputMapper.cpp
@@ -20,6 +20,8 @@
 
 #include <sstream>
 
+#include <ftl/enum.h>
+
 #include "InputDevice.h"
 #include "input/PrintTools.h"
 
@@ -133,7 +135,7 @@
     dump += StringPrintf(INDENT4 "When: %" PRId64 "\n", state.when);
     dump += StringPrintf(INDENT4 "Pressure: %s\n", toString(state.pressure).c_str());
     dump += StringPrintf(INDENT4 "Button State: 0x%08x\n", state.buttons);
-    dump += StringPrintf(INDENT4 "Tool Type: %" PRId32 "\n", state.toolType);
+    dump += StringPrintf(INDENT4 "Tool Type: %s\n", ftl::enum_string(state.toolType).c_str());
 }
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 7d27d4a..81449d1 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -20,8 +20,24 @@
 
 #include "TouchInputMapper.h"
 
+#include <algorithm>
+#include <cinttypes>
+#include <cmath>
+#include <cstddef>
+#include <tuple>
+
+#include <math.h>
+
+#include <android-base/stringprintf.h>
+#include <android/input.h>
 #include <ftl/enum.h>
 #include <input/PrintTools.h>
+#include <input/PropertyMap.h>
+#include <input/VirtualKeyMap.h>
+#include <linux/input-event-codes.h>
+#include <log/log_main.h>
+#include <math/vec2.h>
+#include <ui/FloatRect.h>
 
 #include "CursorButtonAccumulator.h"
 #include "CursorScrollAccumulator.h"
@@ -147,20 +163,6 @@
     info.addMotionRange(mOrientedRanges.y);
     info.addMotionRange(mOrientedRanges.pressure);
 
-    if (mDeviceMode == DeviceMode::UNSCALED && mSource == AINPUT_SOURCE_TOUCHPAD) {
-        // Populate RELATIVE_X and RELATIVE_Y motion ranges for touchpad capture mode.
-        //
-        // RELATIVE_X and RELATIVE_Y motion ranges should be the largest possible relative
-        // motion, i.e. the hardware dimensions, as the finger could move completely across the
-        // touchpad in one sample cycle.
-        const InputDeviceInfo::MotionRange& x = mOrientedRanges.x;
-        const InputDeviceInfo::MotionRange& y = mOrientedRanges.y;
-        info.addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, mSource, -x.max, x.max, x.flat, x.fuzz,
-                            x.resolution);
-        info.addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, mSource, -y.max, y.max, y.flat, y.fuzz,
-                            y.resolution);
-    }
-
     if (mOrientedRanges.size) {
         info.addMotionRange(*mOrientedRanges.size);
     }
@@ -531,14 +533,20 @@
  * 4. Otherwise, use a non-display viewport.
  */
 std::optional<DisplayViewport> TouchInputMapper::findViewport() {
-    if (mParameters.hasAssociatedDisplay && mDeviceMode != DeviceMode::UNSCALED) {
+    if (mParameters.hasAssociatedDisplay) {
         if (getDeviceContext().getAssociatedViewport()) {
             return getDeviceContext().getAssociatedViewport();
         }
 
-        const std::optional<std::string> associatedDisplayUniqueId =
-                getDeviceContext().getAssociatedDisplayUniqueId();
-        if (associatedDisplayUniqueId) {
+        const std::optional<std::string> associatedDisplayUniqueIdByDescriptor =
+                getDeviceContext().getAssociatedDisplayUniqueIdByDescriptor();
+        if (associatedDisplayUniqueIdByDescriptor) {
+            return getDeviceContext().getAssociatedViewport();
+        }
+
+        const std::optional<std::string> associatedDisplayUniqueIdByPort =
+                getDeviceContext().getAssociatedDisplayUniqueIdByPort();
+        if (associatedDisplayUniqueIdByPort) {
             return getDeviceContext().getAssociatedViewport();
         }
 
@@ -939,8 +947,10 @@
         mSource = AINPUT_SOURCE_TOUCH_NAVIGATION;
         mDeviceMode = DeviceMode::NAVIGATION;
     } else {
-        mSource = AINPUT_SOURCE_TOUCHPAD;
-        mDeviceMode = DeviceMode::UNSCALED;
+        ALOGW("Touch device '%s' has invalid parameters or configuration.  The device will be "
+              "inoperable.",
+              getDeviceName().c_str());
+        mDeviceMode = DeviceMode::DISABLED;
     }
 
     const std::optional<DisplayViewport> newViewportOpt = findViewport();
@@ -1059,10 +1069,11 @@
     }
 
     if ((viewportChanged && !skipViewportUpdate) || deviceModeChanged) {
-        ALOGI("Device reconfigured: id=%d, name='%s', size %s, orientation %d, mode %d, "
+        ALOGI("Device reconfigured: id=%d, name='%s', size %s, orientation %s, mode %s, "
               "display id %d",
               getDeviceId(), getDeviceName().c_str(), toString(mDisplayBounds).c_str(),
-              mInputDeviceOrientation, mDeviceMode, mViewport.displayId);
+              ftl::enum_string(mInputDeviceOrientation).c_str(),
+              ftl::enum_string(mDeviceMode).c_str(), mViewport.displayId);
 
         configureVirtualKeys();
 
@@ -1113,7 +1124,8 @@
     dump += StringPrintf(INDENT3 "DisplayBounds: %s\n", toString(mDisplayBounds).c_str());
     dump += StringPrintf(INDENT3 "PhysicalFrameInRotatedDisplay: %s\n",
                          toString(mPhysicalFrameInRotatedDisplay).c_str());
-    dump += StringPrintf(INDENT3 "InputDeviceOrientation: %d\n", mInputDeviceOrientation);
+    dump += StringPrintf(INDENT3 "InputDeviceOrientation: %s\n",
+                         ftl::enum_string(mInputDeviceOrientation).c_str());
 }
 
 void TouchInputMapper::configureVirtualKeys() {
@@ -1884,8 +1896,7 @@
     }
 
     if (!mCurrentRawState.rawPointerData.hoveringIdBits.isEmpty() &&
-        mCurrentRawState.rawPointerData.touchingIdBits.isEmpty() &&
-        mDeviceMode != DeviceMode::UNSCALED) {
+        mCurrentRawState.rawPointerData.touchingIdBits.isEmpty()) {
         // We have hovering pointers, and there are no touching pointers.
         bool hoveringPointersInFrame = false;
         auto hoveringIds = mCurrentRawState.rawPointerData.hoveringIdBits;
@@ -1912,7 +1923,7 @@
         // Skip checking whether the pointer is inside the physical frame if the device is in
         // unscaled or pointer mode.
         if (!isPointInsidePhysicalFrame(pointer.x, pointer.y) &&
-            mDeviceMode != DeviceMode::UNSCALED && mDeviceMode != DeviceMode::POINTER) {
+            mDeviceMode != DeviceMode::POINTER) {
             // If exactly one pointer went down, check for virtual key hit.
             // Otherwise, we will drop the entire stroke.
             if (mCurrentRawState.rawPointerData.touchingIdBits.count() == 1) {
@@ -3090,11 +3101,13 @@
 
     if (DEBUG_GESTURES) {
         ALOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, "
-              "currentGestureMode=%d, currentGestureIdBits=0x%08x, "
-              "lastGestureMode=%d, lastGestureIdBits=0x%08x",
+              "currentGestureMode=%s, currentGestureIdBits=0x%08x, "
+              "lastGestureMode=%s, lastGestureIdBits=0x%08x",
               toString(*outFinishPreviousGesture), toString(*outCancelPreviousGesture),
-              mPointerGesture.currentGestureMode, mPointerGesture.currentGestureIdBits.value,
-              mPointerGesture.lastGestureMode, mPointerGesture.lastGestureIdBits.value);
+              ftl::enum_string(mPointerGesture.currentGestureMode).c_str(),
+              mPointerGesture.currentGestureIdBits.value,
+              ftl::enum_string(mPointerGesture.lastGestureMode).c_str(),
+              mPointerGesture.lastGestureIdBits.value);
         for (BitSet32 idBits = mPointerGesture.currentGestureIdBits; !idBits.isEmpty();) {
             uint32_t id = idBits.clearFirstMarkedBit();
             uint32_t index = mPointerGesture.currentGestureIdToIndex[id];
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 4b39e40..9b7fe93 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -16,17 +16,38 @@
 
 #pragma once
 
+#include <array>
+#include <climits>
+#include <limits>
+#include <list>
+#include <memory>
 #include <optional>
 #include <string>
+#include <utility>
+#include <vector>
 
 #include <stdint.h>
+#include <gui/constants.h>
+#include <input/DisplayViewport.h>
+#include <input/Input.h>
+#include <input/InputDevice.h>
+#include <input/VelocityControl.h>
+#include <input/VelocityTracker.h>
+#include <ui/Rect.h>
 #include <ui/Rotation.h>
+#include <ui/Size.h>
+#include <ui/Transform.h>
+#include <utils/BitSet.h>
+#include <utils/Timers.h>
 
 #include "CursorButtonAccumulator.h"
 #include "CursorScrollAccumulator.h"
 #include "EventHub.h"
 #include "InputMapper.h"
 #include "InputReaderBase.h"
+#include "NotifyArgs.h"
+#include "PointerControllerInterface.h"
+#include "StylusState.h"
 #include "TouchButtonAccumulator.h"
 
 namespace android {
@@ -195,7 +216,6 @@
     enum class DeviceMode {
         DISABLED,   // input is disabled
         DIRECT,     // direct mapping (touchscreen)
-        UNSCALED,   // unscaled mapping (e.g. captured touchpad)
         NAVIGATION, // unscaled mapping with assist gesture (touch navigation)
         POINTER,    // pointer mapping (e.g. uncaptured touchpad, drawing tablet)
 
@@ -569,6 +589,8 @@
 
             // Waiting for quiet time to end before starting the next gesture.
             QUIET,
+
+            ftl_last = QUIET,
         };
 
         // When a gesture is sent to an unfocused window, return true if it can bring that window
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index 721cdfd..f558ba1 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -185,6 +185,7 @@
     static AStatsManager_PullAtomCallbackReturn pullAtomCallback(int32_t atomTag,
                                                                  AStatsEventList* outEventList,
                                                                  void* cookie) {
+        ALOGI("Received pull request for touchpad usage atom");
         LOG_ALWAYS_FATAL_IF(atomTag != android::util::TOUCHPAD_USAGE);
         MetricsAccumulator& accumulator = MetricsAccumulator::getInstance();
         accumulator.produceAtomsAndReset(*outEventList);
@@ -192,6 +193,7 @@
     }
 
     void produceAtomsAndReset(AStatsEventList& outEventList) {
+        ALOGI("Acquiring lock for touchpad usage metrics...");
         std::scoped_lock lock(mLock);
         produceAtomsLocked(outEventList);
         resetCountersLocked();
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index 764bb56..39a88e5 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -420,8 +420,8 @@
 
                     std::list<NotifyArgs> out;
                     mDownTime = when;
+                    mCurrentClassification = MotionClassification::TWO_FINGER_SWIPE;
                     out += exitHover(when, readTime, xCursorPosition, yCursorPosition);
-                    // TODO(b/281106755): add a MotionClassification value for fling stops.
                     out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
                                                  /*actionButton=*/0, /*buttonState=*/0,
                                                  /*pointerCount=*/1, &coords, xCursorPosition,
@@ -431,6 +431,7 @@
                                                  /*pointerCount=*/1, &coords, xCursorPosition,
                                                  yCursorPosition));
                     out += enterHover(when, readTime, xCursorPosition, yCursorPosition);
+                    mCurrentClassification = MotionClassification::NONE;
                     return out;
                 } else {
                     // Use the tap down state of a fling gesture as an indicator that a contact
diff --git a/services/inputflinger/rust/input_filter.rs b/services/inputflinger/rust/input_filter.rs
index a544fa3..6df339e 100644
--- a/services/inputflinger/rust/input_filter.rs
+++ b/services/inputflinger/rust/input_filter.rs
@@ -396,14 +396,16 @@
         IInputThread::{BnInputThread, IInputThread, IInputThreadCallback::IInputThreadCallback},
         KeyEvent::KeyEvent,
     };
-    use std::sync::{Arc, RwLock, RwLockWriteGuard};
+    use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
+    use std::sync::{atomic::AtomicBool, atomic::Ordering, Arc, RwLock, RwLockWriteGuard};
+    use std::time::Duration;
 
     #[derive(Default)]
     struct TestCallbacksInner {
         last_modifier_state: u32,
         last_locked_modifier_state: u32,
         last_event: Option<KeyEvent>,
-        test_thread: Option<TestThread>,
+        test_thread: Option<FakeCppThread>,
     }
 
     #[derive(Default, Clone)]
@@ -438,13 +440,9 @@
             self.0.read().unwrap().last_locked_modifier_state
         }
 
-        pub fn is_thread_created(&self) -> bool {
-            self.0.read().unwrap().test_thread.is_some()
-        }
-
-        pub fn is_thread_finished(&self) -> bool {
+        pub fn is_thread_running(&self) -> bool {
             if let Some(test_thread) = &self.0.read().unwrap().test_thread {
-                return test_thread.is_finish_called();
+                return test_thread.is_running();
             }
             false
         }
@@ -468,41 +466,101 @@
 
         fn createInputFilterThread(
             &self,
-            _callback: &Strong<dyn IInputThreadCallback>,
+            callback: &Strong<dyn IInputThreadCallback>,
         ) -> std::result::Result<Strong<dyn IInputThread>, binder::Status> {
-            let test_thread = TestThread::new();
+            let test_thread = FakeCppThread::new(callback.clone());
+            test_thread.start_looper();
             self.inner().test_thread = Some(test_thread.clone());
             Result::Ok(BnInputThread::new_binder(test_thread, BinderFeatures::default()))
         }
     }
 
     #[derive(Default)]
-    struct TestThreadInner {
-        is_finish_called: bool,
+    struct FakeCppThreadInner {
+        join_handle: Option<std::thread::JoinHandle<()>>,
     }
 
-    #[derive(Default, Clone)]
-    struct TestThread(Arc<RwLock<TestThreadInner>>);
+    #[derive(Clone)]
+    struct FakeCppThread {
+        callback: Arc<RwLock<Strong<dyn IInputThreadCallback>>>,
+        inner: Arc<RwLock<FakeCppThreadInner>>,
+        exit_flag: Arc<AtomicBool>,
+    }
 
-    impl Interface for TestThread {}
+    impl Interface for FakeCppThread {}
 
-    impl TestThread {
-        pub fn new() -> Self {
-            Default::default()
+    impl FakeCppThread {
+        pub fn new(callback: Strong<dyn IInputThreadCallback>) -> Self {
+            let thread = Self {
+                callback: Arc::new(RwLock::new(callback)),
+                inner: Arc::new(RwLock::new(FakeCppThreadInner { join_handle: None })),
+                exit_flag: Arc::new(AtomicBool::new(true)),
+            };
+            thread.create_looper();
+            thread
         }
 
-        fn inner(&self) -> RwLockWriteGuard<'_, TestThreadInner> {
-            self.0.write().unwrap()
+        fn inner(&self) -> RwLockWriteGuard<'_, FakeCppThreadInner> {
+            self.inner.write().unwrap()
         }
 
-        pub fn is_finish_called(&self) -> bool {
-            self.0.read().unwrap().is_finish_called
+        fn create_looper(&self) {
+            let clone = self.clone();
+            let join_handle = std::thread::Builder::new()
+                .name("fake_cpp_thread".to_string())
+                .spawn(move || loop {
+                    if !clone.exit_flag.load(Ordering::Relaxed) {
+                        clone.loop_once();
+                    }
+                })
+                .unwrap();
+            self.inner().join_handle = Some(join_handle);
+            // Sleep until the looper thread starts
+            std::thread::sleep(Duration::from_millis(10));
+        }
+
+        pub fn start_looper(&self) {
+            self.exit_flag.store(false, Ordering::Relaxed);
+        }
+
+        pub fn stop_looper(&self) {
+            self.exit_flag.store(true, Ordering::Relaxed);
+            if let Some(join_handle) = &self.inner.read().unwrap().join_handle {
+                join_handle.thread().unpark();
+            }
+        }
+
+        pub fn is_running(&self) -> bool {
+            !self.exit_flag.load(Ordering::Relaxed)
+        }
+
+        fn loop_once(&self) {
+            let _ = self.callback.read().unwrap().loopOnce();
         }
     }
 
-    impl IInputThread for TestThread {
+    impl IInputThread for FakeCppThread {
         fn finish(&self) -> binder::Result<()> {
-            self.inner().is_finish_called = true;
+            self.stop_looper();
+            Result::Ok(())
+        }
+
+        fn wake(&self) -> binder::Result<()> {
+            if let Some(join_handle) = &self.inner.read().unwrap().join_handle {
+                join_handle.thread().unpark();
+            }
+            Result::Ok(())
+        }
+
+        fn sleepUntil(&self, wake_up_time: i64) -> binder::Result<()> {
+            let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+            if wake_up_time == i64::MAX {
+                std::thread::park();
+            } else {
+                let duration_now = Duration::from_nanos(now as u64);
+                let duration_wake_up = Duration::from_nanos(wake_up_time as u64);
+                std::thread::park_timeout(duration_wake_up - duration_now);
+            }
             Result::Ok(())
         }
     }
diff --git a/services/inputflinger/rust/input_filter_thread.rs b/services/inputflinger/rust/input_filter_thread.rs
index 2d503ae..34f9b25 100644
--- a/services/inputflinger/rust/input_filter_thread.rs
+++ b/services/inputflinger/rust/input_filter_thread.rs
@@ -33,8 +33,6 @@
 use log::{debug, error};
 use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
 use std::sync::{Arc, RwLock, RwLockWriteGuard};
-use std::time::Duration;
-use std::{thread, thread::Thread};
 
 /// Interface to receive callback from Input filter thread
 pub trait ThreadCallback {
@@ -54,15 +52,18 @@
     thread_creator: InputFilterThreadCreator,
     thread_callback_handler: ThreadCallbackHandler,
     inner: Arc<RwLock<InputFilterThreadInner>>,
+    looper: Arc<RwLock<Looper>>,
 }
 
 struct InputFilterThreadInner {
-    cpp_thread: Option<Strong<dyn IInputThread>>,
-    looper: Option<Thread>,
     next_timeout: i64,
     is_finishing: bool,
 }
 
+struct Looper {
+    cpp_thread: Option<Strong<dyn IInputThread>>,
+}
+
 impl InputFilterThread {
     /// Create a new InputFilterThread instance.
     /// NOTE: This will create a new thread. Clone the existing instance to reuse the same thread.
@@ -71,11 +72,10 @@
             thread_creator,
             thread_callback_handler: ThreadCallbackHandler::new(),
             inner: Arc::new(RwLock::new(InputFilterThreadInner {
-                cpp_thread: None,
-                looper: None,
                 next_timeout: i64::MAX,
                 is_finishing: false,
             })),
+            looper: Arc::new(RwLock::new(Looper { cpp_thread: None })),
         }
     }
 
@@ -83,12 +83,17 @@
     /// time on the input filter thread.
     /// {@see ThreadCallback.notify_timeout_expired(...)}
     pub fn request_timeout_at_time(&self, when_nanos: i64) {
-        let filter_thread = &mut self.filter_thread();
-        if when_nanos < filter_thread.next_timeout {
-            filter_thread.next_timeout = when_nanos;
-            if let Some(looper) = &filter_thread.looper {
-                looper.unpark();
+        let mut need_wake = false;
+        {
+            // acquire filter lock
+            let filter_thread = &mut self.filter_thread();
+            if when_nanos < filter_thread.next_timeout {
+                filter_thread.next_timeout = when_nanos;
+                need_wake = true;
             }
+        } // release filter lock
+        if need_wake {
+            self.wake();
         }
     }
 
@@ -120,29 +125,36 @@
 
     fn start(&self) {
         debug!("InputFilterThread: start thread");
-        let filter_thread = &mut self.filter_thread();
-        if filter_thread.cpp_thread.is_none() {
-            filter_thread.cpp_thread = Some(self.thread_creator.create(
-                &BnInputThreadCallback::new_binder(self.clone(), BinderFeatures::default()),
-            ));
-            filter_thread.looper = None;
-            filter_thread.is_finishing = false;
-        }
+        {
+            // acquire looper lock
+            let looper = &mut self.looper();
+            if looper.cpp_thread.is_none() {
+                looper.cpp_thread = Some(self.thread_creator.create(
+                    &BnInputThreadCallback::new_binder(self.clone(), BinderFeatures::default()),
+                ));
+            }
+        } // release looper lock
+        self.set_finishing(false);
     }
 
     fn stop(&self) {
         debug!("InputFilterThread: stop thread");
+        self.set_finishing(true);
+        self.wake();
+        {
+            // acquire looper lock
+            let looper = &mut self.looper();
+            if let Some(cpp_thread) = &looper.cpp_thread {
+                let _ = cpp_thread.finish();
+            }
+            // Clear all references
+            looper.cpp_thread = None;
+        } // release looper lock
+    }
+
+    fn set_finishing(&self, is_finishing: bool) {
         let filter_thread = &mut self.filter_thread();
-        filter_thread.is_finishing = true;
-        if let Some(looper) = &filter_thread.looper {
-            looper.unpark();
-        }
-        if let Some(cpp_thread) = &filter_thread.cpp_thread {
-            let _ = cpp_thread.finish();
-        }
-        // Clear all references
-        filter_thread.cpp_thread = None;
-        filter_thread.looper = None;
+        filter_thread.is_finishing = is_finishing;
     }
 
     fn loop_once(&self, now: i64) {
@@ -163,25 +175,34 @@
                     wake_up_time = filter_thread.next_timeout;
                 }
             }
-            if filter_thread.looper.is_none() {
-                filter_thread.looper = Some(std::thread::current());
-            }
         } // release thread lock
         if timeout_expired {
             self.thread_callback_handler.notify_timeout_expired(now);
         }
-        if wake_up_time == i64::MAX {
-            thread::park();
-        } else {
-            let duration_now = Duration::from_nanos(now as u64);
-            let duration_wake_up = Duration::from_nanos(wake_up_time as u64);
-            thread::park_timeout(duration_wake_up - duration_now);
-        }
+        self.sleep_until(wake_up_time);
     }
 
     fn filter_thread(&self) -> RwLockWriteGuard<'_, InputFilterThreadInner> {
         self.inner.write().unwrap()
     }
+
+    fn sleep_until(&self, when_nanos: i64) {
+        let looper = self.looper.read().unwrap();
+        if let Some(cpp_thread) = &looper.cpp_thread {
+            let _ = cpp_thread.sleepUntil(when_nanos);
+        }
+    }
+
+    fn wake(&self) {
+        let looper = self.looper.read().unwrap();
+        if let Some(cpp_thread) = &looper.cpp_thread {
+            let _ = cpp_thread.wake();
+        }
+    }
+
+    fn looper(&self) -> RwLockWriteGuard<'_, Looper> {
+        self.looper.write().unwrap()
+    }
 }
 
 impl Interface for InputFilterThread {}
@@ -252,165 +273,64 @@
 
 #[cfg(test)]
 mod tests {
-    use crate::input_filter::test_callbacks::TestCallbacks;
-    use crate::input_filter_thread::{
-        test_thread::TestThread, test_thread_callback::TestThreadCallback,
-    };
+    use crate::input_filter::{test_callbacks::TestCallbacks, InputFilterThreadCreator};
+    use crate::input_filter_thread::{test_thread_callback::TestThreadCallback, InputFilterThread};
+    use binder::Strong;
+    use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
+    use std::sync::{Arc, RwLock};
+    use std::time::Duration;
 
     #[test]
     fn test_register_callback_creates_cpp_thread() {
         let test_callbacks = TestCallbacks::new();
-        let test_thread = TestThread::new(test_callbacks.clone());
+        let test_thread = get_thread(test_callbacks.clone());
         let test_thread_callback = TestThreadCallback::new();
-        test_thread.register_thread_callback(test_thread_callback);
-        assert!(test_callbacks.is_thread_created());
+        test_thread.register_thread_callback(Box::new(test_thread_callback));
+        assert!(test_callbacks.is_thread_running());
     }
 
     #[test]
     fn test_unregister_callback_finishes_cpp_thread() {
         let test_callbacks = TestCallbacks::new();
-        let test_thread = TestThread::new(test_callbacks.clone());
+        let test_thread = get_thread(test_callbacks.clone());
         let test_thread_callback = TestThreadCallback::new();
-        test_thread.register_thread_callback(test_thread_callback.clone());
-        test_thread.unregister_thread_callback(test_thread_callback);
-        assert!(test_callbacks.is_thread_finished());
+        test_thread.register_thread_callback(Box::new(test_thread_callback.clone()));
+        test_thread.unregister_thread_callback(Box::new(test_thread_callback));
+        assert!(!test_callbacks.is_thread_running());
     }
 
     #[test]
     fn test_notify_timeout_called_after_timeout_expired() {
         let test_callbacks = TestCallbacks::new();
-        let test_thread = TestThread::new(test_callbacks.clone());
+        let test_thread = get_thread(test_callbacks.clone());
         let test_thread_callback = TestThreadCallback::new();
-        test_thread.register_thread_callback(test_thread_callback.clone());
-        test_thread.start_looper();
+        test_thread.register_thread_callback(Box::new(test_thread_callback.clone()));
 
-        test_thread.request_timeout_at_time(500);
-        test_thread.dispatch_next();
+        let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_milliseconds();
+        test_thread.request_timeout_at_time((now + 10) * 1000000);
 
-        test_thread.move_time_forward(500);
-
-        test_thread.stop_looper();
+        std::thread::sleep(Duration::from_millis(100));
         assert!(test_thread_callback.is_notify_timeout_called());
     }
 
     #[test]
     fn test_notify_timeout_not_called_before_timeout_expired() {
         let test_callbacks = TestCallbacks::new();
-        let test_thread = TestThread::new(test_callbacks.clone());
+        let test_thread = get_thread(test_callbacks.clone());
         let test_thread_callback = TestThreadCallback::new();
-        test_thread.register_thread_callback(test_thread_callback.clone());
-        test_thread.start_looper();
+        test_thread.register_thread_callback(Box::new(test_thread_callback.clone()));
 
-        test_thread.request_timeout_at_time(500);
-        test_thread.dispatch_next();
+        let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_milliseconds();
+        test_thread.request_timeout_at_time((now + 100) * 1000000);
 
-        test_thread.move_time_forward(100);
-
-        test_thread.stop_looper();
+        std::thread::sleep(Duration::from_millis(10));
         assert!(!test_thread_callback.is_notify_timeout_called());
     }
-}
 
-#[cfg(test)]
-pub mod test_thread {
-
-    use crate::input_filter::{test_callbacks::TestCallbacks, InputFilterThreadCreator};
-    use crate::input_filter_thread::{test_thread_callback::TestThreadCallback, InputFilterThread};
-    use binder::Strong;
-    use std::sync::{
-        atomic::AtomicBool, atomic::AtomicI64, atomic::Ordering, Arc, RwLock, RwLockWriteGuard,
-    };
-    use std::time::Duration;
-
-    #[derive(Clone)]
-    pub struct TestThread {
-        input_thread: InputFilterThread,
-        inner: Arc<RwLock<TestThreadInner>>,
-        exit_flag: Arc<AtomicBool>,
-        now: Arc<AtomicI64>,
-    }
-
-    struct TestThreadInner {
-        join_handle: Option<std::thread::JoinHandle<()>>,
-    }
-
-    impl TestThread {
-        pub fn new(callbacks: TestCallbacks) -> TestThread {
-            Self {
-                input_thread: InputFilterThread::new(InputFilterThreadCreator::new(Arc::new(
-                    RwLock::new(Strong::new(Box::new(callbacks))),
-                ))),
-                inner: Arc::new(RwLock::new(TestThreadInner { join_handle: None })),
-                exit_flag: Arc::new(AtomicBool::new(false)),
-                now: Arc::new(AtomicI64::new(0)),
-            }
-        }
-
-        fn inner(&self) -> RwLockWriteGuard<'_, TestThreadInner> {
-            self.inner.write().unwrap()
-        }
-
-        pub fn get_input_thread(&self) -> InputFilterThread {
-            self.input_thread.clone()
-        }
-
-        pub fn register_thread_callback(&self, thread_callback: TestThreadCallback) {
-            self.input_thread.register_thread_callback(Box::new(thread_callback));
-        }
-
-        pub fn unregister_thread_callback(&self, thread_callback: TestThreadCallback) {
-            self.input_thread.unregister_thread_callback(Box::new(thread_callback));
-        }
-
-        pub fn start_looper(&self) {
-            self.exit_flag.store(false, Ordering::Relaxed);
-            let clone = self.clone();
-            let join_handle = std::thread::Builder::new()
-                .name("test_thread".to_string())
-                .spawn(move || {
-                    while !clone.exit_flag.load(Ordering::Relaxed) {
-                        clone.loop_once();
-                    }
-                })
-                .unwrap();
-            self.inner().join_handle = Some(join_handle);
-            // Sleep until the looper thread starts
-            std::thread::sleep(Duration::from_millis(10));
-        }
-
-        pub fn stop_looper(&self) {
-            self.exit_flag.store(true, Ordering::Relaxed);
-            {
-                let mut inner = self.inner();
-                if let Some(join_handle) = &inner.join_handle {
-                    join_handle.thread().unpark();
-                }
-                inner.join_handle.take().map(std::thread::JoinHandle::join);
-                inner.join_handle = None;
-            }
-            self.exit_flag.store(false, Ordering::Relaxed);
-        }
-
-        pub fn move_time_forward(&self, value: i64) {
-            let _ = self.now.fetch_add(value, Ordering::Relaxed);
-            self.dispatch_next();
-        }
-
-        pub fn dispatch_next(&self) {
-            if let Some(join_handle) = &self.inner().join_handle {
-                join_handle.thread().unpark();
-            }
-            // Sleep until the looper thread runs a loop
-            std::thread::sleep(Duration::from_millis(10));
-        }
-
-        fn loop_once(&self) {
-            self.input_thread.loop_once(self.now.load(Ordering::Relaxed));
-        }
-
-        pub fn request_timeout_at_time(&self, when_nanos: i64) {
-            self.input_thread.request_timeout_at_time(when_nanos);
-        }
+    fn get_thread(callbacks: TestCallbacks) -> InputFilterThread {
+        InputFilterThread::new(InputFilterThreadCreator::new(Arc::new(RwLock::new(Strong::new(
+            Box::new(callbacks),
+        )))))
     }
 }
 
diff --git a/services/inputflinger/rust/slow_keys_filter.rs b/services/inputflinger/rust/slow_keys_filter.rs
index 09fbf40..0f18a2f 100644
--- a/services/inputflinger/rust/slow_keys_filter.rs
+++ b/services/inputflinger/rust/slow_keys_filter.rs
@@ -207,13 +207,19 @@
 
 #[cfg(test)]
 mod tests {
-    use crate::input_filter::{test_callbacks::TestCallbacks, test_filter::TestFilter, Filter};
-    use crate::input_filter_thread::test_thread::TestThread;
+    use crate::input_filter::{
+        test_callbacks::TestCallbacks, test_filter::TestFilter, Filter, InputFilterThreadCreator,
+    };
+    use crate::input_filter_thread::InputFilterThread;
     use crate::slow_keys_filter::{SlowKeysFilter, POLICY_FLAG_DISABLE_KEY_REPEAT};
     use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
+    use binder::Strong;
     use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
         DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
     };
+    use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
+    use std::sync::{Arc, RwLock};
+    use std::time::Duration;
 
     static BASE_KEY_EVENT: KeyEvent = KeyEvent {
         id: 1,
@@ -231,18 +237,19 @@
         metaState: 0,
     };
 
+    static SLOW_KEYS_THRESHOLD_NS: i64 = 100 * 1000000; // 100 ms
+
     #[test]
     fn test_is_notify_key_for_internal_keyboard_not_blocked() {
         let test_callbacks = TestCallbacks::new();
-        let test_thread = TestThread::new(test_callbacks.clone());
+        let test_thread = get_thread(test_callbacks.clone());
         let next = TestFilter::new();
         let mut filter = setup_filter_with_internal_device(
             Box::new(next.clone()),
             test_thread.clone(),
-            1,   /* device_id */
-            100, /* threshold */
+            1, /* device_id */
+            SLOW_KEYS_THRESHOLD_NS,
         );
-        test_thread.start_looper();
 
         let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
         filter.notify_key(&event);
@@ -252,15 +259,14 @@
     #[test]
     fn test_is_notify_key_for_external_stylus_not_blocked() {
         let test_callbacks = TestCallbacks::new();
-        let test_thread = TestThread::new(test_callbacks.clone());
+        let test_thread = get_thread(test_callbacks.clone());
         let next = TestFilter::new();
         let mut filter = setup_filter_with_external_device(
             Box::new(next.clone()),
             test_thread.clone(),
-            1,   /* device_id */
-            100, /* threshold */
+            1, /* device_id */
+            SLOW_KEYS_THRESHOLD_NS,
         );
-        test_thread.start_looper();
 
         let event =
             KeyEvent { action: KeyEventAction::DOWN, source: Source::STYLUS, ..BASE_KEY_EVENT };
@@ -271,89 +277,115 @@
     #[test]
     fn test_notify_key_for_external_keyboard_when_key_pressed_for_threshold_time() {
         let test_callbacks = TestCallbacks::new();
-        let test_thread = TestThread::new(test_callbacks.clone());
+        let test_thread = get_thread(test_callbacks.clone());
         let next = TestFilter::new();
         let mut filter = setup_filter_with_external_device(
             Box::new(next.clone()),
             test_thread.clone(),
-            1,   /* device_id */
-            100, /* threshold */
+            1, /* device_id */
+            SLOW_KEYS_THRESHOLD_NS,
         );
-        test_thread.start_looper();
-
-        filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT });
+        let down_time = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+        filter.notify_key(&KeyEvent {
+            action: KeyEventAction::DOWN,
+            downTime: down_time,
+            eventTime: down_time,
+            ..BASE_KEY_EVENT
+        });
         assert!(next.last_event().is_none());
-        test_thread.dispatch_next();
 
-        test_thread.move_time_forward(100);
-
-        test_thread.stop_looper();
+        std::thread::sleep(Duration::from_nanos(2 * SLOW_KEYS_THRESHOLD_NS as u64));
         assert_eq!(
             next.last_event().unwrap(),
             KeyEvent {
                 action: KeyEventAction::DOWN,
-                downTime: 100,
-                eventTime: 100,
+                downTime: down_time + SLOW_KEYS_THRESHOLD_NS,
+                eventTime: down_time + SLOW_KEYS_THRESHOLD_NS,
                 policyFlags: POLICY_FLAG_DISABLE_KEY_REPEAT,
                 ..BASE_KEY_EVENT
             }
         );
+
+        let up_time = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+        filter.notify_key(&KeyEvent {
+            action: KeyEventAction::UP,
+            downTime: down_time,
+            eventTime: up_time,
+            ..BASE_KEY_EVENT
+        });
+
+        assert_eq!(
+            next.last_event().unwrap(),
+            KeyEvent {
+                action: KeyEventAction::UP,
+                downTime: down_time + SLOW_KEYS_THRESHOLD_NS,
+                eventTime: up_time,
+                ..BASE_KEY_EVENT
+            }
+        );
     }
 
     #[test]
     fn test_notify_key_for_external_keyboard_when_key_not_pressed_for_threshold_time() {
         let test_callbacks = TestCallbacks::new();
-        let test_thread = TestThread::new(test_callbacks.clone());
+        let test_thread = get_thread(test_callbacks.clone());
         let next = TestFilter::new();
         let mut filter = setup_filter_with_external_device(
             Box::new(next.clone()),
             test_thread.clone(),
-            1,   /* device_id */
-            100, /* threshold */
+            1, /* device_id */
+            SLOW_KEYS_THRESHOLD_NS,
         );
-        test_thread.start_looper();
+        let mut now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+        filter.notify_key(&KeyEvent {
+            action: KeyEventAction::DOWN,
+            downTime: now,
+            eventTime: now,
+            ..BASE_KEY_EVENT
+        });
 
-        filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT });
-        test_thread.dispatch_next();
+        std::thread::sleep(Duration::from_nanos(SLOW_KEYS_THRESHOLD_NS as u64 / 2));
 
-        test_thread.move_time_forward(10);
+        now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+        filter.notify_key(&KeyEvent {
+            action: KeyEventAction::UP,
+            downTime: now,
+            eventTime: now,
+            ..BASE_KEY_EVENT
+        });
 
-        filter.notify_key(&KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_EVENT });
-        test_thread.dispatch_next();
-
-        test_thread.stop_looper();
         assert!(next.last_event().is_none());
     }
 
     #[test]
     fn test_notify_key_for_external_keyboard_when_device_removed_before_threshold_time() {
         let test_callbacks = TestCallbacks::new();
-        let test_thread = TestThread::new(test_callbacks.clone());
+        let test_thread = get_thread(test_callbacks.clone());
         let next = TestFilter::new();
         let mut filter = setup_filter_with_external_device(
             Box::new(next.clone()),
             test_thread.clone(),
-            1,   /* device_id */
-            100, /* threshold */
+            1, /* device_id */
+            SLOW_KEYS_THRESHOLD_NS,
         );
-        test_thread.start_looper();
 
-        filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT });
-        assert!(next.last_event().is_none());
-        test_thread.dispatch_next();
+        let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+        filter.notify_key(&KeyEvent {
+            action: KeyEventAction::DOWN,
+            downTime: now,
+            eventTime: now,
+            ..BASE_KEY_EVENT
+        });
 
         filter.notify_devices_changed(&[]);
-        test_thread.dispatch_next();
+        std::thread::sleep(Duration::from_nanos(2 * SLOW_KEYS_THRESHOLD_NS as u64));
 
-        test_thread.move_time_forward(100);
-
-        test_thread.stop_looper();
         assert!(next.last_event().is_none());
     }
 
     fn setup_filter_with_external_device(
         next: Box<dyn Filter + Send + Sync>,
-        test_thread: TestThread,
+        test_thread: InputFilterThread,
         device_id: i32,
         threshold: i64,
     ) -> SlowKeysFilter {
@@ -367,7 +399,7 @@
 
     fn setup_filter_with_internal_device(
         next: Box<dyn Filter + Send + Sync>,
-        test_thread: TestThread,
+        test_thread: InputFilterThread,
         device_id: i32,
         threshold: i64,
     ) -> SlowKeysFilter {
@@ -381,12 +413,18 @@
 
     fn setup_filter_with_devices(
         next: Box<dyn Filter + Send + Sync>,
-        test_thread: TestThread,
+        test_thread: InputFilterThread,
         devices: &[DeviceInfo],
         threshold: i64,
     ) -> SlowKeysFilter {
-        let mut filter = SlowKeysFilter::new(next, threshold, test_thread.get_input_thread());
+        let mut filter = SlowKeysFilter::new(next, threshold, test_thread);
         filter.notify_devices_changed(devices);
         filter
     }
+
+    fn get_thread(callbacks: TestCallbacks) -> InputFilterThread {
+        InputFilterThread::new(InputFilterThreadCreator::new(Arc::new(RwLock::new(Strong::new(
+            Box::new(callbacks),
+        )))))
+    }
 }
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 6ae9790..9b5db23 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -67,6 +67,8 @@
         "InputProcessorConverter_test.cpp",
         "InputDispatcher_test.cpp",
         "InputReader_test.cpp",
+        "InputTraceSession.cpp",
+        "InputTracingTest.cpp",
         "InstrumentedInputReader.cpp",
         "LatencyTracker_test.cpp",
         "MultiTouchMotionAccumulator_test.cpp",
diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
index e231bcc..36491ab 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
@@ -466,8 +466,4 @@
     mFilteredEvent = nullptr;
 }
 
-gui::Uid FakeInputDispatcherPolicy::getPackageUid(std::string) {
-    return gui::Uid::INVALID;
-}
-
 } // namespace android
diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
index d83924f..25d3d3c 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
@@ -196,7 +196,6 @@
     void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
     void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
                                  const std::set<gui::Uid>& uids) override;
-    gui::Uid getPackageUid(std::string) override;
 
     void assertFilterInputEventWasCalledInternal(
             const std::function<void(const InputEvent&)>& verify);
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index 8f593b5..e9118a9 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -139,7 +139,7 @@
 
 void FakeInputReaderPolicy::addInputUniqueIdAssociation(const std::string& inputUniqueId,
                                                         const std::string& displayUniqueId) {
-    mConfig.uniqueIdAssociations.insert({inputUniqueId, displayUniqueId});
+    mConfig.uniqueIdAssociationsByPort.insert({inputUniqueId, displayUniqueId});
 }
 
 void FakeInputReaderPolicy::addKeyboardLayoutAssociation(const std::string& inputUniqueId,
diff --git a/services/inputflinger/tests/FakeWindows.h b/services/inputflinger/tests/FakeWindows.h
index c0c8975..26c2b4b 100644
--- a/services/inputflinger/tests/FakeWindows.h
+++ b/services/inputflinger/tests/FakeWindows.h
@@ -157,6 +157,16 @@
 
     inline void setSpy(bool spy) { mInfo.setInputConfig(InputConfig::SPY, spy); }
 
+    inline void setSecure(bool secure) {
+        if (secure) {
+            mInfo.layoutParamsFlags |= gui::WindowInfo::Flag::SECURE;
+        } else {
+            using namespace ftl::flag_operators;
+            mInfo.layoutParamsFlags &= ~gui::WindowInfo::Flag::SECURE;
+        }
+        mInfo.setInputConfig(InputConfig::SENSITIVE_FOR_TRACING, secure);
+    }
+
     inline void setInterceptsStylus(bool interceptsStylus) {
         mInfo.setInputConfig(InputConfig::INTERCEPTS_STYLUS, interceptsStylus);
     }
@@ -229,10 +239,14 @@
 
     std::unique_ptr<KeyEvent> consumeKey(bool handled = true);
 
-    inline void consumeKeyEvent(const ::testing::Matcher<KeyEvent>& matcher) {
+    inline std::unique_ptr<KeyEvent> consumeKeyEvent(const ::testing::Matcher<KeyEvent>& matcher) {
         std::unique_ptr<KeyEvent> keyEvent = consumeKey();
-        ASSERT_NE(nullptr, keyEvent);
-        ASSERT_THAT(*keyEvent, matcher);
+        EXPECT_NE(nullptr, keyEvent);
+        if (!keyEvent) {
+            return nullptr;
+        }
+        EXPECT_THAT(*keyEvent, matcher);
+        return keyEvent;
     }
 
     inline void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index 337b52b..6a009b2 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -1192,12 +1192,12 @@
                             VariantWith<NotifyMotionArgs>(
                                     WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)),
                             VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithMotionClassification(MotionClassification::NONE)))));
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER))));
     ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(POINTER_X, POINTER_Y),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT),
+                              WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE)))));
 }
 
 TEST_F(GestureConverterTest, Tap) {
@@ -2613,12 +2613,12 @@
                             VariantWith<NotifyMotionArgs>(
                                     WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)),
                             VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithMotionClassification(MotionClassification::NONE)))));
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER))));
     ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(0, 0),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ADISPLAY_ID_DEFAULT),
+                              WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE)))));
 }
 
 TEST_F(GestureConverterTestWithChoreographer, Tap) {
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 62a9235..bc173b1 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -4800,6 +4800,96 @@
 }
 
 /**
+ * Three windows:
+ * - Left window
+ * - Right window
+ * - Outside window(watch for ACTION_OUTSIDE events)
+ * The windows "left" and "outside" share the same owner, the window "right" has a different owner,
+ * In order to allow the outside window can receive the ACTION_OUTSIDE events, the outside window is
+ * positioned above the "left" and "right" windows, and it doesn't overlap with them.
+ *
+ * First, device A report a down event landed in the right window, the outside window can receive
+ * an ACTION_OUTSIDE event that with zeroed coordinates, the device B report a down event landed
+ * in the left window, the outside window can receive an ACTION_OUTSIDE event the with valid
+ * coordinates, after these, device A and device B continue report MOVE event, the right and left
+ * window can receive it, but outside window event can't receive it.
+ */
+TEST_F(InputDispatcherTest, ActionOutsideForOwnedWindowHasValidCoordinatesWhenMultiDevice) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
+                                       ADISPLAY_ID_DEFAULT);
+    leftWindow->setFrame(Rect{0, 0, 100, 100});
+    leftWindow->setOwnerInfo(gui::Pid{1}, gui::Uid{101});
+
+    sp<FakeWindowHandle> outsideWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Outside Window",
+                                       ADISPLAY_ID_DEFAULT);
+    outsideWindow->setFrame(Rect{100, 100, 200, 200});
+    outsideWindow->setOwnerInfo(gui::Pid{1}, gui::Uid{101});
+    outsideWindow->setWatchOutsideTouch(true);
+
+    std::shared_ptr<FakeApplicationHandle> anotherApplication =
+            std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> rightWindow =
+            sp<FakeWindowHandle>::make(anotherApplication, mDispatcher, "Right Window",
+                                       ADISPLAY_ID_DEFAULT);
+    rightWindow->setFrame(Rect{100, 0, 200, 100});
+    rightWindow->setOwnerInfo(gui::Pid{2}, gui::Uid{202});
+
+    // OutsideWindow must be above left window and right window to receive ACTION_OUTSIDE events
+    // when left window or right window is tapped
+    mDispatcher->onWindowInfosChanged(
+            {{*outsideWindow->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()},
+             {},
+             0,
+             0});
+
+    const DeviceId deviceA = 9;
+    const DeviceId deviceB = 3;
+
+    // Tap on right window use device A
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftWindow->assertNoEvents();
+    rightWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+    // Right window is belonged to another owner, so outsideWindow should receive ACTION_OUTSIDE
+    // with zeroed coords.
+    outsideWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_OUTSIDE), WithDeviceId(deviceA), WithCoords(0, 0)));
+
+    // Tap on left window use device B
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+    rightWindow->assertNoEvents();
+    // Because new gesture down on the left window that has the same owner with outside Window, the
+    // outside Window should receive the ACTION_OUTSIDE with coords.
+    outsideWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_OUTSIDE), WithDeviceId(deviceB), WithCoords(-50, -50)));
+
+    // Ensure that windows that can only accept outside do not receive remaining gestures
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(51))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftWindow->assertNoEvents();
+    rightWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceA)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(51).y(51))
+                                      .deviceId(deviceB)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceB)));
+    rightWindow->assertNoEvents();
+    outsideWindow->assertNoEvents();
+}
+
+/**
  * 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.
@@ -7238,6 +7328,154 @@
     appearingWindow->assertNoEvents();
 }
 
+/**
+ * Three windows:
+ * - left window, which has FLAG_SLIPPERY, so it supports slippery exit
+ * - right window
+ * - spy window
+ * The three windows do not overlap.
+ *
+ * We have two devices reporting events:
+ * - Device A reports ACTION_DOWN, which lands in the left window
+ * - Device B reports ACTION_DOWN, which lands in the spy window.
+ * - Now, device B reports ACTION_MOVE events which move to the right window.
+ *
+ * The right window should not receive any events because the spy window is not a foreground window,
+ * and also it does not support slippery touches.
+ */
+TEST_F(InputDispatcherTest, MultiDeviceSpyWindowSlipTest) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left window",
+                                       ADISPLAY_ID_DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 100, 100));
+    leftWindow->setSlippery(true);
+
+    sp<FakeWindowHandle> rightWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right window",
+                                       ADISPLAY_ID_DEFAULT);
+    rightWindow->setFrame(Rect(100, 0, 200, 100));
+
+    sp<FakeWindowHandle> spyWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy Window", ADISPLAY_ID_DEFAULT);
+    spyWindow->setFrame(Rect(200, 0, 300, 100));
+    spyWindow->setSpy(true);
+    spyWindow->setTrustedOverlay(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *rightWindow->getInfo(), *spyWindow->getInfo()}, {}, 0, 0});
+
+    const DeviceId deviceA = 9;
+    const DeviceId deviceB = 3;
+
+    // Tap on left window with device A
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+
+    // Tap on spy window with device B
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(250).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+
+    // Move to right window with device B. Touches should not slip to the right window, because spy
+    // window is not a foreground window, and it does not have FLAG_SLIPPERY
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    leftWindow->assertNoEvents();
+    rightWindow->assertNoEvents();
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceB)));
+}
+
+/**
+ * Three windows arranged horizontally and without any overlap.
+ * The left and right windows have FLAG_SLIPPERY. The middle window does not have any special flags.
+ *
+ * We have two devices reporting events:
+ * - Device A reports ACTION_DOWN which lands in the left window
+ * - Device B reports ACTION_DOWN which lands in the right window
+ * - Device B reports ACTION_MOVE that shifts to the middle window.
+ * This should cause touches for Device B to slip from the right window to the middle window.
+ * The right window should receive ACTION_CANCEL for device B and the
+ * middle window should receive down event for Device B.
+ * If device B reports more ACTION_MOVE events, the middle window should receive remaining events.
+ */
+TEST_F(InputDispatcherTest, MultiDeviceSlipperyWindowTest) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left window",
+                                       ADISPLAY_ID_DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 100, 100));
+    leftWindow->setSlippery(true);
+
+    sp<FakeWindowHandle> middleWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "middle window",
+                                       ADISPLAY_ID_DEFAULT);
+    middleWindow->setFrame(Rect(100, 0, 200, 100));
+
+    sp<FakeWindowHandle> rightWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right window",
+                                       ADISPLAY_ID_DEFAULT);
+    rightWindow->setFrame(Rect(200, 0, 300, 100));
+    rightWindow->setSlippery(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *middleWindow->getInfo(), *rightWindow->getInfo()},
+             {},
+             0,
+             0});
+
+    const DeviceId deviceA = 9;
+    const DeviceId deviceB = 3;
+
+    // Tap on left window with device A
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+
+    // Tap on right window with device B
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(250).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    rightWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+
+    // Move to middle window with device B. Touches should slip to middle window, because right
+    // window is a foreground window that's associated with device B and has FLAG_SLIPPERY.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    rightWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB)));
+    middleWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+
+    // Move to middle window with device A. Touches should slip to middle window, because left
+    // window is a foreground window that's associated with device A and has FLAG_SLIPPERY.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceA)));
+    middleWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+
+    // Ensure that middle window can receive the remaining move events.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(51))
+                                      .deviceId(deviceB)
+                                      .build());
+    leftWindow->assertNoEvents();
+    middleWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceB)));
+    rightWindow->assertNoEvents();
+}
+
 TEST_F(InputDispatcherTest, NotifiesDeviceInteractionsWithMotions) {
     using Uid = gui::Uid;
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
@@ -7391,6 +7629,60 @@
     spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithEventId(notifyArgs.id)));
 }
 
+/**
+ * When a device reports a DOWN event, which lands in a window that supports splits, and then the
+ * device then reports a POINTER_DOWN, which lands in the location of a non-existing window, then
+ * the previous window should receive this event and not be dropped.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, SingleDevicePointerDownEventRetentionWithoutWindowTarget) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 100, 100));
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .build());
+
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(200).y(200))
+                                      .build());
+
+    window->consumeMotionEvent(AllOf(WithMotionAction(POINTER_1_DOWN)));
+}
+
+/**
+ * When deviceA reports a DOWN event, which lands in a window that supports splits, and then deviceB
+ * also reports a DOWN event, which lands in the location of a non-existing window, then the
+ * previous window should receive deviceB's event and it should be dropped.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, SecondDeviceDownEventDroppedWithoutWindowTarget) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 100, 100));
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    const DeviceId deviceA = 9;
+    const DeviceId deviceB = 3;
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(200))
+                                      .deviceId(deviceB)
+                                      .build());
+    window->assertNoEvents();
+}
+
 class InputDispatcherFallbackKeyTest : public InputDispatcherTest {
 protected:
     std::shared_ptr<FakeApplicationHandle> mApp;
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index cfe239e..e5d929d 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -2907,7 +2907,7 @@
     const auto initialGeneration = mDevice->getGeneration();
     unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                                  InputReaderConfiguration::Change::DISPLAY_INFO);
-    ASSERT_EQ(DISPLAY_UNIQUE_ID, mDevice->getAssociatedDisplayUniqueId());
+    ASSERT_EQ(DISPLAY_UNIQUE_ID, mDevice->getAssociatedDisplayUniqueIdByPort());
     ASSERT_GT(mDevice->getGeneration(), initialGeneration);
     ASSERT_EQ(mDevice->getDeviceInfo().getAssociatedDisplayId(), SECONDARY_DISPLAY_ID);
 }
@@ -9046,7 +9046,7 @@
 
     // Test all 4 orientations
     for (ui::Rotation orientation : ftl::enum_range<ui::Rotation>()) {
-        SCOPED_TRACE("Orientation " + StringPrintf("%i", orientation));
+        SCOPED_TRACE(StringPrintf("Orientation %s", ftl::enum_string(orientation).c_str()));
         clearViewports();
         prepareDisplay(orientation);
         std::vector<TouchVideoFrame> frames{frame};
@@ -9071,7 +9071,7 @@
 
     // Test all 4 orientations
     for (ui::Rotation orientation : ftl::enum_range<ui::Rotation>()) {
-        SCOPED_TRACE("Orientation " + StringPrintf("%i", orientation));
+        SCOPED_TRACE(StringPrintf("Orientation %s", ftl::enum_string(orientation).c_str()));
         clearViewports();
         prepareDisplay(orientation);
         std::vector<TouchVideoFrame> frames{frame};
@@ -9790,163 +9790,13 @@
     ASSERT_EQ(SECONDARY_DISPLAY_ID, motionArgs.displayId);
 }
 
-TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) {
-    // we need a pointer controller for mouse mode of touchpad (start pointer at 0,0)
+TEST_F(MultiTouchInputMapperTest, Process_TouchpadPointer) {
     std::shared_ptr<FakePointerController> fakePointerController =
             std::make_shared<FakePointerController>();
     fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
     fakePointerController->setPosition(0, 0);
 
-    // prepare device and capture
-    prepareDisplay(ui::ROTATION_0);
-    prepareAxes(POSITION | ID | SLOT);
-    mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0);
-    mFakePolicy->setPointerCapture(/*window=*/sp<BBinder>::make());
-    mFakePolicy->setPointerController(fakePointerController);
-    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
-
-    // captured touchpad should be a touchpad source
-    NotifyDeviceResetArgs resetArgs;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
-    ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
-
-    InputDeviceInfo deviceInfo = mDevice->getDeviceInfo();
-
-    const InputDeviceInfo::MotionRange* relRangeX =
-            deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, AINPUT_SOURCE_TOUCHPAD);
-    ASSERT_NE(relRangeX, nullptr);
-    ASSERT_EQ(relRangeX->min, -(RAW_X_MAX - RAW_X_MIN));
-    ASSERT_EQ(relRangeX->max, RAW_X_MAX - RAW_X_MIN);
-    const InputDeviceInfo::MotionRange* relRangeY =
-            deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, AINPUT_SOURCE_TOUCHPAD);
-    ASSERT_NE(relRangeY, nullptr);
-    ASSERT_EQ(relRangeY->min, -(RAW_Y_MAX - RAW_Y_MIN));
-    ASSERT_EQ(relRangeY->max, RAW_Y_MAX - RAW_Y_MIN);
-
-    // run captured pointer tests - note that this is unscaled, so input listener events should be
-    //                              identical to what the hardware sends (accounting for any
-    //                              calibration).
-    // FINGER 0 DOWN
-    processSlot(mapper, 0);
-    processId(mapper, 1);
-    processPosition(mapper, 100 + RAW_X_MIN, 100 + RAW_Y_MIN);
-    processKey(mapper, BTN_TOUCH, 1);
-    processSync(mapper);
-
-    // expect coord[0] to contain initial location of touch 0
-    NotifyMotionArgs args;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
-    ASSERT_EQ(1U, args.getPointerCount());
-    ASSERT_EQ(0, args.pointerProperties[0].id);
-    ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, args.source);
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[0], 100, 100, 1, 0, 0, 0, 0, 0, 0, 0));
-
-    // FINGER 1 DOWN
-    processSlot(mapper, 1);
-    processId(mapper, 2);
-    processPosition(mapper, 560 + RAW_X_MIN, 154 + RAW_Y_MIN);
-    processSync(mapper);
-
-    // expect coord[0] to contain previous location, coord[1] to contain new touch 1 location
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(ACTION_POINTER_1_DOWN, args.action);
-    ASSERT_EQ(2U, args.getPointerCount());
-    ASSERT_EQ(0, args.pointerProperties[0].id);
-    ASSERT_EQ(1, args.pointerProperties[1].id);
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[0], 100, 100, 1, 0, 0, 0, 0, 0, 0, 0));
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[1], 560, 154, 1, 0, 0, 0, 0, 0, 0, 0));
-
-    // FINGER 1 MOVE
-    processPosition(mapper, 540 + RAW_X_MIN, 690 + RAW_Y_MIN);
-    processSync(mapper);
-
-    // expect coord[0] to contain previous location, coord[1] to contain new touch 1 location
-    // from move
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[0], 100, 100, 1, 0, 0, 0, 0, 0, 0, 0));
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[1], 540, 690, 1, 0, 0, 0, 0, 0, 0, 0));
-
-    // FINGER 0 MOVE
-    processSlot(mapper, 0);
-    processPosition(mapper, 50 + RAW_X_MIN, 800 + RAW_Y_MIN);
-    processSync(mapper);
-
-    // expect coord[0] to contain new touch 0 location, coord[1] to contain previous location
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[0], 50, 800, 1, 0, 0, 0, 0, 0, 0, 0));
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[1], 540, 690, 1, 0, 0, 0, 0, 0, 0, 0));
-
-    // BUTTON DOWN
-    processKey(mapper, BTN_LEFT, 1);
-    processSync(mapper);
-
-    // touchinputmapper design sends a move before button press
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action);
-
-    // BUTTON UP
-    processKey(mapper, BTN_LEFT, 0);
-    processSync(mapper);
-
-    // touchinputmapper design sends a move after button release
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-
-    // FINGER 0 UP
-    processId(mapper, -1);
-    processSync(mapper);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | 0x0000, args.action);
-
-    // FINGER 1 MOVE
-    processSlot(mapper, 1);
-    processPosition(mapper, 320 + RAW_X_MIN, 900 + RAW_Y_MIN);
-    processSync(mapper);
-
-    // expect coord[0] to contain new location of touch 1, and properties[0].id to contain 1
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_EQ(1U, args.getPointerCount());
-    ASSERT_EQ(1, args.pointerProperties[0].id);
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[0], 320, 900, 1, 0, 0, 0, 0, 0, 0, 0));
-
-    // FINGER 1 UP
-    processId(mapper, -1);
-    processKey(mapper, BTN_TOUCH, 0);
-    processSync(mapper);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
-
-    // non captured touchpad should be a mouse source
-    mFakePolicy->setPointerCapture(/*window=*/nullptr);
-    configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
-}
-
-TEST_F(MultiTouchInputMapperTest, Process_UnCapturedTouchpadPointer) {
-    std::shared_ptr<FakePointerController> fakePointerController =
-            std::make_shared<FakePointerController>();
-    fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
-    fakePointerController->setPosition(0, 0);
-
-    // prepare device and capture
+    // prepare device
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0);
@@ -10004,7 +9854,7 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
 }
 
-TEST_F(MultiTouchInputMapperTest, WhenCapturedAndNotCaptured_GetSources) {
+TEST_F(MultiTouchInputMapperTest, Touchpad_GetSources) {
     std::shared_ptr<FakePointerController> fakePointerController =
             std::make_shared<FakePointerController>();
 
@@ -10017,11 +9867,6 @@
 
     // uncaptured touchpad should be a pointer device
     ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
-
-    // captured touchpad should be a touchpad device
-    mFakePolicy->setPointerCapture(/*window=*/sp<BBinder>::make());
-    configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
-    ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
 }
 
 // --- BluetoothMultiTouchInputMapperTest ---
diff --git a/services/inputflinger/tests/InputTraceSession.cpp b/services/inputflinger/tests/InputTraceSession.cpp
new file mode 100644
index 0000000..32acb5f
--- /dev/null
+++ b/services/inputflinger/tests/InputTraceSession.cpp
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "InputTraceSession.h"
+
+#include <NotifyArgsBuilders.h>
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <input/PrintTools.h>
+
+#include <utility>
+
+namespace android {
+
+using perfetto::protos::pbzero::AndroidInputEvent;
+using perfetto::protos::pbzero::AndroidInputEventConfig;
+using perfetto::protos::pbzero::AndroidKeyEvent;
+using perfetto::protos::pbzero::AndroidMotionEvent;
+using perfetto::protos::pbzero::AndroidWindowInputDispatchEvent;
+
+// These operator<< definitions must be in the global namespace for them to be accessible to the
+// GTEST library. They cannot be in the anonymous namespace.
+static std::ostream& operator<<(std::ostream& out,
+                                const std::variant<KeyEvent, MotionEvent>& event) {
+    std::visit([&](const auto& e) { out << e; }, event);
+    return out;
+}
+
+static std::ostream& operator<<(std::ostream& out,
+                                const InputTraceSession::WindowDispatchEvent& event) {
+    out << "Window dispatch to windowId: " << event.window->getId() << ", event: " << event.event;
+    return out;
+}
+
+namespace {
+
+inline uint32_t getId(const std::variant<KeyEvent, MotionEvent>& event) {
+    return std::visit([&](const auto& e) { return e.getId(); }, event);
+}
+
+std::unique_ptr<perfetto::TracingSession> startTrace(
+        const std::function<void(protozero::HeapBuffered<AndroidInputEventConfig>&)>& configure) {
+    protozero::HeapBuffered<AndroidInputEventConfig> inputEventConfig{};
+    configure(inputEventConfig);
+
+    perfetto::TraceConfig config;
+    config.add_buffers()->set_size_kb(1024); // Record up to 1 MiB.
+    auto* dataSourceConfig = config.add_data_sources()->mutable_config();
+    dataSourceConfig->set_name("android.input.inputevent");
+    dataSourceConfig->set_android_input_event_config_raw(inputEventConfig.SerializeAsString());
+
+    std::unique_ptr<perfetto::TracingSession> tracingSession(perfetto::Tracing::NewTrace());
+    tracingSession->Setup(config);
+    tracingSession->StartBlocking();
+    return tracingSession;
+}
+
+std::string stopTrace(std::unique_ptr<perfetto::TracingSession> tracingSession) {
+    tracingSession->StopBlocking();
+    std::vector<char> traceChars(tracingSession->ReadTraceBlocking());
+    return {traceChars.data(), traceChars.size()};
+}
+
+// Decodes the trace, and returns all of the traced input events, and whether they were each
+// traced as a redacted event.
+auto decodeTrace(const std::string& rawTrace) {
+    using namespace perfetto::protos::pbzero;
+
+    ArrayMap<AndroidMotionEvent::Decoder, bool /*redacted*/> tracedMotions;
+    ArrayMap<AndroidKeyEvent::Decoder, bool /*redacted*/> tracedKeys;
+    ArrayMap<AndroidWindowInputDispatchEvent::Decoder, bool /*redacted*/> tracedWindowDispatches;
+
+    Trace::Decoder trace{rawTrace};
+    if (trace.has_packet()) {
+        auto it = trace.packet();
+        while (it) {
+            TracePacket::Decoder packet{it->as_bytes()};
+            if (packet.has_android_input_event()) {
+                AndroidInputEvent::Decoder event{packet.android_input_event()};
+                if (event.has_dispatcher_motion_event()) {
+                    tracedMotions.emplace_back(event.dispatcher_motion_event(),
+                                               /*redacted=*/false);
+                }
+                if (event.has_dispatcher_motion_event_redacted()) {
+                    tracedMotions.emplace_back(event.dispatcher_motion_event_redacted(),
+                                               /*redacted=*/true);
+                }
+                if (event.has_dispatcher_key_event()) {
+                    tracedKeys.emplace_back(event.dispatcher_key_event(),
+                                            /*redacted=*/false);
+                }
+                if (event.has_dispatcher_key_event_redacted()) {
+                    tracedKeys.emplace_back(event.dispatcher_key_event_redacted(),
+                                            /*redacted=*/true);
+                }
+                if (event.has_dispatcher_window_dispatch_event()) {
+                    tracedWindowDispatches.emplace_back(event.dispatcher_window_dispatch_event(),
+                                                        /*redacted=*/false);
+                }
+                if (event.has_dispatcher_window_dispatch_event_redacted()) {
+                    tracedWindowDispatches
+                            .emplace_back(event.dispatcher_window_dispatch_event_redacted(),
+                                          /*redacted=*/true);
+                }
+            }
+            it++;
+        }
+    }
+    return std::tuple{std::move(tracedMotions), std::move(tracedKeys),
+                      std::move(tracedWindowDispatches)};
+}
+
+bool eventMatches(const MotionEvent& expected, const AndroidMotionEvent::Decoder& traced) {
+    return static_cast<uint32_t>(expected.getId()) == traced.event_id();
+}
+
+bool eventMatches(const KeyEvent& expected, const AndroidKeyEvent::Decoder& traced) {
+    return static_cast<uint32_t>(expected.getId()) == traced.event_id();
+}
+
+bool eventMatches(const InputTraceSession::WindowDispatchEvent& expected,
+                  const AndroidWindowInputDispatchEvent::Decoder& traced) {
+    return static_cast<uint32_t>(getId(expected.event)) == traced.event_id() &&
+            expected.window->getId() == traced.window_id();
+}
+
+template <typename ExpectedEvents, typename TracedEvents>
+void verifyExpectedEventsTraced(const ExpectedEvents& expectedEvents,
+                                const TracedEvents& tracedEvents, std::string_view name) {
+    uint32_t totalExpectedCount = 0;
+
+    for (const auto& [expectedEvent, expectedLevel] : expectedEvents) {
+        int32_t totalMatchCount = 0;
+        int32_t redactedMatchCount = 0;
+        for (const auto& [tracedEvent, isRedacted] : tracedEvents) {
+            if (eventMatches(expectedEvent, tracedEvent)) {
+                totalMatchCount++;
+                if (isRedacted) {
+                    redactedMatchCount++;
+                }
+            }
+        }
+        switch (expectedLevel) {
+            case Level::NONE:
+                ASSERT_EQ(totalMatchCount, 0) << "Event should not be traced, but it was traced"
+                                              << "\n\tExpected event: " << expectedEvent;
+                break;
+            case Level::REDACTED:
+            case Level::COMPLETE:
+                ASSERT_EQ(totalMatchCount, 1)
+                        << "Event should match exactly one traced event, but it matched: "
+                        << totalMatchCount << "\n\tExpected event: " << expectedEvent;
+                ASSERT_EQ(redactedMatchCount, expectedLevel == Level::REDACTED ? 1 : 0);
+                totalExpectedCount++;
+                break;
+        }
+    }
+
+    ASSERT_EQ(tracedEvents.size(), totalExpectedCount)
+            << "The number of traced " << name
+            << " events does not exactly match the number of expected events";
+}
+
+} // namespace
+
+InputTraceSession::InputTraceSession(
+        std::function<void(protozero::HeapBuffered<AndroidInputEventConfig>&)> configure)
+      : mPerfettoSession(startTrace(std::move(configure))) {}
+
+InputTraceSession::~InputTraceSession() {
+    const auto rawTrace = stopTrace(std::move(mPerfettoSession));
+    verifyExpectations(rawTrace);
+}
+
+void InputTraceSession::expectMotionTraced(Level level, const MotionEvent& event) {
+    mExpectedMotions.emplace_back(event, level);
+}
+
+void InputTraceSession::expectKeyTraced(Level level, const KeyEvent& event) {
+    mExpectedKeys.emplace_back(event, level);
+}
+
+void InputTraceSession::expectDispatchTraced(Level level, const WindowDispatchEvent& event) {
+    mExpectedWindowDispatches.emplace_back(event, level);
+}
+
+void InputTraceSession::verifyExpectations(const std::string& rawTrace) {
+    auto [tracedMotions, tracedKeys, tracedWindowDispatches] = decodeTrace(rawTrace);
+
+    verifyExpectedEventsTraced(mExpectedMotions, tracedMotions, "motion");
+    verifyExpectedEventsTraced(mExpectedKeys, tracedKeys, "key");
+    verifyExpectedEventsTraced(mExpectedWindowDispatches, tracedWindowDispatches,
+                               "window dispatch");
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/InputTraceSession.h b/services/inputflinger/tests/InputTraceSession.h
new file mode 100644
index 0000000..ed20bc8
--- /dev/null
+++ b/services/inputflinger/tests/InputTraceSession.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "FakeWindows.h"
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+#include <perfetto/config/android/android_input_event_config.pbzero.h>
+#include <perfetto/trace/android/android_input_event.pbzero.h>
+#include <perfetto/trace/trace.pbzero.h>
+#include <perfetto/tracing.h>
+#include <variant>
+#include <vector>
+
+namespace android {
+
+/**
+ * Tracing level constants used for adding expectations to the InputTraceSession.
+ */
+enum class Level {
+    NONE,
+    REDACTED,
+    COMPLETE,
+};
+
+template <typename K, typename V>
+using ArrayMap = std::vector<std::pair<K, V>>;
+
+/**
+ * A scoped representation of a tracing session that is used to make assertions on the trace.
+ *
+ * When the trace session is created, an "android.input.inputevent" trace will be started
+ * synchronously with the given configuration. While the trace is ongoing, the caller must
+ * specify the events that are expected to be in the trace using the expect* methods.
+ *
+ * When the session is destroyed, the trace is stopped synchronously, and all expectations will
+ * be verified using the gtest framework. This acts as a strict verifier, where the verification
+ * will fail both if an expected event does not show up in the trace and if there is an extra
+ * event in the trace that was not expected. Ordering is NOT verified for any events.
+ */
+class InputTraceSession {
+public:
+    explicit InputTraceSession(
+            std::function<void(
+                    protozero::HeapBuffered<perfetto::protos::pbzero::AndroidInputEventConfig>&)>
+                    configure);
+
+    ~InputTraceSession();
+
+    void expectMotionTraced(Level level, const MotionEvent& event);
+
+    void expectKeyTraced(Level level, const KeyEvent& event);
+
+    struct WindowDispatchEvent {
+        std::variant<KeyEvent, MotionEvent> event;
+        sp<FakeWindowHandle> window;
+    };
+    void expectDispatchTraced(Level level, const WindowDispatchEvent& event);
+
+private:
+    std::unique_ptr<perfetto::TracingSession> mPerfettoSession;
+    ArrayMap<WindowDispatchEvent, Level> mExpectedWindowDispatches;
+    ArrayMap<MotionEvent, Level> mExpectedMotions;
+    ArrayMap<KeyEvent, Level> mExpectedKeys;
+
+    void verifyExpectations(const std::string& rawTrace);
+};
+
+} // namespace android
diff --git a/services/inputflinger/tests/InputTracingTest.cpp b/services/inputflinger/tests/InputTracingTest.cpp
new file mode 100644
index 0000000..0404d6d
--- /dev/null
+++ b/services/inputflinger/tests/InputTracingTest.cpp
@@ -0,0 +1,749 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "../InputCommonConverter.h"
+#include "../dispatcher/InputDispatcher.h"
+#include "../dispatcher/trace/InputTracingPerfettoBackend.h"
+#include "../dispatcher/trace/ThreadedBackend.h"
+#include "FakeApplicationHandle.h"
+#include "FakeInputDispatcherPolicy.h"
+#include "FakeWindows.h"
+#include "InputTraceSession.h"
+#include "TestEventMatchers.h"
+
+#include <NotifyArgsBuilders.h>
+#include <android-base/logging.h>
+#include <android/content/pm/IPackageManagerNative.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+#include <perfetto/trace/android/android_input_event.pbzero.h>
+#include <perfetto/trace/trace.pbzero.h>
+#include <private/android_filesystem_config.h>
+#include <map>
+#include <vector>
+
+namespace android::inputdispatcher::trace {
+
+using perfetto::protos::pbzero::AndroidInputEventConfig;
+
+namespace {
+
+constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
+
+// Ensure common actions are interchangeable between keys and motions for convenience.
+static_assert(static_cast<int32_t>(AMOTION_EVENT_ACTION_DOWN) ==
+              static_cast<int32_t>(AKEY_EVENT_ACTION_DOWN));
+static_assert(static_cast<int32_t>(AMOTION_EVENT_ACTION_UP) ==
+              static_cast<int32_t>(AKEY_EVENT_ACTION_UP));
+constexpr int32_t ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN;
+constexpr int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE;
+constexpr int32_t ACTION_UP = AMOTION_EVENT_ACTION_UP;
+constexpr int32_t ACTION_CANCEL = AMOTION_EVENT_ACTION_CANCEL;
+
+constexpr gui::Pid PID{1};
+
+constexpr gui::Uid ALLOWED_UID_1{10012};
+constexpr gui::Uid ALLOWED_UID_2{10013};
+constexpr gui::Uid DISALLOWED_UID_1{1};
+constexpr gui::Uid DISALLOWED_UID_2{99};
+constexpr gui::Uid UNLISTED_UID{12345};
+
+const std::string ALLOWED_PKG_1{"allowed.pkg.1"};
+const std::string ALLOWED_PKG_2{"allowed.pkg.2"};
+const std::string DISALLOWED_PKG_1{"disallowed.pkg.1"};
+const std::string DISALLOWED_PKG_2{"disallowed.pkg.2"};
+
+const std::map<std::string, gui::Uid> kPackageUidMap{
+        {ALLOWED_PKG_1, ALLOWED_UID_1},
+        {ALLOWED_PKG_2, ALLOWED_UID_2},
+        {DISALLOWED_PKG_1, DISALLOWED_UID_1},
+        {DISALLOWED_PKG_2, DISALLOWED_UID_2},
+};
+
+class FakePackageManager : public content::pm::IPackageManagerNativeDefault {
+public:
+    binder::Status getPackageUid(const ::std::string& pkg, int64_t flags, int32_t userId,
+            int32_t* outUid) override {
+        auto it = kPackageUidMap.find(pkg);
+        *outUid = it != kPackageUidMap.end() ? static_cast<int32_t>(it->second.val()) : -1;
+        return binder::Status::ok();
+    }
+};
+
+const sp<testing::NiceMock<FakePackageManager>> kPackageManager =
+        sp<testing::NiceMock<FakePackageManager>>::make();
+
+const std::shared_ptr<FakeApplicationHandle> APP = std::make_shared<FakeApplicationHandle>();
+
+} // namespace
+
+// --- InputTracingTest ---
+
+class InputTracingTest : public testing::Test {
+protected:
+    std::unique_ptr<FakeInputDispatcherPolicy> mFakePolicy;
+    std::unique_ptr<InputDispatcher> mDispatcher;
+
+    void SetUp() override {
+        impl::PerfettoBackend::sUseInProcessBackendForTest = true;
+        impl::PerfettoBackend::sPackageManagerProvider = []() { return kPackageManager; };
+        mFakePolicy = std::make_unique<FakeInputDispatcherPolicy>();
+
+        auto tracingBackend = std::make_unique<impl::ThreadedBackend<impl::PerfettoBackend>>(
+                impl::PerfettoBackend());
+        mRequestTracerIdle = tracingBackend->getIdleWaiterForTesting();
+        mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy, std::move(tracingBackend));
+
+        mDispatcher->setInputDispatchMode(/*enabled=*/true, /*frozen=*/false);
+        ASSERT_EQ(OK, mDispatcher->start());
+    }
+
+    void TearDown() override {
+        ASSERT_EQ(OK, mDispatcher->stop());
+        mDispatcher.reset();
+        mFakePolicy.reset();
+    }
+
+    void waitForTracerIdle() {
+        mDispatcher->waitForIdle();
+        mRequestTracerIdle();
+    }
+
+    void setFocusedWindow(const sp<gui::WindowInfoHandle>& window) {
+        gui::FocusRequest request;
+        request.token = window->getToken();
+        request.windowName = window->getName();
+        request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
+        request.displayId = window->getInfo()->displayId;
+        mDispatcher->setFocusedWindow(request);
+    }
+
+    void tapAndExpect(const std::vector<const sp<FakeWindowHandle>>& windows,
+                      Level inboundTraceLevel, Level dispatchTraceLevel, InputTraceSession& s) {
+        const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                  .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                                  .build();
+        mDispatcher->notifyMotion(down);
+        s.expectMotionTraced(inboundTraceLevel, toMotionEvent(down));
+        for (const auto& window : windows) {
+            auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+            s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window});
+        }
+
+        const auto up = MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                                .build();
+        mDispatcher->notifyMotion(up);
+        s.expectMotionTraced(inboundTraceLevel, toMotionEvent(up));
+        for (const auto& window : windows) {
+            auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_UP));
+            s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window});
+        }
+    }
+
+    void keypressAndExpect(const std::vector<const sp<FakeWindowHandle>>& windows,
+                           Level inboundTraceLevel, Level dispatchTraceLevel,
+                           InputTraceSession& s) {
+        const auto down = KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).build();
+        mDispatcher->notifyKey(down);
+        s.expectKeyTraced(inboundTraceLevel, toKeyEvent(down));
+        for (const auto& window : windows) {
+            auto consumed = window->consumeKeyEvent(WithKeyAction(ACTION_DOWN));
+            s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window});
+        }
+
+        const auto up = KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).build();
+        mDispatcher->notifyKey(up);
+        s.expectKeyTraced(inboundTraceLevel, toKeyEvent(up));
+        for (const auto& window : windows) {
+            auto consumed = window->consumeKeyEvent(WithKeyAction(ACTION_UP));
+            s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window});
+        }
+    }
+
+private:
+    std::function<void()> mRequestTracerIdle;
+};
+
+TEST_F(InputTracingTest, EmptyConfigTracesNothing) {
+    InputTraceSession s{[](auto& config) {}};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true);
+
+    tapAndExpect({window}, Level::NONE, Level::NONE, s);
+    keypressAndExpect({window}, Level::NONE, Level::NONE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, TraceAll) {
+    InputTraceSession s{
+            [](auto& config) { config->set_mode(AndroidInputEventConfig::TRACE_MODE_TRACE_ALL); }};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true);
+
+    tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+    keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, NoRulesTracesNothing) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+    }};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true);
+
+    tapAndExpect({window}, Level::NONE, Level::NONE, s);
+    keypressAndExpect({window}, Level::NONE, Level::NONE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, EmptyRuleMatchesEverything) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Match everything as COMPLETE
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+    }};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true);
+
+    tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+    keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, UnspecifiedTracelLevel) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Match everything, trace level unspecified
+        auto rule = config->add_rules();
+    }};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true);
+
+    // Event is not traced by default if trace level is unspecified
+    tapAndExpect({window}, Level::NONE, Level::NONE, s);
+    keypressAndExpect({window}, Level::NONE, Level::NONE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MatchSecureWindow) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Match secure windows as COMPLETE
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+        rule->set_match_secure(true);
+    }};
+
+    // Add a normal window and a spy window.
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    spy->setSpy(true);
+    spy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    // Since neither are secure windows, events should not be traced.
+    tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+    // Events should be matched as secure if any of the target windows is marked as secure.
+    spy->setSecure(true);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+    tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    spy->setSecure(false);
+    window->setSecure(true);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+    tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    spy->setSecure(true);
+    window->setSecure(true);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+    tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    spy->setSecure(false);
+    window->setSecure(false);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+    tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MatchImeConnectionActive) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Match IME Connection Active as COMPLETE
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+        rule->set_match_ime_connection_active(true);
+    }};
+
+    // Add a normal window and a spy window.
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    spy->setSpy(true);
+    spy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    // Since IME connection is not active, events should not be traced.
+    tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+    mDispatcher->setInputMethodConnectionIsActive(true);
+    tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    mDispatcher->setInputMethodConnectionIsActive(false);
+    tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MatchAllPackages) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Match all package as COMPLETE
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+        rule->add_match_all_packages(ALLOWED_PKG_1);
+        rule->add_match_all_packages(ALLOWED_PKG_2);
+    }};
+
+    // All windows are allowlisted.
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    window->setOwnerInfo(PID, ALLOWED_UID_1);
+    auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    spy->setOwnerInfo(PID, ALLOWED_UID_2);
+    spy->setSpy(true);
+    spy->setTrustedOverlay(true);
+    auto systemSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    systemSpy->setOwnerInfo(PID, gui::Uid{AID_SYSTEM});
+    systemSpy->setSpy(true);
+    systemSpy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged(
+            {{*systemSpy->getInfo(), *spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({systemSpy, spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    // Add a disallowed spy. This will result in the event not being traced for all windows.
+    auto disallowedSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    disallowedSpy->setOwnerInfo(PID, DISALLOWED_UID_1);
+    disallowedSpy->setSpy(true);
+    disallowedSpy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged({{*systemSpy->getInfo(), *spy->getInfo(),
+                                        *disallowedSpy->getInfo(), *window->getInfo()},
+                                       {},
+                                       0,
+                                       0});
+
+    tapAndExpect({systemSpy, spy, disallowedSpy, window}, Level::NONE, Level::NONE, s);
+
+    // Change the owner of the disallowed spy to one for which we don't have a package mapping.
+    disallowedSpy->setOwnerInfo(PID, UNLISTED_UID);
+    mDispatcher->onWindowInfosChanged({{*systemSpy->getInfo(), *spy->getInfo(),
+                                        *disallowedSpy->getInfo(), *window->getInfo()},
+                                       {},
+                                       0,
+                                       0});
+
+    tapAndExpect({systemSpy, spy, disallowedSpy, window}, Level::NONE, Level::NONE, s);
+
+    // Remove the disallowed spy. Events are traced again.
+    mDispatcher->onWindowInfosChanged(
+            {{*systemSpy->getInfo(), *spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({systemSpy, spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MatchAnyPackages) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Match any package as COMPLETE
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+        rule->add_match_any_packages(ALLOWED_PKG_1);
+        rule->add_match_any_packages(ALLOWED_PKG_2);
+    }};
+
+    // Just a disallowed window. Events are not traced.
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    window->setOwnerInfo(PID, DISALLOWED_UID_1);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({window}, Level::NONE, Level::NONE, s);
+
+    // Add a spy for which we don't have a package mapping. Events are still not traced.
+    auto disallowedSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    disallowedSpy->setOwnerInfo(PID, UNLISTED_UID);
+    disallowedSpy->setSpy(true);
+    disallowedSpy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged({{*disallowedSpy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({disallowedSpy, window}, Level::NONE, Level::NONE, s);
+
+    // Add an allowed spy. Events are now traced for all packages.
+    auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    spy->setOwnerInfo(PID, ALLOWED_UID_1);
+    spy->setSpy(true);
+    spy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged(
+            {{*disallowedSpy->getInfo(), *spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({disallowedSpy, spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    // Add another disallowed spy. Events are still traced.
+    auto disallowedSpy2 = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    disallowedSpy2->setOwnerInfo(PID, DISALLOWED_UID_2);
+    disallowedSpy2->setSpy(true);
+    disallowedSpy2->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged({{*disallowedSpy->getInfo(), *disallowedSpy2->getInfo(),
+                                        *spy->getInfo(), *window->getInfo()},
+                                       {},
+                                       0,
+                                       0});
+
+    tapAndExpect({disallowedSpy, disallowedSpy2, spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MultipleMatchersInOneRule) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Match all of the following conditions as COMPLETE
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+        rule->add_match_all_packages(ALLOWED_PKG_1);
+        rule->add_match_all_packages(ALLOWED_PKG_2);
+        rule->add_match_any_packages(ALLOWED_PKG_1);
+        rule->add_match_any_packages(DISALLOWED_PKG_1);
+        rule->set_match_secure(false);
+        rule->set_match_ime_connection_active(false);
+    }};
+
+    // A single window into an allowed UID. Matches all matchers.
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    window->setOwnerInfo(PID, ALLOWED_UID_1);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    // Secure window does not match.
+    window->setSecure(true);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({window}, Level::NONE, Level::NONE, s);
+
+    // IME Connection Active does not match.
+    window->setSecure(false);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    mDispatcher->setInputMethodConnectionIsActive(true);
+
+    tapAndExpect({window}, Level::NONE, Level::NONE, s);
+
+    // Event going to DISALLOWED_PKG_1 does not match because it's not listed in match_all_packages.
+    mDispatcher->setInputMethodConnectionIsActive(false);
+    auto disallowedSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    disallowedSpy->setOwnerInfo(PID, DISALLOWED_UID_1);
+    disallowedSpy->setSpy(true);
+    disallowedSpy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged({{*disallowedSpy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({disallowedSpy, window}, Level::NONE, Level::NONE, s);
+
+    // Event going to ALLOWED_PKG_1 does not match because it's not listed in match_any_packages.
+    window->setOwnerInfo(PID, ALLOWED_UID_2);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({window}, Level::NONE, Level::NONE, s);
+
+    // All conditions match.
+    auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    spy->setOwnerInfo(PID, ALLOWED_UID_1);
+    spy->setSpy(true);
+    spy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MultipleRulesMatchInOrder) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Don't trace secure events
+        auto rule1 = config->add_rules();
+        rule1->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_NONE);
+        rule1->set_match_secure(true);
+        // Rule: Trace matched packages as COMPLETE when IME inactive
+        auto rule2 = config->add_rules();
+        rule2->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+        rule2->add_match_all_packages(ALLOWED_PKG_1);
+        rule2->add_match_all_packages(ALLOWED_PKG_2);
+        rule2->set_match_ime_connection_active(false);
+        // Rule: Trace the rest of the events as REDACTED
+        auto rule3 = config->add_rules();
+        rule3->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED);
+    }};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    window->setOwnerInfo(PID, ALLOWED_UID_1);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    // Verify that the first rule that matches in the order that they are specified is the
+    // one that applies to the event.
+    mDispatcher->setInputMethodConnectionIsActive(true);
+    tapAndExpect({window}, Level::REDACTED, Level::REDACTED, s);
+
+    mDispatcher->setInputMethodConnectionIsActive(false);
+    auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    spy->setOwnerInfo(PID, ALLOWED_UID_2);
+    spy->setSpy(true);
+    spy->setTrustedOverlay(true);
+    spy->setSecure(true);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+    spy->setSecure(false);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    spy->setOwnerInfo(PID, DISALLOWED_UID_1);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({spy, window}, Level::REDACTED, Level::REDACTED, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, TraceInboundEvents) {
+    InputTraceSession s{[](auto& config) {
+        // Only trace inbounds events - don't trace window dispatch
+        config->set_trace_dispatcher_input_events(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Trace everything as REDACTED
+        auto rule1 = config->add_rules();
+        rule1->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED);
+    }};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    window->setOwnerInfo(PID, ALLOWED_UID_1);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    // Only the inbound events are traced. No dispatch events are traced.
+    tapAndExpect({window}, Level::REDACTED, Level::NONE, s);
+
+    // Notify a down event, which should be traced.
+    const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                              .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                              .build();
+    s.expectMotionTraced(Level::REDACTED, toMotionEvent(down));
+    mDispatcher->notifyMotion(down);
+    auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    s.expectDispatchTraced(Level::NONE, {*consumed, window});
+
+    // Force a cancel event to be synthesized. This should not be traced, because only inbound
+    // events are requested.
+    mDispatcher->cancelCurrentTouch();
+    consumed = window->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+    s.expectMotionTraced(Level::NONE, *consumed);
+    s.expectDispatchTraced(Level::NONE, {*consumed, window});
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, TraceWindowDispatch) {
+    InputTraceSession s{[](auto& config) {
+        // Only trace window dispatch - don't trace event details
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Trace everything as REDACTED
+        auto rule1 = config->add_rules();
+        rule1->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED);
+    }};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    window->setOwnerInfo(PID, ALLOWED_UID_1);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    // Only dispatch events are traced. No inbound events are traced.
+    tapAndExpect({window}, Level::NONE, Level::REDACTED, s);
+
+    // Notify a down event; the dispatch should be traced.
+    const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                              .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                              .build();
+    s.expectMotionTraced(Level::NONE, toMotionEvent(down));
+    mDispatcher->notifyMotion(down);
+    auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    s.expectDispatchTraced(Level::REDACTED, {*consumed, window});
+
+    // Force a cancel event to be synthesized. All events that are dispatched should be traced.
+    mDispatcher->cancelCurrentTouch();
+    consumed = window->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+    s.expectMotionTraced(Level::NONE, *consumed);
+    s.expectDispatchTraced(Level::REDACTED, {*consumed, window});
+
+    waitForTracerIdle();
+}
+
+// TODO(b/336097719): Investigate flakiness and re-enable this test.
+TEST_F(InputTracingTest, DISABLED_SimultaneousTracingSessions) {
+    auto s1 = std::make_unique<InputTraceSession>(
+            [](auto& config) { config->set_mode(AndroidInputEventConfig::TRACE_MODE_TRACE_ALL); });
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true);
+
+    tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1);
+    keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1);
+
+    auto s2 = std::make_unique<InputTraceSession>([](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Trace all events as REDACTED when IME inactive
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED);
+        rule->set_match_ime_connection_active(false);
+    });
+
+    auto s3 = std::make_unique<InputTraceSession>([](auto& config) {
+        // Only trace window dispatch
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Trace non-secure events as COMPLETE
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+        rule->set_match_secure(false);
+    });
+
+    // Down event should be recorded on all traces.
+    const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                              .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                              .build();
+    mDispatcher->notifyMotion(down);
+    s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(down));
+    s2->expectMotionTraced(Level::REDACTED, toMotionEvent(down));
+    s3->expectMotionTraced(Level::NONE, toMotionEvent(down));
+    auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+    s2->expectDispatchTraced(Level::REDACTED, {*consumed, window});
+    s3->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+
+    // Move event when IME is active.
+    mDispatcher->setInputMethodConnectionIsActive(true);
+    const auto move1 = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                               .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                               .build();
+    mDispatcher->notifyMotion(move1);
+    s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(move1));
+    s2->expectMotionTraced(Level::NONE, toMotionEvent(move1));
+    s3->expectMotionTraced(Level::NONE, toMotionEvent(move1));
+    consumed = window->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+    s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+    s2->expectDispatchTraced(Level::NONE, {*consumed, window});
+    s3->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+
+    // Move event after window became secure.
+    mDispatcher->setInputMethodConnectionIsActive(false);
+    window->setSecure(true);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    const auto move2 = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                               .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                               .build();
+    mDispatcher->notifyMotion(move2);
+    s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(move2));
+    s2->expectMotionTraced(Level::REDACTED, toMotionEvent(move2));
+    s3->expectMotionTraced(Level::NONE, toMotionEvent(move2));
+    consumed = window->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+    s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+    s2->expectDispatchTraced(Level::REDACTED, {*consumed, window});
+    s3->expectDispatchTraced(Level::NONE, {*consumed, window});
+
+    waitForTracerIdle();
+    s2.reset();
+
+    // Up event.
+    window->setSecure(false);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    const auto up = MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                            .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                            .build();
+    mDispatcher->notifyMotion(up);
+    s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(up));
+    s3->expectMotionTraced(Level::NONE, toMotionEvent(up));
+    consumed = window->consumeMotionEvent(WithMotionAction(ACTION_UP));
+    s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+    s3->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+
+    waitForTracerIdle();
+    s3.reset();
+
+    tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1);
+    keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1);
+
+    waitForTracerIdle();
+    s1.reset();
+}
+
+} // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 3b2565e..11c6b7e 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -42,6 +42,7 @@
 
 constexpr int32_t DEVICE_ID = 3;
 constexpr int32_t SECOND_DEVICE_ID = DEVICE_ID + 1;
+constexpr int32_t THIRD_DEVICE_ID = SECOND_DEVICE_ID + 1;
 constexpr int32_t DISPLAY_ID = 5;
 constexpr int32_t ANOTHER_DISPLAY_ID = 10;
 constexpr int32_t DISPLAY_WIDTH = 480;
@@ -537,7 +538,8 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
-              generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
+              generateTestDeviceInfo(SECOND_DEVICE_ID,
+                                     AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
                                      DISPLAY_ID)}});
 
     ASSERT_FALSE(pc->isPointerShown());
@@ -548,6 +550,73 @@
     ASSERT_FALSE(pc->isPointerShown());
 }
 
+TEST_F(PointerChoreographerTest, DisabledMouseConnected) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    InputDeviceInfo mouseDeviceInfo =
+            generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE);
+    // Disable this mouse device.
+    mouseDeviceInfo.setEnabled(false);
+    mChoreographer.notifyInputDevicesChanged({/*id=*/0, {mouseDeviceInfo}});
+
+    // Disabled mouse device should not create PointerController
+    assertPointerControllerNotCreated();
+}
+
+TEST_F(PointerChoreographerTest, MouseDeviceDisableLater) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    InputDeviceInfo mouseDeviceInfo =
+            generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE);
+
+    mChoreographer.notifyInputDevicesChanged({/*id=*/0, {mouseDeviceInfo}});
+
+    auto pc = assertPointerControllerCreated(PointerControllerInterface::ControllerType::MOUSE);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Now we disable this mouse device
+    mouseDeviceInfo.setEnabled(false);
+    mChoreographer.notifyInputDevicesChanged({/*id=*/0, {mouseDeviceInfo}});
+
+    // Because the mouse device disabled, the PointerController should be removed.
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, MultipleEnabledAndDisabledMiceConnectionAndRemoval) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    InputDeviceInfo disabledMouseDeviceInfo =
+            generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE);
+    disabledMouseDeviceInfo.setEnabled(false);
+
+    InputDeviceInfo enabledMouseDeviceInfo =
+            generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE);
+
+    InputDeviceInfo anotherEnabledMouseDeviceInfo =
+            generateTestDeviceInfo(THIRD_DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE);
+
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {disabledMouseDeviceInfo, enabledMouseDeviceInfo, anotherEnabledMouseDeviceInfo}});
+
+    // Mouse should show, because we have two enabled mice device.
+    auto pc = assertPointerControllerCreated(PointerControllerInterface::ControllerType::MOUSE);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Now we remove one of enabled mice device.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {disabledMouseDeviceInfo, enabledMouseDeviceInfo}});
+
+    // Because we still have an enabled mouse device, pointer should still show.
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // We finally remove last enabled mouse device.
+    mChoreographer.notifyInputDevicesChanged({/*id=*/0, {disabledMouseDeviceInfo}});
+
+    // The PointerController should be removed, because there is no enabled mouse device.
+    assertPointerControllerRemoved(pc);
+}
+
 TEST_F(PointerChoreographerTest, WhenShowTouchesEnabledAndDisabledDoesNotCreatePointerController) {
     // Disable show touches and add a touch device.
     mChoreographer.setShowTouchesEnabled(false);
@@ -1772,4 +1841,107 @@
     ASSERT_FALSE(pc->isPointerShown());
 }
 
+TEST_F(PointerChoreographerTest, DrawingTabletCanReportMouseEvent) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ADISPLAY_ID_NONE)}});
+    // There should be no controller created when a drawing tablet is connected
+    assertPointerControllerNotCreated();
+
+    // But if it ends up reporting a mouse event, then the mouse controller will be created
+    // dynamically.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // The controller is removed when the drawing tablet is removed
+    mChoreographer.notifyInputDevicesChanged({/*id=*/0, {}});
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, MultipleDrawingTabletsReportMouseEvents) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+    // First drawing tablet is added
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ADISPLAY_ID_NONE)}});
+    assertPointerControllerNotCreated();
+
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Second drawing tablet is added
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ADISPLAY_ID_NONE),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, DRAWING_TABLET_SOURCE, ADISPLAY_ID_NONE)}});
+    assertPointerControllerNotRemoved(pc);
+
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+
+    // First drawing tablet is removed
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ADISPLAY_ID_NONE)}});
+    assertPointerControllerNotRemoved(pc);
+
+    // Second drawing tablet is removed
+    mChoreographer.notifyInputDevicesChanged({/*id=*/0, {}});
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, MouseAndDrawingTabletReportMouseEvents) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+    // Mouse and drawing tablet connected
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ADISPLAY_ID_NONE),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Drawing tablet reports a mouse event
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, DRAWING_TABLET_SOURCE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+
+    // Remove the mouse device
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ADISPLAY_ID_NONE)}});
+
+    // The mouse controller should not be removed, because the drawing tablet has produced a
+    // mouse event, so we are treating it as a mouse too.
+    assertPointerControllerNotRemoved(pc);
+
+    mChoreographer.notifyInputDevicesChanged({/*id=*/0, {}});
+    assertPointerControllerRemoved(pc);
+}
+
 } // namespace android
diff --git a/services/sensorservice/SensorEventConnection.cpp b/services/sensorservice/SensorEventConnection.cpp
index dc86577..3446f58 100644
--- a/services/sensorservice/SensorEventConnection.cpp
+++ b/services/sensorservice/SensorEventConnection.cpp
@@ -461,16 +461,18 @@
         // is pre-Q, still permit delivering events to the app even if permission isn't granted
         // (since this permission was only introduced in Q)
         if ((event.type == SENSOR_TYPE_STEP_COUNTER || event.type == SENSOR_TYPE_STEP_DETECTOR) &&
-                mTargetSdk > 0 && mTargetSdk <= __ANDROID_API_P__) {
+            mTargetSdk > 0 && mTargetSdk <= __ANDROID_API_P__) {
+            success = true;
+        } else if (mUid == AID_SYSTEM) {
+            // Allow access if it is requested from system.
             success = true;
         } else {
             int32_t sensorHandle = event.sensor;
             String16 noteMsg("Sensor event (");
             noteMsg.append(String16(mService->getSensorStringType(sensorHandle)));
             noteMsg.append(String16(")"));
-            int32_t appOpMode = mService->sAppOpsManager.noteOp(iter->second, mUid,
-                                                                mOpPackageName, mAttributionTag,
-                                                                noteMsg);
+            int32_t appOpMode = mService->sAppOpsManager.noteOp(iter->second, mUid, mOpPackageName,
+                                                                mAttributionTag, noteMsg);
             success = (appOpMode == AppOpsManager::MODE_ALLOWED);
         }
     }
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index e1c43c6..69e4309 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -2302,11 +2302,16 @@
         // requirement to hold the AR permission to access Step Counter and Step Detector events
         // was introduced.
         canAccess = true;
+    } else if (IPCThreadState::self()->getCallingUid() == AID_SYSTEM) {
+        // Allow access if it is requested from system.
+        canAccess = true;
     } else if (hasPermissionForSensor(sensor)) {
-        // Ensure that the AppOp is allowed, or that there is no necessary app op for the sensor
+        // Ensure that the AppOp is allowed, or that there is no necessary app op
+        // for the sensor
         if (opCode >= 0) {
-            const int32_t appOpMode = sAppOpsManager.checkOp(opCode,
-                    IPCThreadState::self()->getCallingUid(), opPackageName);
+            const int32_t appOpMode =
+                    sAppOpsManager.checkOp(opCode, IPCThreadState::self()->getCallingUid(),
+                                           opPackageName);
             canAccess = (appOpMode == AppOpsManager::MODE_ALLOWED);
         } else {
             canAccess = true;
diff --git a/services/surfaceflinger/BackgroundExecutor.cpp b/services/surfaceflinger/BackgroundExecutor.cpp
index 5a1ec6f..3cef875 100644
--- a/services/surfaceflinger/BackgroundExecutor.cpp
+++ b/services/surfaceflinger/BackgroundExecutor.cpp
@@ -19,6 +19,9 @@
 #define LOG_TAG "BackgroundExecutor"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
+#include <processgroup/sched_policy.h>
+#include <pthread.h>
+#include <sched.h>
 #include <utils/Log.h>
 #include <mutex>
 
@@ -26,14 +29,24 @@
 
 namespace android {
 
-ANDROID_SINGLETON_STATIC_INSTANCE(BackgroundExecutor);
+namespace {
 
-BackgroundExecutor::BackgroundExecutor() : Singleton<BackgroundExecutor>() {
+void set_thread_priority(bool highPriority) {
+    set_sched_policy(0, highPriority ? SP_FOREGROUND : SP_BACKGROUND);
+    struct sched_param param = {0};
+    param.sched_priority = highPriority ? 2 : 0 /* must be 0 for non-RT */;
+    sched_setscheduler(gettid(), highPriority ? SCHED_FIFO : SCHED_NORMAL, &param);
+}
+
+} // anonymous namespace
+
+BackgroundExecutor::BackgroundExecutor(bool highPriority) {
     // mSemaphore must be initialized before any calls to
     // BackgroundExecutor::sendCallbacks. For this reason, we initialize it
     // within the constructor instead of within mThread.
     LOG_ALWAYS_FATAL_IF(sem_init(&mSemaphore, 0, 0), "sem_init failed");
-    mThread = std::thread([&]() {
+    mThread = std::thread([&, highPriority]() {
+        set_thread_priority(highPriority);
         while (!mDone) {
             LOG_ALWAYS_FATAL_IF(sem_wait(&mSemaphore), "sem_wait failed (%d)", errno);
             auto callbacks = mCallbacksQueue.pop();
@@ -45,6 +58,11 @@
             }
         }
     });
+    if (highPriority) {
+        pthread_setname_np(mThread.native_handle(), "BckgrndExec HP");
+    } else {
+        pthread_setname_np(mThread.native_handle(), "BckgrndExec LP");
+    }
 }
 
 BackgroundExecutor::~BackgroundExecutor() {
diff --git a/services/surfaceflinger/BackgroundExecutor.h b/services/surfaceflinger/BackgroundExecutor.h
index 66b7d7a..1b5fadd 100644
--- a/services/surfaceflinger/BackgroundExecutor.h
+++ b/services/surfaceflinger/BackgroundExecutor.h
@@ -18,7 +18,6 @@
 
 #include <ftl/small_vector.h>
 #include <semaphore.h>
-#include <utils/Singleton.h>
 #include <thread>
 
 #include "LocklessQueue.h"
@@ -26,10 +25,20 @@
 namespace android {
 
 // Executes tasks off the main thread.
-class BackgroundExecutor : public Singleton<BackgroundExecutor> {
+class BackgroundExecutor {
 public:
-    BackgroundExecutor();
     ~BackgroundExecutor();
+
+    static BackgroundExecutor& getInstance() {
+        static BackgroundExecutor instance(true);
+        return instance;
+    }
+
+    static BackgroundExecutor& getLowPriorityInstance() {
+        static BackgroundExecutor instance(false);
+        return instance;
+    }
+
     using Callbacks = ftl::SmallVector<std::function<void()>, 10>;
     // Queues callbacks onto a work queue to be executed by a background thread.
     // This is safe to call from multiple threads.
@@ -37,6 +46,8 @@
     void flushQueue();
 
 private:
+    BackgroundExecutor(bool highPriority);
+
     sem_t mSemaphore;
     std::atomic_bool mDone = false;
 
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index a52cc87..7fa58df 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -11,6 +11,7 @@
 cc_defaults {
     name: "libcompositionengine_defaults",
     defaults: [
+        "aconfig_lib_cc_static_link.defaults",
         "android.hardware.graphics.composer3-ndk_shared",
         "android.hardware.power-ndk_shared",
         "librenderengine_deps",
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
index e7d0afc..d420838 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -325,6 +325,7 @@
     virtual void setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) = 0;
     virtual void setHintSessionRequiresRenderEngine(bool requiresRenderEngine) = 0;
     virtual bool isPowerHintSessionEnabled() = 0;
+    virtual bool isPowerHintSessionGpuReportingEnabled() = 0;
     virtual void cacheClientCompositionRequests(uint32_t cacheSize) = 0;
     virtual bool canPredictCompositionStrategy(const CompositionRefreshArgs&) = 0;
 };
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
index eaffa9e..d87968f 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
@@ -93,6 +93,7 @@
 
 private:
     bool isPowerHintSessionEnabled() override;
+    bool isPowerHintSessionGpuReportingEnabled() override;
     void setHintSessionGpuStart(TimePoint startTime) override;
     void setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) override;
     void setHintSessionRequiresRenderEngine(bool requiresRenderEngine) override;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index 3671f15..adcbbb9 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -148,6 +148,7 @@
     void setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) override;
     void setHintSessionRequiresRenderEngine(bool requiresRenderEngine) override;
     bool isPowerHintSessionEnabled() override;
+    bool isPowerHintSessionGpuReportingEnabled() override;
     void dumpBase(std::string&) const;
 
     // Implemented by the final implementation for the final state it uses.
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
index 019a058..3f3deae 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
@@ -138,6 +138,7 @@
     MOCK_METHOD(void, setHintSessionGpuFence, (std::unique_ptr<FenceTime> && gpuFence));
     MOCK_METHOD(void, setHintSessionRequiresRenderEngine, (bool requiresRenderEngine));
     MOCK_METHOD(bool, isPowerHintSessionEnabled, ());
+    MOCK_METHOD(bool, isPowerHintSessionGpuReportingEnabled, ());
 };
 
 } // namespace android::compositionengine::mock
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index 3d35704..c18be7a 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -412,6 +412,10 @@
     return mPowerAdvisor != nullptr && mPowerAdvisor->usePowerHintSession();
 }
 
+bool Display::isPowerHintSessionGpuReportingEnabled() {
+    return mPowerAdvisor != nullptr && mPowerAdvisor->supportsGpuReporting();
+}
+
 // For ADPF GPU v0 this is expected to set start time to when the GPU commands are submitted with
 // fence returned, i.e. when RenderEngine flushes the commands and returns the draw fence.
 void Display::setHintSessionGpuStart(TimePoint startTime) {
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 1f01b57..84f3f25 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -1214,7 +1214,7 @@
     if (!optReadyFence) {
         return;
     }
-    if (isPowerHintSessionEnabled() && !FlagManager::getInstance().adpf_gpu_sf()) {
+    if (isPowerHintSessionEnabled() && !isPowerHintSessionGpuReportingEnabled()) {
         // get fence end time to know when gpu is complete in display
         setHintSessionGpuFence(
                 std::make_unique<FenceTime>(sp<Fence>::make(dup(optReadyFence->get()))));
@@ -1363,7 +1363,7 @@
         if (fence != Fence::NO_FENCE && fence->isValid() &&
             !outputCompositionState.reusedClientComposition) {
             setHintSessionRequiresRenderEngine(true);
-            if (FlagManager::getInstance().adpf_gpu_sf()) {
+            if (isPowerHintSessionGpuReportingEnabled()) {
                 // the order of the two calls here matters as we should check if the previously
                 // tracked fence has signaled first and archive the previous start time
                 setHintSessionGpuStart(TimePoint::now());
@@ -1563,6 +1563,10 @@
     return false;
 }
 
+bool Output::isPowerHintSessionGpuReportingEnabled() {
+    return false;
+}
+
 void Output::presentFrameAndReleaseLayers() {
     ATRACE_FORMAT("%s for %s", __func__, mNamePlusId.c_str());
     ALOGV(__FUNCTION__);
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
index 0e3fdbb..10dc927 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <common/FlagManager.h>
 #include <compositionengine/impl/planner/LayerState.h>
 
 namespace {
@@ -70,6 +71,10 @@
         if (field->getField() == LayerStateField::Buffer) {
             continue;
         }
+        if (FlagManager::getInstance().cache_when_source_crop_layer_only_moved() &&
+            field->getField() == LayerStateField::SourceCrop) {
+            continue;
+        }
         android::hashCombineSingleHashed(hash, field->getHash());
     }
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 575a30e..4612117 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -126,6 +126,7 @@
                        const std::unordered_map<std::string, bool>&());
 
     MOCK_CONST_METHOD1(dump, void(std::string&));
+    MOCK_CONST_METHOD1(dumpOverlayProperties, void(std::string&));
     MOCK_CONST_METHOD0(getComposer, android::Hwc2::Composer*());
 
     MOCK_METHOD(hal::HWDisplayId, getPrimaryHwcDisplayId, (), (const, override));
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
index d0843a2..ed2ffa9 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
@@ -38,6 +38,7 @@
     MOCK_METHOD(void, notifyDisplayUpdateImminentAndCpuReset, (), (override));
     MOCK_METHOD(bool, usePowerHintSession, (), (override));
     MOCK_METHOD(bool, supportsPowerHintSession, (), (override));
+    MOCK_METHOD(bool, supportsGpuReporting, (), (override));
     MOCK_METHOD(void, updateTargetWorkDuration, (Duration targetDuration), (override));
     MOCK_METHOD(void, reportActualWorkDuration, (), (override));
     MOCK_METHOD(void, enablePowerHintSession, (bool enabled), (override));
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 6be245e..0dc3c9f 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -2020,10 +2020,12 @@
         MOCK_METHOD(void, setHintSessionRequiresRenderEngine, (bool requiresRenderEngine),
                     (override));
         MOCK_METHOD(bool, isPowerHintSessionEnabled, (), (override));
+        MOCK_METHOD(bool, isPowerHintSessionGpuReportingEnabled, (), (override));
     };
 
     OutputPresentTest() {
         EXPECT_CALL(mOutput, isPowerHintSessionEnabled()).WillRepeatedly(Return(true));
+        EXPECT_CALL(mOutput, isPowerHintSessionGpuReportingEnabled()).WillRepeatedly(Return(true));
     }
 
     StrictMock<OutputPartialMock> mOutput;
@@ -3001,6 +3003,7 @@
         MOCK_METHOD(void, setHintSessionGpuFence, (std::unique_ptr<FenceTime> && gpuFence),
                     (override));
         MOCK_METHOD(bool, isPowerHintSessionEnabled, (), (override));
+        MOCK_METHOD(bool, isPowerHintSessionGpuReportingEnabled, (), (override));
     };
 
     OutputFinishFrameTest() {
@@ -3010,6 +3013,7 @@
         EXPECT_CALL(mOutput, getCompositionEngine()).WillRepeatedly(ReturnRef(mCompositionEngine));
         EXPECT_CALL(mCompositionEngine, getRenderEngine()).WillRepeatedly(ReturnRef(mRenderEngine));
         EXPECT_CALL(mOutput, isPowerHintSessionEnabled()).WillRepeatedly(Return(true));
+        EXPECT_CALL(mOutput, isPowerHintSessionGpuReportingEnabled()).WillRepeatedly(Return(true));
     }
 
     StrictMock<OutputPartialMock> mOutput;
@@ -3027,7 +3031,6 @@
 }
 
 TEST_F(OutputFinishFrameTest, takesEarlyOutifComposeSurfacesReturnsNoFence) {
-    SET_FLAG_FOR_TEST(flags::adpf_gpu_sf, true);
     mOutput.mState.isEnabled = true;
     EXPECT_CALL(mOutput, updateProtectedContentState());
     EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _)).WillOnce(Return(true));
@@ -3038,7 +3041,7 @@
 }
 
 TEST_F(OutputFinishFrameTest, queuesBufferIfComposeSurfacesReturnsAFenceWithAdpfGpuOff) {
-    SET_FLAG_FOR_TEST(flags::adpf_gpu_sf, false);
+    EXPECT_CALL(mOutput, isPowerHintSessionGpuReportingEnabled()).WillOnce(Return(false));
     mOutput.mState.isEnabled = true;
 
     InSequence seq;
@@ -3054,7 +3057,6 @@
 }
 
 TEST_F(OutputFinishFrameTest, queuesBufferIfComposeSurfacesReturnsAFence) {
-    SET_FLAG_FOR_TEST(flags::adpf_gpu_sf, true);
     mOutput.mState.isEnabled = true;
 
     InSequence seq;
@@ -3071,7 +3073,6 @@
 
 TEST_F(OutputFinishFrameTest, queuesBufferWithHdrSdrRatio) {
     SET_FLAG_FOR_TEST(flags::fp16_client_target, true);
-    SET_FLAG_FOR_TEST(flags::adpf_gpu_sf, true);
     mOutput.mState.isEnabled = true;
 
     InSequence seq;
@@ -3099,7 +3100,6 @@
 }
 
 TEST_F(OutputFinishFrameTest, predictionSucceeded) {
-    SET_FLAG_FOR_TEST(flags::adpf_gpu_sf, true);
     mOutput.mState.isEnabled = true;
     mOutput.mState.strategyPrediction = CompositionStrategyPredictionState::SUCCESS;
     InSequence seq;
@@ -3111,7 +3111,6 @@
 }
 
 TEST_F(OutputFinishFrameTest, predictionFailedAndBufferIsReused) {
-    SET_FLAG_FOR_TEST(flags::adpf_gpu_sf, true);
     mOutput.mState.isEnabled = true;
     mOutput.mState.strategyPrediction = CompositionStrategyPredictionState::FAIL;
 
@@ -3458,6 +3457,7 @@
                     (override));
         MOCK_METHOD(void, setHintSessionRequiresRenderEngine, (bool), (override));
         MOCK_METHOD(bool, isPowerHintSessionEnabled, (), (override));
+        MOCK_METHOD(bool, isPowerHintSessionGpuReportingEnabled, (), (override));
     };
 
     OutputComposeSurfacesTest() {
@@ -3488,6 +3488,7 @@
         EXPECT_CALL(*mDisplayColorProfile, getHdrCapabilities())
                 .WillRepeatedly(ReturnRef(kHdrCapabilities));
         EXPECT_CALL(mOutput, isPowerHintSessionEnabled()).WillRepeatedly(Return(true));
+        EXPECT_CALL(mOutput, isPowerHintSessionGpuReportingEnabled()).WillRepeatedly(Return(true));
     }
 
     struct ExecuteState : public CallOrderStateMachineHelper<TestType, ExecuteState> {
@@ -3761,7 +3762,7 @@
 }
 
 TEST_F(OutputComposeSurfacesTest, clientCompositionIfBufferChangesWithAdpfGpuOff) {
-    SET_FLAG_FOR_TEST(flags::adpf_gpu_sf, false);
+    EXPECT_CALL(mOutput, isPowerHintSessionGpuReportingEnabled()).WillOnce(Return(false));
     LayerFE::LayerSettings r1;
     LayerFE::LayerSettings r2;
 
@@ -3805,7 +3806,6 @@
 }
 
 TEST_F(OutputComposeSurfacesTest, clientCompositionIfBufferChanges) {
-    SET_FLAG_FOR_TEST(flags::adpf_gpu_sf, true);
     LayerFE::LayerSettings r1;
     LayerFE::LayerSettings r2;
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
index 39fce2b..03758b3 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
@@ -18,6 +18,7 @@
 #define LOG_TAG "LayerStateTest"
 
 #include <aidl/android/hardware/graphics/common/BufferUsage.h>
+#include <common/include/common/test/FlagUtils.h>
 #include <compositionengine/impl/OutputLayer.h>
 #include <compositionengine/impl/planner/LayerState.h>
 #include <compositionengine/mock/LayerFE.h>
@@ -26,6 +27,7 @@
 #include <log/log.h>
 
 #include "android/hardware_buffer.h"
+#include "com_android_graphics_surfaceflinger_flags.h"
 #include "compositionengine/LayerFECompositionState.h"
 
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
@@ -464,6 +466,9 @@
 }
 
 TEST_F(LayerStateTest, compareSourceCrop) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              cache_when_source_crop_layer_only_moved,
+                      false);
     OutputLayerCompositionState outputLayerCompositionState;
     outputLayerCompositionState.sourceCrop = sFloatRectOne;
     LayerFECompositionState layerFECompositionState;
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp
index 35d0ffb..a1210b4 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp
@@ -18,6 +18,9 @@
 #undef LOG_TAG
 #define LOG_TAG "PredictorTest"
 
+#include <common/include/common/test/FlagUtils.h>
+#include "com_android_graphics_surfaceflinger_flags.h"
+
 #include <compositionengine/impl/planner/Predictor.h>
 #include <compositionengine/mock/LayerFE.h>
 #include <compositionengine/mock/OutputLayer.h>
@@ -127,6 +130,9 @@
 }
 
 TEST_F(LayerStackTest, getApproximateMatch_matchesSingleDifferenceInSingleLayer) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              cache_when_source_crop_layer_only_moved,
+                      false);
     mock::OutputLayer outputLayerOne;
     sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -158,6 +164,9 @@
 }
 
 TEST_F(LayerStackTest, getApproximateMatch_matchesSingleDifferenceInMultiLayerStack) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              cache_when_source_crop_layer_only_moved,
+                      false);
     mock::OutputLayer outputLayerOne;
     sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -304,6 +313,9 @@
 }
 
 TEST_F(LayerStackTest, getApproximateMatch_doesNotMatchMultipleApproximations) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              cache_when_source_crop_layer_only_moved,
+                      false);
     mock::OutputLayer outputLayerOne;
     sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -347,6 +359,9 @@
 };
 
 TEST_F(LayerStackTest, reorderingChangesNonBufferHash) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              cache_when_source_crop_layer_only_moved,
+                      false);
     mock::OutputLayer outputLayerOne;
     sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -467,6 +482,9 @@
 }
 
 TEST_F(PredictorTest, getPredictedPlan_recordCandidateAndRetrieveApproximateMatch) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              cache_when_source_crop_layer_only_moved,
+                      false);
     mock::OutputLayer outputLayerOne;
     sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -504,6 +522,9 @@
 }
 
 TEST_F(PredictorTest, recordMissedPlan_skipsApproximateMatch) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              cache_when_source_crop_layer_only_moved,
+                      false);
     mock::OutputLayer outputLayerOne;
     sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 776bcd3..3cfb9ca 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -77,9 +77,7 @@
 using aidl::android::hardware::graphics::common::HdrConversionStrategy;
 using aidl::android::hardware::graphics::composer3::Capability;
 using aidl::android::hardware::graphics::composer3::DisplayCapability;
-using aidl::android::hardware::graphics::composer3::VrrConfig;
 using namespace std::string_literals;
-namespace hal = android::hardware::graphics::composer::hal;
 
 namespace android {
 
@@ -964,8 +962,45 @@
     return mSupportedLayerGenericMetadata;
 }
 
+void HWComposer::dumpOverlayProperties(std::string& result) const {
+    // dump overlay properties
+    result.append("OverlayProperties:\n");
+    base::StringAppendF(&result, "supportMixedColorSpaces: %d\n",
+                        mOverlayProperties.supportMixedColorSpaces);
+    base::StringAppendF(&result, "SupportedBufferCombinations(%zu entries)\n",
+                        mOverlayProperties.combinations.size());
+    for (const auto& combination : mOverlayProperties.combinations) {
+        result.append("    pixelFormats=\n");
+        for (const auto& pixelFormat : combination.pixelFormats) {
+            base::StringAppendF(&result, "        %s (%d)\n",
+                                decodePixelFormat(static_cast<PixelFormat>(pixelFormat)).c_str(),
+                                static_cast<uint32_t>(pixelFormat));
+        }
+        result.append("    standards=\n");
+        for (const auto& standard : combination.standards) {
+            base::StringAppendF(&result, "        %s (%d)\n",
+                                decodeStandardOnly(static_cast<uint32_t>(standard)).c_str(),
+                                static_cast<uint32_t>(standard));
+        }
+        result.append("    transfers=\n");
+        for (const auto& transfer : combination.transfers) {
+            base::StringAppendF(&result, "        %s (%d)\n",
+                                decodeTransferOnly(static_cast<uint32_t>(transfer)).c_str(),
+                                static_cast<uint32_t>(transfer));
+        }
+        result.append("    ranges=\n");
+        for (const auto& range : combination.ranges) {
+            base::StringAppendF(&result, "        %s (%d)\n",
+                                decodeRangeOnly(static_cast<uint32_t>(range)).c_str(),
+                                static_cast<uint32_t>(range));
+        }
+        result.append("\n");
+    }
+}
+
 void HWComposer::dump(std::string& result) const {
     result.append(mComposer->dumpDebugInfo());
+    dumpOverlayProperties(result);
 }
 
 std::optional<PhysicalDisplayId> HWComposer::toPhysicalDisplayId(
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 7fbbb1a..bc32cda 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -269,6 +269,8 @@
 
     virtual void dump(std::string& out) const = 0;
 
+    virtual void dumpOverlayProperties(std::string& out) const = 0;
+
     virtual Hwc2::Composer* getComposer() const = 0;
 
     // Returns the first display connected at boot. Its connection via HWComposer::onHotplug,
@@ -468,6 +470,7 @@
 
     // for debugging ----------------------------------------------------------
     void dump(std::string& out) const override;
+    void dumpOverlayProperties(std::string& out) const override;
 
     Hwc2::Composer* getComposer() const override { return mComposer.get(); }
 
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
index 96cf84c..96d5ca6 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -206,7 +206,8 @@
 }
 
 bool PowerAdvisor::shouldCreateSessionWithConfig() {
-    return mSessionConfigSupported && FlagManager::getInstance().adpf_use_fmq_channel();
+    return mSessionConfigSupported && mBootFinished &&
+            FlagManager::getInstance().adpf_use_fmq_channel();
 }
 
 bool PowerAdvisor::ensurePowerHintSessionRunning() {
@@ -241,7 +242,7 @@
 }
 
 void PowerAdvisor::updateTargetWorkDuration(Duration targetDuration) {
-    if (!usePowerHintSession()) {
+    if (!mBootFinished || !usePowerHintSession()) {
         ALOGV("Power hint session target duration cannot be set, skipping");
         return;
     }
@@ -280,7 +281,7 @@
         ATRACE_INT64("Measured duration", actualDuration->durationNanos);
         ATRACE_INT64("Target error term", actualDuration->durationNanos - mTargetDuration.ns());
         ATRACE_INT64("Reported duration", actualDuration->durationNanos);
-        if (FlagManager::getInstance().adpf_gpu_sf()) {
+        if (supportsGpuReporting()) {
             ATRACE_INT64("Reported cpu duration", actualDuration->cpuDurationNanos);
             ATRACE_INT64("Reported gpu duration", actualDuration->gpuDurationNanos);
         }
@@ -341,6 +342,10 @@
     return ensurePowerHintSessionRunning();
 }
 
+bool PowerAdvisor::supportsGpuReporting() {
+    return mBootFinished && FlagManager::getInstance().adpf_gpu_sf();
+}
+
 void PowerAdvisor::setGpuStartTime(DisplayId displayId, TimePoint startTime) {
     DisplayTimingData& displayData = mDisplayTimingData[displayId];
     if (displayData.gpuEndFenceTime) {
@@ -366,7 +371,7 @@
 
 void PowerAdvisor::setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) {
     DisplayTimingData& displayData = mDisplayTimingData[displayId];
-    if (displayData.gpuEndFenceTime && !FlagManager::getInstance().adpf_gpu_sf()) {
+    if (displayData.gpuEndFenceTime && !supportsGpuReporting()) {
         nsecs_t signalTime = displayData.gpuEndFenceTime->getSignalTime();
         if (signalTime != Fence::SIGNAL_TIME_INVALID && signalTime != Fence::SIGNAL_TIME_PENDING) {
             displayData.lastValidGpuStartTime = displayData.gpuStartTime;
@@ -386,7 +391,7 @@
         }
     }
     displayData.gpuEndFenceTime = std::move(fenceTime);
-    if (!FlagManager::getInstance().adpf_gpu_sf()) {
+    if (!supportsGpuReporting()) {
         displayData.gpuStartTime = TimePoint::now();
     }
 }
@@ -549,9 +554,8 @@
             .timeStampNanos = TimePoint::now().ns(),
             .durationNanos = combinedDuration.ns(),
             .workPeriodStartTimestampNanos = mCommitStartTimes[0].ns(),
-            .cpuDurationNanos = FlagManager::getInstance().adpf_gpu_sf() ? cpuDuration.ns() : 0,
-            .gpuDurationNanos =
-                    FlagManager::getInstance().adpf_gpu_sf() ? estimatedGpuDuration.ns() : 0,
+            .cpuDurationNanos = supportsGpuReporting() ? cpuDuration.ns() : 0,
+            .gpuDurationNanos = supportsGpuReporting() ? estimatedGpuDuration.ns() : 0,
     };
     if (sTraceHintSessionData) {
         ATRACE_INT64("Idle duration", idleDuration.ns());
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
index 60967b0..161ca63 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
@@ -59,6 +59,7 @@
     // set before onBootFinished, which gates all methods that run on threads other than SF main
     virtual bool usePowerHintSession() = 0;
     virtual bool supportsPowerHintSession() = 0;
+    virtual bool supportsGpuReporting() = 0;
 
     // Sends a power hint that updates to the target work duration for the frame
     virtual void updateTargetWorkDuration(Duration targetDuration) = 0;
@@ -122,6 +123,7 @@
     bool isUsingExpensiveRendering() override { return mNotifiedExpensiveRendering; };
     bool usePowerHintSession() override;
     bool supportsPowerHintSession() override;
+    bool supportsGpuReporting() override;
     void updateTargetWorkDuration(Duration targetDuration) override;
     void reportActualWorkDuration() override;
     void enablePowerHintSession(bool enabled) override;
@@ -231,7 +233,6 @@
     // There are two different targets and actual work durations we care about,
     // this normalizes them together and takes the max of the two
     Duration combineTimingEstimates(Duration totalDuration, Duration flingerDuration);
-
     // Whether to use the new "createHintSessionWithConfig" method
     bool shouldCreateSessionWithConfig() REQUIRES(mHintSessionMutex);
 
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
index 821ac0c..0dcbb3c 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
@@ -153,7 +153,7 @@
         out << prefix + (isLastChild ? "└─ " : "├─ ");
         if (variant == LayerHierarchy::Variant::Relative) {
             out << "(Relative) ";
-        } else if (variant == LayerHierarchy::Variant::Mirror) {
+        } else if (LayerHierarchy::isMirror(variant)) {
             if (!includeMirroredHierarchy) {
                 out << "(Mirroring) " << *mLayer << "\n" + prefix + "   └─ ...";
                 return;
@@ -289,6 +289,12 @@
         LayerHierarchy* mirror = getHierarchyFromId(mirrorId);
         hierarchy->addChild(mirror, LayerHierarchy::Variant::Mirror);
     }
+    if (FlagManager::getInstance().detached_mirror()) {
+        if (layer->layerIdToMirror != UNASSIGNED_LAYER_ID) {
+            LayerHierarchy* mirror = getHierarchyFromId(layer->layerIdToMirror);
+            hierarchy->addChild(mirror, LayerHierarchy::Variant::Detached_Mirror);
+        }
+    }
 }
 
 void LayerHierarchyBuilder::onLayerDestroyed(RequestedLayerState* layer) {
@@ -325,7 +331,7 @@
     LayerHierarchy* hierarchy = getHierarchyFromId(layer->id);
     auto it = hierarchy->mChildren.begin();
     while (it != hierarchy->mChildren.end()) {
-        if (it->second == LayerHierarchy::Variant::Mirror) {
+        if (LayerHierarchy::isMirror(it->second)) {
             it = hierarchy->mChildren.erase(it);
         } else {
             it++;
@@ -335,6 +341,12 @@
     for (uint32_t mirrorId : layer->mirrorIds) {
         hierarchy->addChild(getHierarchyFromId(mirrorId), LayerHierarchy::Variant::Mirror);
     }
+    if (FlagManager::getInstance().detached_mirror()) {
+        if (layer->layerIdToMirror != UNASSIGNED_LAYER_ID) {
+            hierarchy->addChild(getHierarchyFromId(layer->layerIdToMirror),
+                                LayerHierarchy::Variant::Detached_Mirror);
+        }
+    }
 }
 
 void LayerHierarchyBuilder::doUpdate(
@@ -501,7 +513,7 @@
     // stored to reset the id upon destruction.
     traversalPath.id = layerId;
     traversalPath.variant = variant;
-    if (variant == LayerHierarchy::Variant::Mirror) {
+    if (LayerHierarchy::isMirror(variant)) {
         traversalPath.mirrorRootIds.emplace_back(mParentPath.id);
     } else if (variant == LayerHierarchy::Variant::Relative) {
         if (std::find(traversalPath.relativeRootIds.begin(), traversalPath.relativeRootIds.end(),
@@ -516,7 +528,7 @@
 LayerHierarchy::ScopedAddToTraversalPath::~ScopedAddToTraversalPath() {
     // Reset the traversal id to its original parent state using the state that was saved in
     // the constructor.
-    if (mTraversalPath.variant == LayerHierarchy::Variant::Mirror) {
+    if (LayerHierarchy::isMirror(mTraversalPath.variant)) {
         mTraversalPath.mirrorRootIds.pop_back();
     } else if (mTraversalPath.variant == LayerHierarchy::Variant::Relative) {
         mTraversalPath.relativeRootIds.pop_back();
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
index a1c73c3..f62e758 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.h
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
@@ -35,6 +35,7 @@
 // Detached - child of the parent but currently relative parented to another layer
 // Relative - relative child of the parent
 // Mirror - mirrored from another layer
+// Detached_Mirror - mirrored from another layer, ignoring local transform
 //
 // By representing the hierarchy as a graph, we can represent mirrored layer hierarchies without
 // cloning the layer requested state. The mirrored hierarchy and its corresponding
@@ -43,13 +44,18 @@
 class LayerHierarchy {
 public:
     enum Variant : uint32_t {
-        Attached, // child of the parent
-        Detached, // child of the parent but currently relative parented to another layer
-        Relative, // relative child of the parent
-        Mirror,   // mirrored from another layer
+        Attached,        // child of the parent
+        Detached,        // child of the parent but currently relative parented to another layer
+        Relative,        // relative child of the parent
+        Mirror,          // mirrored from another layer
+        Detached_Mirror, // mirrored from another layer, ignoring local transform
         ftl_first = Attached,
-        ftl_last = Mirror,
+        ftl_last = Detached_Mirror,
     };
+    static inline bool isMirror(Variant variant) {
+        return ((variant == Mirror) || (variant == Detached_Mirror));
+    }
+
     // Represents a unique path to a node.
     // The layer hierarchy is represented as a graph. Each node can be visited by multiple parents.
     // This allows us to represent mirroring in an efficient way. See the example below:
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
index 0983e7c..4b0618e 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
@@ -72,8 +72,10 @@
             // Check if we are mirroring a single layer, and if so add it to the list of children
             // to be mirrored.
             layer.layerIdToMirror = linkLayer(layer.layerIdToMirror, layer.id);
-            if (layer.layerIdToMirror != UNASSIGNED_LAYER_ID) {
-                layer.mirrorIds.emplace_back(layer.layerIdToMirror);
+            if (!FlagManager::getInstance().detached_mirror()) {
+                if (layer.layerIdToMirror != UNASSIGNED_LAYER_ID) {
+                    layer.mirrorIds.emplace_back(layer.layerIdToMirror);
+                }
             }
         }
         layer.touchCropId = linkLayer(layer.touchCropId, layer.id);
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
index ea06cf6..70e3c64 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
@@ -127,9 +127,8 @@
     pid = state.ownerPid;
     changes = RequestedLayerState::Changes::Created;
     clientChanges = 0;
-    mirrorRootPath = path.variant == LayerHierarchy::Variant::Mirror
-            ? path
-            : LayerHierarchy::TraversalPath::ROOT;
+    mirrorRootPath =
+            LayerHierarchy::isMirror(path.variant) ? path : LayerHierarchy::TraversalPath::ROOT;
     reachablilty = LayerSnapshot::Reachablilty::Unreachable;
     frameRateSelectionPriority = state.frameRateSelectionPriority;
     layerMetadata = state.metadata;
@@ -472,13 +471,14 @@
         geomContentCrop = requested.getBufferCrop();
     }
 
-    if (forceUpdate ||
-        requested.what &
-                (layer_state_t::eFlagsChanged | layer_state_t::eDestinationFrameChanged |
-                 layer_state_t::ePositionChanged | layer_state_t::eMatrixChanged |
-                 layer_state_t::eBufferTransformChanged |
-                 layer_state_t::eTransformToDisplayInverseChanged) ||
-        requested.changes.test(RequestedLayerState::Changes::BufferSize) || displayChanges) {
+    if ((forceUpdate ||
+         requested.what &
+                 (layer_state_t::eFlagsChanged | layer_state_t::eDestinationFrameChanged |
+                  layer_state_t::ePositionChanged | layer_state_t::eMatrixChanged |
+                  layer_state_t::eBufferTransformChanged |
+                  layer_state_t::eTransformToDisplayInverseChanged) ||
+         requested.changes.test(RequestedLayerState::Changes::BufferSize) || displayChanges) &&
+        !ignoreLocalTransform) {
         localTransform = requested.getTransform(displayRotationFlags);
         localTransformInverse = localTransform.inverse();
     }
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
index 73ee22f..eef8dff 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -80,6 +80,9 @@
     ui::Transform localTransformInverse;
     gui::WindowInfo inputInfo;
     ui::Transform localTransform;
+    // set to true if this snapshot will ignore local transforms. Used when the snapshot
+    // is a mirror root
+    bool ignoreLocalTransform;
     gui::DropInputMode dropInputMode;
     bool isTrustedOverlay;
     gui::GameMode gameMode;
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index 7daeefe..a2b5329 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -575,9 +575,11 @@
     mSnapshots.emplace_back(std::make_unique<LayerSnapshot>(layer, path));
     LayerSnapshot* snapshot = mSnapshots.back().get();
     snapshot->globalZ = static_cast<size_t>(mSnapshots.size()) - 1;
-    if (path.isClone() && path.variant != LayerHierarchy::Variant::Mirror) {
+    if (path.isClone() && !LayerHierarchy::isMirror(path.variant)) {
         snapshot->mirrorRootPath = parentSnapshot.mirrorRootPath;
     }
+    snapshot->ignoreLocalTransform =
+            path.isClone() && path.variant == LayerHierarchy::Variant::Detached_Mirror;
     mPathToSnapshot[path] = snapshot;
 
     mIdToSnapshots.emplace(path.id, snapshot);
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 073bad3..0137a7b 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -150,7 +150,7 @@
         mWindowType(static_cast<WindowInfo::Type>(
                 args.metadata.getInt32(gui::METADATA_WINDOW_TYPE, 0))),
         mLayerCreationFlags(args.flags),
-        mLegacyLayerFE(args.flinger->getFactory().createLayerFE(mName)) {
+        mLegacyLayerFE(args.flinger->getFactory().createLayerFE(mName, this)) {
     ALOGV("Creating Layer %s", getDebugName());
 
     uint32_t layerFlags = 0;
@@ -1549,10 +1549,6 @@
     return usage;
 }
 
-void Layer::skipReportingTransformHint() {
-    mSkipReportingTransformHint = true;
-}
-
 void Layer::updateTransformHint(ui::Transform::RotationFlags transformHint) {
     if (mFlinger->mDebugDisableTransformHint || transformHint & ui::Transform::ROT_INVALID) {
         transformHint = ui::Transform::ROT_0;
@@ -2618,19 +2614,6 @@
     return outputLayer ? outputLayer->getState().visibleRegion : Region();
 }
 
-void Layer::setInitialValuesForClone(const sp<Layer>& clonedFrom, uint32_t mirrorRootId) {
-    if (mFlinger->mLayerLifecycleManagerEnabled) return;
-    mSnapshot->path.id = clonedFrom->getSequence();
-    mSnapshot->path.mirrorRootIds.emplace_back(mirrorRootId);
-
-    cloneDrawingState(clonedFrom.get());
-    mClonedFrom = clonedFrom;
-    mPremultipliedAlpha = clonedFrom->mPremultipliedAlpha;
-    mPotentialCursor = clonedFrom->mPotentialCursor;
-    mProtectedByApp = clonedFrom->mProtectedByApp;
-    updateCloneBufferInfo();
-}
-
 void Layer::updateCloneBufferInfo() {
     if (!isClone() || !isClonedFromAlive()) {
         return;
@@ -2730,7 +2713,7 @@
         }
         sp<Layer> clonedChild = clonedLayersMap[child];
         if (clonedChild == nullptr) {
-            clonedChild = child->createClone(mirrorRoot->getSequence());
+            clonedChild = child->createClone();
             clonedLayersMap[child] = clonedChild;
         }
         addChildToDrawing(clonedChild);
@@ -2976,13 +2959,7 @@
 
 void Layer::releasePendingBuffer(nsecs_t dequeueReadyTime) {
     for (const auto& handle : mDrawingState.callbackHandles) {
-        if (mFlinger->mLayerLifecycleManagerEnabled) {
-            handle->transformHint = mTransformHint;
-        } else {
-            handle->transformHint = mSkipReportingTransformHint
-                    ? std::nullopt
-                    : std::make_optional<uint32_t>(mTransformHintLegacy);
-        }
+        handle->transformHint = mTransformHint;
         handle->dequeueReadyTime = dequeueReadyTime;
         handle->currentMaxAcquiredBufferCount =
                 mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mOwnerUid);
@@ -3241,10 +3218,6 @@
     mFlinger->mTimeStats->setPostTime(layerId, mDrawingState.frameNumber, getName().c_str(),
                                       mOwnerUid, postTime, getGameMode());
 
-    if (mFlinger->mLegacyFrontEndEnabled) {
-        recordLayerHistoryBufferUpdate(getLayerProps(), systemTime());
-    }
-
     setFrameTimelineVsyncForBufferTransaction(info, postTime);
 
     if (dequeueTime && *dequeueTime != 0) {
@@ -3706,11 +3679,10 @@
     }
 }
 
-sp<Layer> Layer::createClone(uint32_t mirrorRootId) {
+sp<Layer> Layer::createClone() {
     surfaceflinger::LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0,
                                            LayerMetadata());
     sp<Layer> layer = mFlinger->getFactory().createBufferStateLayer(args);
-    layer->setInitialValuesForClone(sp<Layer>::fromExisting(this), mirrorRootId);
     return layer;
 }
 
@@ -4006,7 +3978,7 @@
 }
 
 sp<LayerFE> Layer::copyCompositionEngineLayerFE() const {
-    auto result = mFlinger->getFactory().createLayerFE(mName);
+    auto result = mFlinger->getFactory().createLayerFE(mName, this);
     result->mSnapshot = std::make_unique<LayerSnapshot>(*mSnapshot);
     return result;
 }
@@ -4018,7 +3990,7 @@
             return layerFE;
         }
     }
-    auto layerFE = mFlinger->getFactory().createLayerFE(mName);
+    auto layerFE = mFlinger->getFactory().createLayerFE(mName, this);
     mLayerFEs.emplace_back(path, layerFE);
     return layerFE;
 }
@@ -4326,7 +4298,6 @@
     if (mTransformHintLegacy == ui::Transform::ROT_INVALID) {
         mTransformHintLegacy = displayTransformHint;
     }
-    mSkipReportingTransformHint = false;
 }
 
 const std::shared_ptr<renderengine::ExternalTexture>& Layer::getExternalTexture() const {
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index c094aa1..9db7664 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -250,7 +250,7 @@
     // true if this layer is visible, false otherwise
     virtual bool isVisible() const;
 
-    virtual sp<Layer> createClone(uint32_t mirrorRoot);
+    virtual sp<Layer> createClone();
 
     // Set a 2x2 transformation matrix on the layer. This transform
     // will be applied after parent transforms, but before any final
@@ -698,7 +698,6 @@
      * Sets display transform hint on BufferLayerConsumer.
      */
     void updateTransformHint(ui::Transform::RotationFlags);
-    void skipReportingTransformHint();
     inline const State& getDrawingState() const { return mDrawingState; }
     inline State& getDrawingState() { return mDrawingState; }
 
@@ -977,7 +976,6 @@
     friend class TransactionFrameTracerTest;
     friend class TransactionSurfaceFrameTest;
 
-    virtual void setInitialValuesForClone(const sp<Layer>& clonedFrom, uint32_t mirrorRootId);
     void preparePerFrameCompositionState();
     void preparePerFrameBufferCompositionState();
     void preparePerFrameEffectsCompositionState();
@@ -1259,7 +1257,6 @@
     // Transform hint provided to the producer. This must be accessed holding
     // the mStateLock.
     ui::Transform::RotationFlags mTransformHintLegacy = ui::Transform::ROT_0;
-    bool mSkipReportingTransformHint = true;
     std::optional<ui::Transform::RotationFlags> mTransformHint = std::nullopt;
 
     ReleaseCallbackId mPreviousReleaseCallbackId = ReleaseCallbackId::INVALID_ID;
diff --git a/services/surfaceflinger/LayerProtoHelper.cpp b/services/surfaceflinger/LayerProtoHelper.cpp
index aa6026e..513c943 100644
--- a/services/surfaceflinger/LayerProtoHelper.cpp
+++ b/services/surfaceflinger/LayerProtoHelper.cpp
@@ -334,7 +334,7 @@
                                                                           variant);
         frontend::LayerSnapshot* childSnapshot = getSnapshot(path, layer);
         if (variant == Variant::Attached || variant == Variant::Detached ||
-            variant == Variant::Mirror) {
+            frontend::LayerHierarchy::isMirror(variant)) {
             mChildToParent[childSnapshot->uniqueSequence] = snapshot->uniqueSequence;
             layerProto->add_children(childSnapshot->uniqueSequence);
         } else if (variant == Variant::Relative) {
diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp
index 51d4ff8..c25ddb6 100644
--- a/services/surfaceflinger/LayerRenderArea.cpp
+++ b/services/surfaceflinger/LayerRenderArea.cpp
@@ -24,31 +24,17 @@
 #include "SurfaceFlinger.h"
 
 namespace android {
-namespace {
 
-void reparentForDrawing(const sp<Layer>& oldParent, const sp<Layer>& newParent,
-                   const Rect& drawingBounds) {
-        // Compute and cache the bounds for the new parent layer.
-        newParent->computeBounds(drawingBounds.toFloatRect(), ui::Transform(),
-            0.f /* shadowRadius */);
-        newParent->updateSnapshot(true /* updateGeometry */);
-        oldParent->setChildrenDrawingParent(newParent);
-};
-
-} // namespace
-
-LayerRenderArea::LayerRenderArea(SurfaceFlinger& flinger, sp<Layer> layer, const Rect& crop,
-                                 ui::Size reqSize, ui::Dataspace reqDataSpace, bool childrenOnly,
-                                 bool allowSecureLayers, const ui::Transform& layerTransform,
-                                 const Rect& layerBufferSize, bool hintForSeamlessTransition)
+LayerRenderArea::LayerRenderArea(sp<Layer> layer, const Rect& crop, ui::Size reqSize,
+                                 ui::Dataspace reqDataSpace, bool allowSecureLayers,
+                                 const ui::Transform& layerTransform, const Rect& layerBufferSize,
+                                 bool hintForSeamlessTransition)
       : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, hintForSeamlessTransition,
                    allowSecureLayers),
         mLayer(std::move(layer)),
-        mLayerTransform(layerTransform),
         mLayerBufferSize(layerBufferSize),
         mCrop(crop),
-        mFlinger(flinger),
-        mChildrenOnly(childrenOnly) {}
+        mTransform(layerTransform) {}
 
 const ui::Transform& LayerRenderArea::getTransform() const {
     return mTransform;
@@ -71,53 +57,4 @@
     }
 }
 
-void LayerRenderArea::render(std::function<void()> drawLayers) {
-    using namespace std::string_literals;
-
-    if (!mChildrenOnly) {
-        mTransform = mLayerTransform.inverse();
-    }
-
-    if (mFlinger.mLayerLifecycleManagerEnabled) {
-        drawLayers();
-        return;
-    }
-    // If layer is offscreen, update mirroring info if it exists
-    if (mLayer->isRemovedFromCurrentState()) {
-        mLayer->traverse(LayerVector::StateSet::Drawing,
-                         [&](Layer* layer) { layer->updateMirrorInfo({}); });
-        mLayer->traverse(LayerVector::StateSet::Drawing,
-                         [&](Layer* layer) { layer->updateCloneBufferInfo(); });
-    }
-
-    if (!mChildrenOnly) {
-        // If the layer is offscreen, compute bounds since we don't compute bounds for offscreen
-        // layers in a regular cycles.
-        if (mLayer->isRemovedFromCurrentState()) {
-            FloatRect maxBounds = mFlinger.getMaxDisplayBounds();
-            mLayer->computeBounds(maxBounds, ui::Transform(), 0.f /* shadowRadius */);
-        }
-        drawLayers();
-    } else {
-        // In the "childrenOnly" case we reparent the children to a screenshot
-        // layer which has no properties set and which does not draw.
-        //  We hold the statelock as the reparent-for-drawing operation modifies the
-        //  hierarchy and there could be readers on Binder threads, like dump.
-        auto screenshotParentLayer = mFlinger.getFactory().createEffectLayer(
-                {&mFlinger, nullptr, "Screenshot Parent"s, ISurfaceComposerClient::eNoColorFill,
-                 LayerMetadata()});
-        {
-            Mutex::Autolock _l(mFlinger.mStateLock);
-            reparentForDrawing(mLayer, screenshotParentLayer, getSourceCrop());
-        }
-        drawLayers();
-        {
-            Mutex::Autolock _l(mFlinger.mStateLock);
-            mLayer->setChildrenDrawingParent(mLayer);
-        }
-    }
-    mLayer->updateSnapshot(/*updateGeometry=*/true);
-    mLayer->updateChildrenSnapshots(/*updateGeometry=*/true);
-}
-
 } // namespace android
diff --git a/services/surfaceflinger/LayerRenderArea.h b/services/surfaceflinger/LayerRenderArea.h
index aa609ee..b12afe8 100644
--- a/services/surfaceflinger/LayerRenderArea.h
+++ b/services/surfaceflinger/LayerRenderArea.h
@@ -32,29 +32,23 @@
 
 class LayerRenderArea : public RenderArea {
 public:
-    LayerRenderArea(SurfaceFlinger& flinger, sp<Layer> layer, const Rect& crop, ui::Size reqSize,
-                    ui::Dataspace reqDataSpace, bool childrenOnly, bool allowSecureLayers,
-                    const ui::Transform& layerTransform, const Rect& layerBufferSize,
-                    bool hintForSeamlessTransition);
+    LayerRenderArea(sp<Layer> layer, const Rect& crop, ui::Size reqSize, ui::Dataspace reqDataSpace,
+                    bool allowSecureLayers, const ui::Transform& layerTransform,
+                    const Rect& layerBufferSize, bool hintForSeamlessTransition);
 
     const ui::Transform& getTransform() const override;
     bool isSecure() const override;
     sp<const DisplayDevice> getDisplayDevice() const override;
     Rect getSourceCrop() const override;
 
-    void render(std::function<void()> drawLayers) override;
     virtual sp<Layer> getParentLayer() const { return mLayer; }
 
 private:
     const sp<Layer> mLayer;
-    const ui::Transform mLayerTransform;
     const Rect mLayerBufferSize;
     const Rect mCrop;
 
     ui::Transform mTransform;
-
-    SurfaceFlinger& mFlinger;
-    const bool mChildrenOnly;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/OWNERS b/services/surfaceflinger/OWNERS
index 0aee7d4..ffc1dd7 100644
--- a/services/surfaceflinger/OWNERS
+++ b/services/surfaceflinger/OWNERS
@@ -2,7 +2,6 @@
 
 adyabr@google.com
 alecmouri@google.com
-chaviw@google.com
 domlaskowski@google.com
 jreck@google.com
 lpy@google.com
@@ -10,5 +9,6 @@
 racarr@google.com
 ramindani@google.com
 rnlee@google.com
+sallyqi@google.com
 scroggo@google.com
 vishnun@google.com
diff --git a/services/surfaceflinger/RenderArea.h b/services/surfaceflinger/RenderArea.h
index 5de148e..18a5304 100644
--- a/services/surfaceflinger/RenderArea.h
+++ b/services/surfaceflinger/RenderArea.h
@@ -47,9 +47,6 @@
 
     virtual ~RenderArea() = default;
 
-    // Invoke drawLayers to render layers into the render area.
-    virtual void render(std::function<void()> drawLayers) { drawLayers(); }
-
     // Returns true if the render area is secure.  A secure layer should be
     // blacked out / skipped when rendered to an insecure render area.
     virtual bool isSecure() const = 0;
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 96eccf2..6b65449 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -235,7 +235,8 @@
         ParcelableVsyncEventData* outVsyncEventData) {
     ATRACE_CALL();
     outVsyncEventData->vsync =
-            mEventThread->getLatestVsyncEventData(sp<EventThreadConnection>::fromExisting(this));
+            mEventThread->getLatestVsyncEventData(sp<EventThreadConnection>::fromExisting(this),
+                                                  systemTime());
     return binder::Status::ok();
 }
 
@@ -387,8 +388,8 @@
     }
 }
 
-VsyncEventData EventThread::getLatestVsyncEventData(
-        const sp<EventThreadConnection>& connection) const {
+VsyncEventData EventThread::getLatestVsyncEventData(const sp<EventThreadConnection>& connection,
+                                                    nsecs_t now) const {
     // Resync so that the vsync is accurate with hardware. getLatestVsyncEventData is an alternate
     // way to get vsync data (instead of posting callbacks to Choreographer).
     mCallback.resync();
@@ -399,11 +400,10 @@
     const auto [presentTime, deadline] = [&]() -> std::pair<nsecs_t, nsecs_t> {
         std::lock_guard<std::mutex> lock(mMutex);
         const auto vsyncTime = mVsyncSchedule->getTracker().nextAnticipatedVSyncTimeFrom(
-                systemTime() + mWorkDuration.get().count() + mReadyDuration.count());
+                now + mWorkDuration.get().count() + mReadyDuration.count());
         return {vsyncTime, vsyncTime - mReadyDuration.count()};
     }();
-    generateFrameTimeline(vsyncEventData, frameInterval.ns(), systemTime(SYSTEM_TIME_MONOTONIC),
-                          presentTime, deadline);
+    generateFrameTimeline(vsyncEventData, frameInterval.ns(), now, presentTime, deadline);
     if (FlagManager::getInstance().vrr_config()) {
         mCallback.onExpectedPresentTimePosted(TimePoint::fromNs(presentTime));
     }
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index 90e61a9..f772126 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -127,8 +127,8 @@
     virtual void setVsyncRate(uint32_t rate, const sp<EventThreadConnection>& connection) = 0;
     // Requests the next vsync. If resetIdleTimer is set to true, it resets the idle timer.
     virtual void requestNextVsync(const sp<EventThreadConnection>& connection) = 0;
-    virtual VsyncEventData getLatestVsyncEventData(
-            const sp<EventThreadConnection>& connection) const = 0;
+    virtual VsyncEventData getLatestVsyncEventData(const sp<EventThreadConnection>& connection,
+                                                   nsecs_t now) const = 0;
 
     virtual void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) = 0;
 
@@ -160,8 +160,8 @@
     status_t registerDisplayEventConnection(const sp<EventThreadConnection>& connection) override;
     void setVsyncRate(uint32_t rate, const sp<EventThreadConnection>& connection) override;
     void requestNextVsync(const sp<EventThreadConnection>& connection) override;
-    VsyncEventData getLatestVsyncEventData(
-            const sp<EventThreadConnection>& connection) const override;
+    VsyncEventData getLatestVsyncEventData(const sp<EventThreadConnection>& connection,
+                                           nsecs_t now) const override;
 
     void enableSyntheticVsync(bool) override;
 
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 974c837..a819b79 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -280,9 +280,18 @@
                     case Layer::FrameRateCompatibility::Exact:
                         return LayerVoteType::ExplicitExact;
                     case Layer::FrameRateCompatibility::Gte:
-                        return LayerVoteType::ExplicitGte;
+                        if (isVrrDevice) {
+                            return LayerVoteType::ExplicitGte;
+                        } else {
+                            // For MRR, treat GTE votes as Max because it is used for animations and
+                            // scroll. MRR cannot change frame rate without jank, so it should
+                            // prefer smoothness.
+                            return LayerVoteType::Max;
+                        }
                 }
             }();
+            const bool isValuelessVote = voteType == LayerVoteType::NoVote ||
+                    voteType == LayerVoteType::Min || voteType == LayerVoteType::Max;
 
             if (FlagManager::getInstance().game_default_frame_rate()) {
                 // Determine the layer frame rate considering the following priorities:
@@ -307,7 +316,8 @@
                               gameModeFrameRateOverride.getIntValue());
                     }
                 } else if (frameRate.isValid() && frameRate.isVoteValidForMrr(isVrrDevice)) {
-                    info->setLayerVote({setFrameRateVoteType, frameRate.vote.rate,
+                    info->setLayerVote({setFrameRateVoteType,
+                                        isValuelessVote ? 0_Hz : frameRate.vote.rate,
                                         frameRate.vote.seamlessness, frameRate.category});
                     if (CC_UNLIKELY(mTraceEnabled)) {
                         trace(*info, gameFrameRateOverrideVoteType,
@@ -335,8 +345,8 @@
             } else {
                 if (frameRate.isValid() && frameRate.isVoteValidForMrr(isVrrDevice)) {
                     const auto type = info->isVisible() ? voteType : LayerVoteType::NoVote;
-                    info->setLayerVote({type, frameRate.vote.rate, frameRate.vote.seamlessness,
-                                        frameRate.category});
+                    info->setLayerVote({type, isValuelessVote ? 0_Hz : frameRate.vote.rate,
+                                        frameRate.vote.seamlessness, frameRate.category});
                 } else {
                     if (!frameRate.isVoteValidForMrr(isVrrDevice)) {
                         ATRACE_FORMAT_INSTANT("Reset layer to ignore explicit vote on MRR %s: %s "
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 1bc4ac2..632f42a 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -55,10 +55,10 @@
                                    bool pendingModeChange, const LayerProps& props) {
     lastPresentTime = std::max(lastPresentTime, static_cast<nsecs_t>(0));
 
-    mLastUpdatedTime = std::max(lastPresentTime, now);
     *mLayerProps = props;
     switch (updateType) {
         case LayerUpdateType::AnimationTX:
+            mLastUpdatedTime = std::max(lastPresentTime, now);
             mLastAnimationTime = std::max(lastPresentTime, now);
             break;
         case LayerUpdateType::SetFrameRate:
@@ -67,6 +67,7 @@
             }
             FALLTHROUGH_INTENDED;
         case LayerUpdateType::Buffer:
+            mLastUpdatedTime = std::max(lastPresentTime, now);
             FrameTimeData frameTime = {.presentTime = lastPresentTime,
                                        .queueTime = mLastUpdatedTime,
                                        .pendingModeChange = pendingModeChange,
@@ -180,19 +181,19 @@
 bool LayerInfo::hasEnoughDataForHeuristic() const {
     // The layer had to publish at least HISTORY_SIZE or HISTORY_DURATION of updates
     if (mFrameTimes.size() < 2) {
-        ALOGV("fewer than 2 frames recorded: %zu", mFrameTimes.size());
+        ALOGV("%s fewer than 2 frames recorded: %zu", mName.c_str(), mFrameTimes.size());
         return false;
     }
 
     if (!isFrameTimeValid(mFrameTimes.front())) {
-        ALOGV("stale frames still captured");
+        ALOGV("%s stale frames still captured", mName.c_str());
         return false;
     }
 
     const auto totalDuration = mFrameTimes.back().queueTime - mFrameTimes.front().queueTime;
     if (mFrameTimes.size() < HISTORY_SIZE && totalDuration < HISTORY_DURATION.count()) {
-        ALOGV("not enough frames captured: %zu | %.2f seconds", mFrameTimes.size(),
-              totalDuration / 1e9f);
+        ALOGV("%s not enough frames captured: %zu | %.2f seconds", mName.c_str(),
+              mFrameTimes.size(), totalDuration / 1e9f);
         return false;
     }
 
@@ -364,6 +365,8 @@
     }
 
     if (frequent.clearHistory) {
+        ATRACE_FORMAT_INSTANT("frequent.clearHistory");
+        ALOGV("%s frequent.clearHistory", mName.c_str());
         clearHistory(now);
     }
 
@@ -562,8 +565,25 @@
     return vote.type == FrameRateCompatibility::NoVote;
 }
 
+bool LayerInfo::FrameRate::isValuelessType() const {
+    // For a valueless frame rate compatibility (type), the frame rate should be unspecified (0 Hz).
+    if (!isApproxEqual(vote.rate, 0_Hz)) {
+        return false;
+    }
+    switch (vote.type) {
+        case FrameRateCompatibility::Min:
+        case FrameRateCompatibility::NoVote:
+            return true;
+        case FrameRateCompatibility::Default:
+        case FrameRateCompatibility::ExactOrMultiple:
+        case FrameRateCompatibility::Exact:
+        case FrameRateCompatibility::Gte:
+            return false;
+    }
+}
+
 bool LayerInfo::FrameRate::isValid() const {
-    return isNoVote() || vote.rate.isValid() || category != FrameRateCategory::Default;
+    return isValuelessType() || vote.rate.isValid() || category != FrameRateCategory::Default;
 }
 
 bool LayerInfo::FrameRate::isVoteValidForMrr(bool isVrrDevice) const {
@@ -571,7 +591,7 @@
         return true;
     }
 
-    if (category == FrameRateCategory::Default && vote.type != FrameRateCompatibility::Gte) {
+    if (category == FrameRateCategory::Default) {
         return true;
     }
 
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index 40903ed..a7847bc 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -146,6 +146,9 @@
         // selection.
         bool isNoVote() const;
 
+        // Returns true if the FrameRate has a valid valueless (0 Hz) frame rate type.
+        bool isValuelessType() const;
+
         // Checks whether the given FrameRate's vote specifications is valid for MRR devices
         // given the current flagging.
         bool isVoteValidForMrr(bool isVrrDevice) const;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index c83d81f..005ec05 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -663,13 +663,7 @@
 
 void Scheduler::recordLayerHistory(int32_t id, const LayerProps& layerProps, nsecs_t presentTime,
                                    nsecs_t now, LayerHistory::LayerUpdateType updateType) {
-    const auto& selectorPtr = pacesetterSelectorPtr();
-    // Skip recording layer history on LayerUpdateType::SetFrameRate for MRR devices when the
-    // dVRR vote types are guarded (disabled) for MRR. This is to avoid activity when setting dVRR
-    // vote types.
-    if (selectorPtr->canSwitch() &&
-        (updateType != LayerHistory::LayerUpdateType::SetFrameRate ||
-         layerProps.setFrameRateVote.isVoteValidForMrr(selectorPtr->isVrrDevice()))) {
+    if (pacesetterSelectorPtr()->canSwitch()) {
         mLayerHistory.record(id, layerProps, presentTime, now, updateType);
     }
 }
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
index d6a3f62..d37d2dc 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
@@ -33,6 +33,7 @@
 // TODO(b/185536303): Pull to FTL.
 #include "../../../TracedOrdinal.h"
 #include "../../../Utils/Dumper.h"
+#include "../../../Utils/RingBuffer.h"
 
 namespace android::scheduler {
 
@@ -61,7 +62,7 @@
     // VSYNC of at least one previous frame has not yet passed. In other words, this is NOT the
     // `presentFenceForPreviousFrame` if running N VSYNCs ahead, but the one that should have been
     // signaled by now (unless that frame missed).
-    const FenceTimePtr& presentFenceForPastVsync(Period minFramePeriod) const;
+    FenceTimePtr presentFenceForPastVsync(Period minFramePeriod) const;
 
     // Equivalent to `presentFenceForPastVsync` unless running N VSYNCs ahead.
     const FenceTimePtr& presentFenceForPreviousFrame() const {
@@ -84,6 +85,12 @@
         return mExpectedPresentTime - minFramePeriod;
     }
 
+    void addFence(sp<Fence> presentFence, FenceTimePtr presentFenceTime,
+                  TimePoint expectedPresentTime) {
+        mFenceWithFenceTimes.next() = {std::move(presentFence), presentFenceTime,
+                                       expectedPresentTime};
+    }
+
     VsyncId mVsyncId;
     TimePoint mFrameBeginTime;
     TimePoint mExpectedPresentTime;
@@ -97,8 +104,11 @@
     struct FenceWithFenceTime {
         sp<Fence> fence = Fence::NO_FENCE;
         FenceTimePtr fenceTime = FenceTime::NO_FENCE;
+        TimePoint expectedPresentTime = TimePoint();
     };
     std::array<FenceWithFenceTime, 2> mPresentFences;
+    utils::RingBuffer<FenceWithFenceTime, 5> mFenceWithFenceTimes;
+
     TimePoint mLastSignaledFrameTime;
 
 private:
@@ -109,6 +119,18 @@
         static_assert(N > 1);
         return expectedFrameDuration() > (N - 1) * minFramePeriod;
     }
+
+    const FenceTimePtr pastVsyncTimePtr() const {
+        auto pastFenceTimePtr = FenceTime::NO_FENCE;
+        for (size_t i = 0; i < mFenceWithFenceTimes.size(); i++) {
+            const auto& [_, fenceTimePtr, expectedPresentTime] = mFenceWithFenceTimes[i];
+            if (expectedPresentTime > mFrameBeginTime) {
+                return pastFenceTimePtr;
+            }
+            pastFenceTimePtr = fenceTimePtr;
+        }
+        return pastFenceTimePtr;
+    }
 };
 
 // Computes a display's per-frame metrics about past/upcoming targeting of present deadlines.
diff --git a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
index 8335568..badd21e 100644
--- a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
+++ b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
@@ -16,6 +16,7 @@
 
 #include <gui/TraceUtils.h>
 
+#include <common/FlagManager.h>
 #include <scheduler/FrameTargeter.h>
 #include <scheduler/IVsyncSource.h>
 
@@ -33,8 +34,10 @@
     return mExpectedPresentTime - Period::fromNs(minFramePeriod.ns() << shift);
 }
 
-const FenceTimePtr& FrameTarget::presentFenceForPastVsync(Period minFramePeriod) const {
-    // TODO(b/267315508): Generalize to N VSYNCs.
+FenceTimePtr FrameTarget::presentFenceForPastVsync(Period minFramePeriod) const {
+    if (FlagManager::getInstance().allow_n_vsyncs_in_targeter()) {
+        return pastVsyncTimePtr();
+    }
     const size_t i = static_cast<size_t>(targetsVsyncsAhead<2>(minFramePeriod));
     return mPresentFences[i].fenceTime;
 }
@@ -44,7 +47,8 @@
     // should use `TimePoint::now()` in case of delays since `mFrameBeginTime`.
 
     // TODO(b/267315508): Generalize to N VSYNCs.
-    if (targetsVsyncsAhead<3>(minFramePeriod)) {
+    const bool allowNVsyncs = FlagManager::getInstance().allow_n_vsyncs_in_targeter();
+    if (!allowNVsyncs && targetsVsyncsAhead<3>(minFramePeriod)) {
         return true;
     }
 
@@ -144,8 +148,12 @@
 }
 
 FenceTimePtr FrameTargeter::setPresentFence(sp<Fence> presentFence, FenceTimePtr presentFenceTime) {
-    mPresentFences[1] = mPresentFences[0];
-    mPresentFences[0] = {std::move(presentFence), presentFenceTime};
+    if (FlagManager::getInstance().allow_n_vsyncs_in_targeter()) {
+        addFence(std::move(presentFence), presentFenceTime, mExpectedPresentTime);
+    } else {
+        mPresentFences[1] = mPresentFences[0];
+        mPresentFences[0] = {std::move(presentFence), presentFenceTime, mExpectedPresentTime};
+    }
     return presentFenceTime;
 }
 
diff --git a/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
index 29711af..5448eec 100644
--- a/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
+++ b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
@@ -24,6 +24,7 @@
 
 #include <com_android_graphics_surfaceflinger_flags.h>
 
+using namespace com::android::graphics::surfaceflinger;
 using namespace std::chrono_literals;
 
 namespace android::scheduler {
@@ -168,6 +169,7 @@
 }
 
 TEST_F(FrameTargeterTest, recallsPastVsync) {
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
     VsyncId vsyncId{111};
     TimePoint frameBeginTime(1000ms);
     constexpr Fps kRefreshRate = 60_Hz;
@@ -184,6 +186,7 @@
 }
 
 TEST_F(FrameTargeterTest, recallsPastVsyncTwoVsyncsAhead) {
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
     VsyncId vsyncId{222};
     TimePoint frameBeginTime(2000ms);
     constexpr Fps kRefreshRate = 120_Hz;
@@ -203,8 +206,32 @@
     }
 }
 
+TEST_F(FrameTargeterTest, recallsPastNVsyncTwoVsyncsAhead) {
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, true);
+    VsyncId vsyncId{222};
+    TimePoint frameBeginTime(2000ms);
+    constexpr Fps kRefreshRate = 120_Hz;
+    constexpr Period kPeriod = kRefreshRate.getPeriod();
+    constexpr Duration kFrameDuration = 10ms;
+
+    FenceTimePtr previousFence = FenceTime::NO_FENCE;
+
+    for (int n = 5; n-- > 0;) {
+        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
+        const auto fence = frame.end();
+
+        const auto pastVsyncTime = frameBeginTime + kFrameDuration - 2 * kPeriod;
+        EXPECT_EQ(target().pastVsyncTime(kPeriod), pastVsyncTime);
+        EXPECT_EQ(target().presentFenceForPastVsync(kFrameDuration), previousFence);
+
+        frameBeginTime += kPeriod;
+        previousFence = fence;
+    }
+}
+
 TEST_F(FrameTargeterTest, recallsPastVsyncTwoVsyncsAheadVrr) {
-    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::vrr_config, true);
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
 
     VsyncId vsyncId{222};
     TimePoint frameBeginTime(2000ms);
@@ -227,6 +254,33 @@
     }
 }
 
+TEST_F(FrameTargeterTest, recallsPastNVsyncTwoVsyncsAheadVrr) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, true);
+
+    VsyncId vsyncId{222};
+    TimePoint frameBeginTime(2000ms);
+    constexpr Fps kRefreshRate = 120_Hz;
+    constexpr Fps kPeakRefreshRate = 240_Hz;
+    constexpr Period kPeriod = kRefreshRate.getPeriod();
+    constexpr Duration kFrameDuration = 10ms;
+
+    FenceTimePtr previousFence = FenceTime::NO_FENCE;
+
+    for (int n = 5; n-- > 0;) {
+        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate,
+                    kPeakRefreshRate);
+        const auto fence = frame.end();
+
+        const auto pastVsyncTime = frameBeginTime + kFrameDuration - 2 * kPeriod;
+        EXPECT_EQ(target().pastVsyncTime(kPeriod), pastVsyncTime);
+        EXPECT_EQ(target().presentFenceForPastVsync(kFrameDuration), previousFence);
+
+        frameBeginTime += kPeriod;
+        previousFence = fence;
+    }
+}
+
 TEST_F(FrameTargeterTest, doesNotDetectEarlyPresentIfNoFence) {
     constexpr Period kPeriod = (60_Hz).getPeriod();
     EXPECT_EQ(target().presentFenceForPastVsync(kPeriod), FenceTime::NO_FENCE);
@@ -234,6 +288,7 @@
 }
 
 TEST_F(FrameTargeterTest, detectsEarlyPresent) {
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
     VsyncId vsyncId{333};
     TimePoint frameBeginTime(3000ms);
     constexpr Fps kRefreshRate = 60_Hz;
@@ -263,6 +318,7 @@
 // Same as `detectsEarlyPresent`, above, but verifies that we do not set an earliest present time
 // when there is expected present time support.
 TEST_F(FrameTargeterWithExpectedPresentSupportTest, detectsEarlyPresent) {
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
     VsyncId vsyncId{333};
     TimePoint frameBeginTime(3000ms);
     constexpr Fps kRefreshRate = 60_Hz;
@@ -289,6 +345,7 @@
 }
 
 TEST_F(FrameTargeterTest, detectsEarlyPresentTwoVsyncsAhead) {
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
     VsyncId vsyncId{444};
     TimePoint frameBeginTime(4000ms);
     constexpr Fps kRefreshRate = 120_Hz;
@@ -320,7 +377,52 @@
               target().expectedPresentTime() - kPeriod - kHwcMinWorkDuration);
 }
 
+TEST_F(FrameTargeterTest, detectsEarlyPresentNVsyncsAhead) {
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, true);
+    VsyncId vsyncId{444};
+    TimePoint frameBeginTime(4000ms);
+    Fps refreshRate = 120_Hz;
+    Period period = refreshRate.getPeriod();
+
+    // The target is not early while past present fences are pending.
+    for (int n = 5; n-- > 0;) {
+        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, refreshRate, refreshRate);
+        EXPECT_FALSE(wouldPresentEarly(period));
+        EXPECT_FALSE(target().earliestPresentTime());
+    }
+
+    Frame frame(this, vsyncId++, frameBeginTime, 10ms, refreshRate, refreshRate);
+    auto fence = frame.end();
+    frameBeginTime += period;
+    fence->signalForTest(frameBeginTime.ns());
+
+    // The target is two VSYNCs ahead, so the past present fence is still pending.
+    EXPECT_FALSE(wouldPresentEarly(period));
+    EXPECT_FALSE(target().earliestPresentTime());
+
+    { const Frame frame(this, vsyncId++, frameBeginTime, 10ms, refreshRate, refreshRate); }
+
+    Frame oneEarlyPresentFrame(this, vsyncId++, frameBeginTime, 10ms, refreshRate, refreshRate);
+    // The target is early if the past present fence was signaled.
+    EXPECT_TRUE(wouldPresentEarly(period));
+    ASSERT_NE(std::nullopt, target().earliestPresentTime());
+    EXPECT_EQ(*target().earliestPresentTime(),
+              target().expectedPresentTime() - period - kHwcMinWorkDuration);
+
+    fence = oneEarlyPresentFrame.end();
+    frameBeginTime += period;
+    fence->signalForTest(frameBeginTime.ns());
+
+    // Change rate to track frame more than 2 vsyncs ahead
+    refreshRate = 144_Hz;
+    period = refreshRate.getPeriod();
+    Frame onePresentEarlyFrame(this, vsyncId++, frameBeginTime, 16ms, refreshRate, refreshRate);
+    // The target is not early as last frame as the past frame is tracked for pending.
+    EXPECT_FALSE(wouldPresentEarly(period));
+}
+
 TEST_F(FrameTargeterTest, detectsEarlyPresentThreeVsyncsAhead) {
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
     TimePoint frameBeginTime(5000ms);
     constexpr Fps kRefreshRate = 144_Hz;
     constexpr Period kPeriod = kRefreshRate.getPeriod();
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 81697da..ceda1e537 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -72,6 +72,7 @@
 #include <gui/TraceUtils.h>
 #include <hidl/ServiceManagement.h>
 #include <layerproto/LayerProtoParser.h>
+#include <linux/sched/types.h>
 #include <log/log.h>
 #include <private/android_filesystem_config.h>
 #include <private/gui/SyncFeatures.h>
@@ -530,8 +531,6 @@
 
     mLayerLifecycleManagerEnabled =
             base::GetBoolProperty("persist.debug.sf.enable_layer_lifecycle_manager"s, true);
-    mLegacyFrontEndEnabled = !mLayerLifecycleManagerEnabled ||
-            base::GetBoolProperty("persist.debug.sf.enable_legacy_frontend"s, false);
 
     // These are set by the HWC implementation to indicate that they will use the workarounds.
     mIsHotplugErrViaNegVsync =
@@ -808,11 +807,11 @@
                 .setGraphicsApi(renderengine::RenderEngine::GraphicsApi::VK);
     } else {
         const auto kVulkan = renderengine::RenderEngine::GraphicsApi::VK;
-        const bool canSupportVulkan = renderengine::RenderEngine::canSupport(kVulkan);
-        const bool useGraphite =
-                canSupportVulkan && FlagManager::getInstance().graphite_renderengine();
+        const bool useGraphite = FlagManager::getInstance().graphite_renderengine() &&
+                renderengine::RenderEngine::canSupport(kVulkan);
         const bool useVulkan = useGraphite ||
-                (canSupportVulkan && FlagManager::getInstance().vulkan_renderengine());
+                (FlagManager::getInstance().vulkan_renderengine() &&
+                 renderengine::RenderEngine::canSupport(kVulkan));
 
         builder.setSkiaBackend(useGraphite ? renderengine::RenderEngine::SkiaBackend::GRAPHITE
                                            : renderengine::RenderEngine::SkiaBackend::GANESH);
@@ -820,6 +819,23 @@
     }
 }
 
+/**
+ * Choose a suggested blurring algorithm if supportsBlur is true. By default Kawase will be
+ * suggested as it's faster than a full Gaussian blur and looks close enough.
+ */
+renderengine::RenderEngine::BlurAlgorithm chooseBlurAlgorithm(bool supportsBlur) {
+    if (!supportsBlur) {
+        return renderengine::RenderEngine::BlurAlgorithm::NONE;
+    }
+
+    auto const algorithm = base::GetProperty(PROPERTY_DEBUG_RENDERENGINE_BLUR_ALGORITHM, "");
+    if (algorithm == "gaussian") {
+        return renderengine::RenderEngine::BlurAlgorithm::GAUSSIAN;
+    } else {
+        return renderengine::RenderEngine::BlurAlgorithm::KAWASE;
+    }
+}
+
 void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) {
     ATRACE_CALL();
     ALOGI(  "SurfaceFlinger's main thread ready to run. "
@@ -835,7 +851,7 @@
                            .setImageCacheSize(maxFrameBufferAcquiredBuffers)
                            .setEnableProtectedContext(enable_protected_contents(false))
                            .setPrecacheToneMapperShaderOnly(false)
-                           .setSupportsBackgroundBlur(mSupportsBlur)
+                           .setBlurAlgorithm(chooseBlurAlgorithm(mSupportsBlur))
                            .setContextPriority(
                                    useContextPriority
                                            ? renderengine::RenderEngine::ContextPriority::REALTIME
@@ -2073,10 +2089,17 @@
 sp<IDisplayEventConnection> SurfaceFlinger::createDisplayEventConnection(
         gui::ISurfaceComposer::VsyncSource vsyncSource, EventRegistrationFlags eventRegistration,
         const sp<IBinder>& layerHandle) {
-    const auto cycle = vsyncSource == gui::ISurfaceComposer::VsyncSource::eVsyncSourceSurfaceFlinger
-            ? scheduler::Cycle::LastComposite
-            : scheduler::Cycle::Render;
+    const auto cycle = [&] {
+        if (FlagManager::getInstance().deprecate_vsync_sf()) {
+            ALOGW_IF(vsyncSource == gui::ISurfaceComposer::VsyncSource::eVsyncSourceSurfaceFlinger,
+                "requested unsupported config eVsyncSourceSurfaceFlinger");
+            return scheduler::Cycle::Render;
+        }
 
+        return vsyncSource == gui::ISurfaceComposer::VsyncSource::eVsyncSourceSurfaceFlinger
+              ? scheduler::Cycle::LastComposite
+              : scheduler::Cycle::Render;
+    }();
     return mScheduler->createDisplayEventConnection(cycle, eventRegistration, layerHandle);
 }
 
@@ -2200,19 +2223,20 @@
 
 void SurfaceFlinger::onRefreshRateChangedDebug(const RefreshRateChangedDebugData& data) {
     ATRACE_CALL();
-    if (const auto displayId = getHwComposer().toPhysicalDisplayId(data.display); displayId) {
-        const char* const whence = __func__;
-        static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) {
-            const Fps fps = Fps::fromPeriodNsecs(getHwComposer().getComposer()->isVrrSupported()
-                                                         ? data.refreshPeriodNanos
-                                                         : data.vsyncPeriodNanos);
-            ATRACE_FORMAT("%s Fps %d", whence, fps.getIntValue());
-            const auto display = getDisplayDeviceLocked(*displayId);
-            FTL_FAKE_GUARD(kMainThreadContext,
-                           display->updateRefreshRateOverlayRate(fps, display->getActiveMode().fps,
-                                                                 /* setByHwc */ true));
-        }));
-    }
+    const char* const whence = __func__;
+    static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(
+                                                   kMainThreadContext) {
+        if (const auto displayIdOpt = getHwComposer().toPhysicalDisplayId(data.display)) {
+            if (const auto display = getDisplayDeviceLocked(*displayIdOpt)) {
+                const Fps fps = Fps::fromPeriodNsecs(getHwComposer().getComposer()->isVrrSupported()
+                                                             ? data.refreshPeriodNanos
+                                                             : data.vsyncPeriodNanos);
+                ATRACE_FORMAT("%s Fps %d", whence, fps.getIntValue());
+                display->updateRefreshRateOverlayRate(fps, display->getActiveMode().fps,
+                                                      /* setByHwc */ true);
+            }
+        }
+    }));
 }
 
 void SurfaceFlinger::configure() {
@@ -2383,6 +2407,8 @@
         frontend::LayerSnapshotBuilder::Args
                 args{.root = mLayerHierarchyBuilder.getHierarchy(),
                      .layerLifecycleManager = mLayerLifecycleManager,
+                     .includeMetadata = mCompositionEngine->getFeatureFlags().test(
+                             compositionengine::Feature::kSnapshotLayerMetadata),
                      .displays = mFrontEndDisplayInfos,
                      .displayChanges = mFrontEndDisplayInfosChanged,
                      .globalShadowSettings = mDrawingState.globalShadowSettings,
@@ -2413,86 +2439,84 @@
     mustComposite |= mLayerLifecycleManager.getGlobalChanges().get() != 0;
 
     bool newDataLatched = false;
-    if (!mLegacyFrontEndEnabled) {
-        ATRACE_NAME("DisplayCallbackAndStatsUpdates");
-        mustComposite |= applyTransactionsLocked(update.transactions, vsyncId);
-        traverseLegacyLayers([&](Layer* layer) { layer->commitTransaction(); });
-        const nsecs_t latchTime = systemTime();
-        bool unused = false;
+    ATRACE_NAME("DisplayCallbackAndStatsUpdates");
+    mustComposite |= applyTransactionsLocked(update.transactions, vsyncId);
+    traverseLegacyLayers([&](Layer* layer) { layer->commitTransaction(); });
+    const nsecs_t latchTime = systemTime();
+    bool unused = false;
 
-        for (auto& layer : mLayerLifecycleManager.getLayers()) {
-            if (layer->changes.test(frontend::RequestedLayerState::Changes::Created) &&
-                layer->bgColorLayer) {
-                sp<Layer> bgColorLayer = getFactory().createEffectLayer(
-                        LayerCreationArgs(this, nullptr, layer->name,
-                                          ISurfaceComposerClient::eFXSurfaceEffect, LayerMetadata(),
-                                          std::make_optional(layer->id), true));
-                mLegacyLayers[bgColorLayer->sequence] = bgColorLayer;
-            }
-            const bool willReleaseBufferOnLatch = layer->willReleaseBufferOnLatch();
+    for (auto& layer : mLayerLifecycleManager.getLayers()) {
+        if (layer->changes.test(frontend::RequestedLayerState::Changes::Created) &&
+            layer->bgColorLayer) {
+            sp<Layer> bgColorLayer = getFactory().createEffectLayer(
+                    LayerCreationArgs(this, nullptr, layer->name,
+                                      ISurfaceComposerClient::eFXSurfaceEffect, LayerMetadata(),
+                                      std::make_optional(layer->id), true));
+            mLegacyLayers[bgColorLayer->sequence] = bgColorLayer;
+        }
+        const bool willReleaseBufferOnLatch = layer->willReleaseBufferOnLatch();
 
-            auto it = mLegacyLayers.find(layer->id);
-            if (it == mLegacyLayers.end() &&
-                layer->changes.test(frontend::RequestedLayerState::Changes::Destroyed)) {
-                // Layer handle was created and immediately destroyed. It was destroyed before it
-                // was added to the map.
-                continue;
-            }
-
-            LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
-                                            "Couldnt find layer object for %s",
-                                            layer->getDebugString().c_str());
-            if (!layer->hasReadyFrame() && !willReleaseBufferOnLatch) {
-                if (!it->second->hasBuffer()) {
-                    // The last latch time is used to classify a missed frame as buffer stuffing
-                    // instead of a missed frame. This is used to identify scenarios where we
-                    // could not latch a buffer or apply a transaction due to backpressure.
-                    // We only update the latch time for buffer less layers here, the latch time
-                    // is updated for buffer layers when the buffer is latched.
-                    it->second->updateLastLatchTime(latchTime);
-                }
-                continue;
-            }
-
-            const bool bgColorOnly =
-                    !layer->externalTexture && (layer->bgColorLayerId != UNASSIGNED_LAYER_ID);
-            if (willReleaseBufferOnLatch) {
-                mLayersWithBuffersRemoved.emplace(it->second);
-            }
-            it->second->latchBufferImpl(unused, latchTime, bgColorOnly);
-            newDataLatched = true;
-
-            mLayersWithQueuedFrames.emplace(it->second);
-            mLayersIdsWithQueuedFrames.emplace(it->second->sequence);
+        auto it = mLegacyLayers.find(layer->id);
+        if (it == mLegacyLayers.end() &&
+            layer->changes.test(frontend::RequestedLayerState::Changes::Destroyed)) {
+            // Layer handle was created and immediately destroyed. It was destroyed before it
+            // was added to the map.
+            continue;
         }
 
-        updateLayerHistory(latchTime);
-        mLayerSnapshotBuilder.forEachVisibleSnapshot([&](const frontend::LayerSnapshot& snapshot) {
-            if (mLayersIdsWithQueuedFrames.find(snapshot.path.id) ==
-                mLayersIdsWithQueuedFrames.end())
-                return;
-            Region visibleReg;
-            visibleReg.set(snapshot.transformedBoundsWithoutTransparentRegion);
-            invalidateLayerStack(snapshot.outputFilter, visibleReg);
-        });
-
-        for (auto& destroyedLayer : mLayerLifecycleManager.getDestroyedLayers()) {
-            mLegacyLayers.erase(destroyedLayer->id);
+        LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
+                                        "Couldnt find layer object for %s",
+                                        layer->getDebugString().c_str());
+        if (!layer->hasReadyFrame() && !willReleaseBufferOnLatch) {
+            if (!it->second->hasBuffer()) {
+                // The last latch time is used to classify a missed frame as buffer stuffing
+                // instead of a missed frame. This is used to identify scenarios where we
+                // could not latch a buffer or apply a transaction due to backpressure.
+                // We only update the latch time for buffer less layers here, the latch time
+                // is updated for buffer layers when the buffer is latched.
+                it->second->updateLastLatchTime(latchTime);
+            }
+            continue;
         }
 
-        {
-            ATRACE_NAME("LLM:commitChanges");
-            mLayerLifecycleManager.commitChanges();
+        const bool bgColorOnly =
+                !layer->externalTexture && (layer->bgColorLayerId != UNASSIGNED_LAYER_ID);
+        if (willReleaseBufferOnLatch) {
+            mLayersWithBuffersRemoved.emplace(it->second);
         }
+        it->second->latchBufferImpl(unused, latchTime, bgColorOnly);
+        newDataLatched = true;
 
-        // enter boot animation on first buffer latch
-        if (CC_UNLIKELY(mBootStage == BootStage::BOOTLOADER && newDataLatched)) {
-            ALOGI("Enter boot animation");
-            mBootStage = BootStage::BOOTANIMATION;
-        }
+        mLayersWithQueuedFrames.emplace(it->second);
+        mLayersIdsWithQueuedFrames.emplace(it->second->sequence);
     }
+
+    updateLayerHistory(latchTime);
+    mLayerSnapshotBuilder.forEachVisibleSnapshot([&](const frontend::LayerSnapshot& snapshot) {
+        if (mLayersIdsWithQueuedFrames.find(snapshot.path.id) == mLayersIdsWithQueuedFrames.end())
+            return;
+        Region visibleReg;
+        visibleReg.set(snapshot.transformedBoundsWithoutTransparentRegion);
+        invalidateLayerStack(snapshot.outputFilter, visibleReg);
+    });
+
+    for (auto& destroyedLayer : mLayerLifecycleManager.getDestroyedLayers()) {
+        mLegacyLayers.erase(destroyedLayer->id);
+    }
+
+    {
+        ATRACE_NAME("LLM:commitChanges");
+        mLayerLifecycleManager.commitChanges();
+    }
+
+    // enter boot animation on first buffer latch
+    if (CC_UNLIKELY(mBootStage == BootStage::BOOTLOADER && newDataLatched)) {
+        ALOGI("Enter boot animation");
+        mBootStage = BootStage::BOOTANIMATION;
+    }
+
     mustComposite |= (getTransactionFlags() & ~eTransactionFlushNeeded) || newDataLatched;
-    if (mustComposite && !mLegacyFrontEndEnabled) {
+    if (mustComposite) {
         commitTransactions();
     }
 
@@ -2594,12 +2618,7 @@
                                     mScheduler->getPacesetterRefreshRate());
 
         const bool flushTransactions = clearTransactionFlags(eTransactionFlushNeeded);
-        bool transactionsAreEmpty;
-        if (mLegacyFrontEndEnabled) {
-            mustComposite |=
-                    updateLayerSnapshotsLegacy(vsyncId, pacesetterFrameTarget.frameBeginTime().ns(),
-                                               flushTransactions, transactionsAreEmpty);
-        }
+        bool transactionsAreEmpty = false;
         if (mLayerLifecycleManagerEnabled) {
             mustComposite |=
                     updateLayerSnapshots(vsyncId, pacesetterFrameTarget.frameBeginTime().ns(),
@@ -3171,7 +3190,8 @@
             if (mLayerLifecycleManagerEnabled) {
                 mLayerSnapshotBuilder.forEachVisibleSnapshot(
                         [&, compositionDisplay = compositionDisplay](
-                                std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
+                                std::unique_ptr<frontend::LayerSnapshot>&
+                                        snapshot) FTL_FAKE_GUARD(kMainThreadContext) {
                             auto it = mLegacyLayers.find(snapshot->sequence);
                             LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
                                                             "Couldnt find layer object for %s",
@@ -3230,7 +3250,7 @@
 
     if (mNumTrustedPresentationListeners > 0) {
         // We avoid any reverse traversal upwards so this shouldn't be too expensive
-        traverseLegacyLayers([&](Layer* layer) {
+        traverseLegacyLayers([&](Layer* layer) FTL_FAKE_GUARD(kMainThreadContext) {
             if (!layer->hasTrustedPresentationListener()) {
                 return;
             }
@@ -3487,12 +3507,20 @@
     for (const auto [hwcDisplayId, connection] : events) {
         if (auto info = getHwComposer().onHotplug(hwcDisplayId, connection)) {
             const auto displayId = info->id;
-            const bool connected = connection == hal::Connection::CONNECTED;
+            const ftl::Concat displayString("display ", displayId.value, "(HAL ID ", hwcDisplayId,
+                                            ')');
 
-            if (const char* const log =
-                        processHotplug(displayId, hwcDisplayId, connected, std::move(*info))) {
-                ALOGI("%s display %s (HAL ID %" PRIu64 ")", log, to_string(displayId).c_str(),
-                      hwcDisplayId);
+            if (connection == hal::Connection::CONNECTED) {
+                if (!processHotplugConnect(displayId, hwcDisplayId, std::move(*info),
+                                           displayString.c_str())) {
+                    if (FlagManager::getInstance().hotplug2()) {
+                        mScheduler->dispatchHotplugError(
+                                static_cast<int32_t>(DisplayHotplugEvent::ERROR_UNKNOWN));
+                    }
+                    getHwComposer().disconnectDisplay(displayId);
+                }
+            } else {
+                processHotplugDisconnect(displayId, displayString.c_str());
             }
         }
     }
@@ -3500,36 +3528,19 @@
     return !events.empty();
 }
 
-const char* SurfaceFlinger::processHotplug(PhysicalDisplayId displayId,
-                                           hal::HWDisplayId hwcDisplayId, bool connected,
-                                           DisplayIdentificationInfo&& info) {
-    const auto displayOpt = mPhysicalDisplays.get(displayId);
-    if (!connected) {
-        LOG_ALWAYS_FATAL_IF(!displayOpt);
-        const auto& display = displayOpt->get();
-
-        if (const ssize_t index = mCurrentState.displays.indexOfKey(display.token()); index >= 0) {
-            mCurrentState.displays.removeItemsAt(index);
-        }
-
-        mPhysicalDisplays.erase(displayId);
-        return "Disconnecting";
-    }
-
+bool SurfaceFlinger::processHotplugConnect(PhysicalDisplayId displayId,
+                                           hal::HWDisplayId hwcDisplayId,
+                                           DisplayIdentificationInfo&& info,
+                                           const char* displayString) {
     auto [displayModes, activeMode] = loadDisplayModes(displayId);
     if (!activeMode) {
-        ALOGE("Failed to hotplug display %s", to_string(displayId).c_str());
-        if (FlagManager::getInstance().hotplug2()) {
-            mScheduler->dispatchHotplugError(
-                    static_cast<int32_t>(DisplayHotplugEvent::ERROR_UNKNOWN));
-        }
-        getHwComposer().disconnectDisplay(displayId);
-        return nullptr;
+        ALOGE("Failed to hotplug %s", displayString);
+        return false;
     }
 
     ui::ColorModes colorModes = getHwComposer().getColorModes(displayId);
 
-    if (displayOpt) {
+    if (const auto displayOpt = mPhysicalDisplays.get(displayId)) {
         const auto& display = displayOpt->get();
         const auto& snapshot = display.snapshot();
 
@@ -3548,7 +3559,8 @@
         auto& state = mCurrentState.displays.editValueFor(it->second.token());
         state.sequenceId = DisplayDeviceState{}.sequenceId; // Generate new sequenceId.
         state.physical->activeMode = std::move(activeMode);
-        return "Reconnecting";
+        ALOGI("Reconnecting %s", displayString);
+        return true;
     }
 
     const sp<IBinder> token = sp<BBinder>::make();
@@ -3568,7 +3580,23 @@
     state.displayName = std::move(info.name);
 
     mCurrentState.displays.add(token, state);
-    return "Connecting";
+    ALOGI("Connecting %s", displayString);
+    return true;
+}
+
+void SurfaceFlinger::processHotplugDisconnect(PhysicalDisplayId displayId,
+                                              const char* displayString) {
+    ALOGI("Disconnecting %s", displayString);
+
+    const auto displayOpt = mPhysicalDisplays.get(displayId);
+    LOG_ALWAYS_FATAL_IF(!displayOpt);
+    const auto& display = displayOpt->get();
+
+    if (const ssize_t index = mCurrentState.displays.indexOfKey(display.token()); index >= 0) {
+        mCurrentState.displays.removeItemsAt(index);
+    }
+
+    mPhysicalDisplays.erase(displayId);
 }
 
 void SurfaceFlinger::dispatchDisplayModeChangeEvent(PhysicalDisplayId displayId,
@@ -3992,19 +4020,7 @@
                 }
             }
 
-            if (!hintDisplay) {
-                // NOTE: TEMPORARY FIX ONLY. Real fix should cause layers to
-                // redraw after transform hint changes. See bug 8508397.
-                // could be null when this layer is using a layerStack
-                // that is not visible on any display. Also can occur at
-                // screen off/on times.
-                // U Update: Don't provide stale hints to the clients. For
-                // special cases where we want the app to draw its
-                // first frame before the display is available, we rely
-                // on WMS and DMS to provide the right information
-                // so the client can calculate the hint.
-                layer->skipReportingTransformHint();
-            } else {
+            if (hintDisplay) {
                 layer->updateTransformHint(hintDisplay->getTransformHint());
             }
         });
@@ -4143,7 +4159,7 @@
                     outWindowInfos.push_back(snapshot.inputInfo);
                 });
     } else {
-        mDrawingState.traverseInReverseZOrder([&](Layer* layer) {
+        mDrawingState.traverseInReverseZOrder([&](Layer* layer) FTL_FAKE_GUARD(kMainThreadContext) {
             if (!layer->needsInputInfo()) return;
             const auto opt =
                     mFrontEndDisplayInfos.get(layer->getLayerStack())
@@ -4848,6 +4864,8 @@
                 if (listener &&
                     (flushState.queueProcessTime - transaction.postTime) >
                             std::chrono::nanoseconds(4s).count()) {
+                    // Used to add a stalled transaction which uses an internal lock.
+                    ftl::FakeGuard guard(kMainThreadContext);
                     mTransactionHandler
                             .onTransactionQueueStalled(transaction.id,
                                                        {.pid = layer->getOwnerPid(),
@@ -4870,97 +4888,107 @@
         const TransactionHandler::TransactionFlushState& flushState) {
     using TransactionReadiness = TransactionHandler::TransactionReadiness;
     auto ready = TransactionReadiness::Ready;
-    flushState.transaction->traverseStatesWithBuffersWhileTrue([&](const ResolvedComposerState&
-                                                                           resolvedState) -> bool {
-        const frontend::RequestedLayerState* layer =
-                mLayerLifecycleManager.getLayerFromId(resolvedState.layerId);
-        const auto& transaction = *flushState.transaction;
-        const auto& s = resolvedState.state;
-        // check for barrier frames
-        if (s.bufferData->hasBarrier) {
-            // The current producerId is already a newer producer than the buffer that has a
-            // barrier. This means the incoming buffer is older and we can release it here. We
-            // don't wait on the barrier since we know that's stale information.
-            if (layer->barrierProducerId > s.bufferData->producerId) {
-                if (s.bufferData->releaseBufferListener) {
-                    uint32_t currentMaxAcquiredBufferCount =
-                            getMaxAcquiredBufferCountForCurrentRefreshRate(layer->ownerUid.val());
-                    ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64,
-                                          layer->name.c_str(), s.bufferData->frameNumber);
-                    s.bufferData->releaseBufferListener
-                            ->onReleaseBuffer({resolvedState.externalTexture->getBuffer()->getId(),
-                                               s.bufferData->frameNumber},
-                                              s.bufferData->acquireFence
-                                                      ? s.bufferData->acquireFence
-                                                      : Fence::NO_FENCE,
-                                              currentMaxAcquiredBufferCount);
+    flushState.transaction->traverseStatesWithBuffersWhileTrue(
+            [&](const ResolvedComposerState& resolvedState) FTL_FAKE_GUARD(
+                    kMainThreadContext) -> bool {
+                const frontend::RequestedLayerState* layer =
+                        mLayerLifecycleManager.getLayerFromId(resolvedState.layerId);
+                const auto& transaction = *flushState.transaction;
+                const auto& s = resolvedState.state;
+                // check for barrier frames
+                if (s.bufferData->hasBarrier) {
+                    // The current producerId is already a newer producer than the buffer that has a
+                    // barrier. This means the incoming buffer is older and we can release it here.
+                    // We don't wait on the barrier since we know that's stale information.
+                    if (layer->barrierProducerId > s.bufferData->producerId) {
+                        if (s.bufferData->releaseBufferListener) {
+                            uint32_t currentMaxAcquiredBufferCount =
+                                    getMaxAcquiredBufferCountForCurrentRefreshRate(
+                                            layer->ownerUid.val());
+                            ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64,
+                                                  layer->name.c_str(), s.bufferData->frameNumber);
+                            s.bufferData->releaseBufferListener
+                                    ->onReleaseBuffer({resolvedState.externalTexture->getBuffer()
+                                                               ->getId(),
+                                                       s.bufferData->frameNumber},
+                                                      s.bufferData->acquireFence
+                                                              ? s.bufferData->acquireFence
+                                                              : Fence::NO_FENCE,
+                                                      currentMaxAcquiredBufferCount);
+                        }
+
+                        // Delete the entire state at this point and not just release the buffer
+                        // because everything associated with the Layer in this Transaction is now
+                        // out of date.
+                        ATRACE_FORMAT("DeleteStaleBuffer %s barrierProducerId:%d > %d",
+                                      layer->name.c_str(), layer->barrierProducerId,
+                                      s.bufferData->producerId);
+                        return TraverseBuffersReturnValues::DELETE_AND_CONTINUE_TRAVERSAL;
+                    }
+
+                    if (layer->barrierFrameNumber < s.bufferData->barrierFrameNumber) {
+                        const bool willApplyBarrierFrame =
+                                flushState.bufferLayersReadyToPresent.contains(s.surface.get()) &&
+                                ((flushState.bufferLayersReadyToPresent.get(s.surface.get()) >=
+                                  s.bufferData->barrierFrameNumber));
+                        if (!willApplyBarrierFrame) {
+                            ATRACE_FORMAT("NotReadyBarrier %s barrierFrameNumber:%" PRId64
+                                          " > %" PRId64,
+                                          layer->name.c_str(), layer->barrierFrameNumber,
+                                          s.bufferData->barrierFrameNumber);
+                            ready = TransactionReadiness::NotReadyBarrier;
+                            return TraverseBuffersReturnValues::STOP_TRAVERSAL;
+                        }
+                    }
                 }
 
-                // Delete the entire state at this point and not just release the buffer because
-                // everything associated with the Layer in this Transaction is now out of date.
-                ATRACE_FORMAT("DeleteStaleBuffer %s barrierProducerId:%d > %d", layer->name.c_str(),
-                              layer->barrierProducerId, s.bufferData->producerId);
-                return TraverseBuffersReturnValues::DELETE_AND_CONTINUE_TRAVERSAL;
-            }
-
-            if (layer->barrierFrameNumber < s.bufferData->barrierFrameNumber) {
-                const bool willApplyBarrierFrame =
-                        flushState.bufferLayersReadyToPresent.contains(s.surface.get()) &&
-                        ((flushState.bufferLayersReadyToPresent.get(s.surface.get()) >=
-                          s.bufferData->barrierFrameNumber));
-                if (!willApplyBarrierFrame) {
-                    ATRACE_FORMAT("NotReadyBarrier %s barrierFrameNumber:%" PRId64 " > %" PRId64,
-                                  layer->name.c_str(), layer->barrierFrameNumber,
-                                  s.bufferData->barrierFrameNumber);
-                    ready = TransactionReadiness::NotReadyBarrier;
+                // If backpressure is enabled and we already have a buffer to commit, keep
+                // the transaction in the queue.
+                const bool hasPendingBuffer =
+                        flushState.bufferLayersReadyToPresent.contains(s.surface.get());
+                if (layer->backpressureEnabled() && hasPendingBuffer &&
+                    transaction.isAutoTimestamp) {
+                    ATRACE_FORMAT("hasPendingBuffer %s", layer->name.c_str());
+                    ready = TransactionReadiness::NotReady;
                     return TraverseBuffersReturnValues::STOP_TRAVERSAL;
                 }
-            }
-        }
 
-        // If backpressure is enabled and we already have a buffer to commit, keep
-        // the transaction in the queue.
-        const bool hasPendingBuffer =
-                flushState.bufferLayersReadyToPresent.contains(s.surface.get());
-        if (layer->backpressureEnabled() && hasPendingBuffer && transaction.isAutoTimestamp) {
-            ATRACE_FORMAT("hasPendingBuffer %s", layer->name.c_str());
-            ready = TransactionReadiness::NotReady;
-            return TraverseBuffersReturnValues::STOP_TRAVERSAL;
-        }
-
-        const bool acquireFenceAvailable = s.bufferData &&
-                s.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged) &&
-                s.bufferData->acquireFence;
-        const bool fenceSignaled = !acquireFenceAvailable ||
-                s.bufferData->acquireFence->getStatus() != Fence::Status::Unsignaled;
-        if (!fenceSignaled) {
-            // check fence status
-            const bool allowLatchUnsignaled = shouldLatchUnsignaled(s, transaction.states.size(),
-                                                                    flushState.firstTransaction) &&
-                    layer->isSimpleBufferUpdate(s);
-            if (allowLatchUnsignaled) {
-                ATRACE_FORMAT("fence unsignaled try allowLatchUnsignaled %s", layer->name.c_str());
-                ready = TransactionReadiness::NotReadyUnsignaled;
-            } else {
-                ready = TransactionReadiness::NotReady;
-                auto& listener = s.bufferData->releaseBufferListener;
-                if (listener &&
-                    (flushState.queueProcessTime - transaction.postTime) >
-                            std::chrono::nanoseconds(4s).count()) {
-                    mTransactionHandler
-                            .onTransactionQueueStalled(transaction.id,
-                                                       {.pid = layer->ownerPid.val(),
-                                                        .layerId = layer->id,
-                                                        .layerName = layer->name,
-                                                        .bufferId = s.bufferData->getId(),
-                                                        .frameNumber = s.bufferData->frameNumber});
+                const bool acquireFenceAvailable = s.bufferData &&
+                        s.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged) &&
+                        s.bufferData->acquireFence;
+                const bool fenceSignaled = !acquireFenceAvailable ||
+                        s.bufferData->acquireFence->getStatus() != Fence::Status::Unsignaled;
+                if (!fenceSignaled) {
+                    // check fence status
+                    const bool allowLatchUnsignaled =
+                            shouldLatchUnsignaled(s, transaction.states.size(),
+                                                  flushState.firstTransaction) &&
+                            layer->isSimpleBufferUpdate(s);
+                    if (allowLatchUnsignaled) {
+                        ATRACE_FORMAT("fence unsignaled try allowLatchUnsignaled %s",
+                                      layer->name.c_str());
+                        ready = TransactionReadiness::NotReadyUnsignaled;
+                    } else {
+                        ready = TransactionReadiness::NotReady;
+                        auto& listener = s.bufferData->releaseBufferListener;
+                        if (listener &&
+                            (flushState.queueProcessTime - transaction.postTime) >
+                                    std::chrono::nanoseconds(4s).count()) {
+                            mTransactionHandler
+                                    .onTransactionQueueStalled(transaction.id,
+                                                               {.pid = layer->ownerPid.val(),
+                                                                .layerId = layer->id,
+                                                                .layerName = layer->name,
+                                                                .bufferId = s.bufferData->getId(),
+                                                                .frameNumber =
+                                                                        s.bufferData->frameNumber});
+                        }
+                        ATRACE_FORMAT("fence unsignaled %s", layer->name.c_str());
+                        return TraverseBuffersReturnValues::STOP_TRAVERSAL;
+                    }
                 }
-                ATRACE_FORMAT("fence unsignaled %s", layer->name.c_str());
-                return TraverseBuffersReturnValues::STOP_TRAVERSAL;
-            }
-        }
-        return TraverseBuffersReturnValues::CONTINUE_TRAVERSAL;
-    });
+                return TraverseBuffersReturnValues::CONTINUE_TRAVERSAL;
+            });
     return ready;
 }
 
@@ -5082,7 +5110,7 @@
     const int originPid = ipc->getCallingPid();
     const int originUid = ipc->getCallingUid();
     uint32_t permissions = LayerStatePermissions::getTransactionPermissions(originPid, originUid);
-    for (auto composerState : states) {
+    for (auto& composerState : states) {
         composerState.state.sanitize(permissions);
     }
 
@@ -5181,7 +5209,13 @@
     }(state.flags);
 
     const auto frameHint = state.isFrameActive() ? FrameHint::kActive : FrameHint::kNone;
-    mTransactionHandler.queueTransaction(std::move(state));
+    {
+        // Transactions are added via a lockless queue and does not need to be added from the main
+        // thread.
+        ftl::FakeGuard guard(kMainThreadContext);
+        mTransactionHandler.queueTransaction(std::move(state));
+    }
+
     for (const auto& [displayId, data] : mNotifyExpectedPresentMap) {
         if (data.hintStatus.load() == NotifyExpectedPresentHintStatus::ScheduleOnTx) {
             scheduleNotifyExpectedPresentHint(displayId, VsyncId{frameTimelineInfo.vsyncId});
@@ -5216,27 +5250,22 @@
     nsecs_t now = systemTime();
     uint32_t clientStateFlags = 0;
     for (auto& resolvedState : states) {
-        if (mLegacyFrontEndEnabled) {
-            clientStateFlags |=
-                    setClientStateLocked(frameTimelineInfo, resolvedState, desiredPresentTime,
-                                         isAutoTimestamp, postTime, transactionId);
-
-        } else /*mLayerLifecycleManagerEnabled*/ {
-            clientStateFlags |= updateLayerCallbacksAndStats(frameTimelineInfo, resolvedState,
-                                                             desiredPresentTime, isAutoTimestamp,
-                                                             postTime, transactionId);
-        }
-        if ((flags & eAnimation) && resolvedState.state.surface) {
-            if (const auto layer = LayerHandle::getLayer(resolvedState.state.surface)) {
-                const auto layerProps = scheduler::LayerProps{
-                        .visible = layer->isVisible(),
-                        .bounds = layer->getBounds(),
-                        .transform = layer->getTransform(),
-                        .setFrameRateVote = layer->getFrameRateForLayerTree(),
-                        .frameRateSelectionPriority = layer->getFrameRateSelectionPriority(),
-                        .isFrontBuffered = layer->isFrontBuffered(),
-                };
-                layer->recordLayerHistoryAnimationTx(layerProps, now);
+        clientStateFlags |=
+                updateLayerCallbacksAndStats(frameTimelineInfo, resolvedState, desiredPresentTime,
+                                             isAutoTimestamp, postTime, transactionId);
+        if (!mLayerLifecycleManagerEnabled) {
+            if ((flags & eAnimation) && resolvedState.state.surface) {
+                if (const auto layer = LayerHandle::getLayer(resolvedState.state.surface)) {
+                    const auto layerProps = scheduler::LayerProps{
+                            .visible = layer->isVisible(),
+                            .bounds = layer->getBounds(),
+                            .transform = layer->getTransform(),
+                            .setFrameRateVote = layer->getFrameRateForLayerTree(),
+                            .frameRateSelectionPriority = layer->getFrameRateSelectionPriority(),
+                            .isFrontBuffered = layer->isFrontBuffered(),
+                    };
+                    layer->recordLayerHistoryAnimationTx(layerProps, now);
+                }
             }
         }
     }
@@ -5295,7 +5324,7 @@
     }
 
     mFrontEndDisplayInfosChanged = mTransactionFlags & eDisplayTransactionNeeded;
-    if (mFrontEndDisplayInfosChanged && !mLegacyFrontEndEnabled) {
+    if (mFrontEndDisplayInfosChanged) {
         processDisplayChangesLocked();
         mFrontEndDisplayInfos.clear();
         for (const auto& [_, display] : mDisplays) {
@@ -5892,7 +5921,7 @@
             return result;
         }
 
-        mirrorLayer->setClonedChild(mirrorFrom->createClone(mirrorLayer->getSequence()));
+        mirrorLayer->setClonedChild(mirrorFrom->createClone());
     }
 
     outResult.layerId = mirrorLayer->sequence;
@@ -5938,11 +5967,6 @@
         return result;
     }
 
-    if (mLegacyFrontEndEnabled) {
-        std::scoped_lock<std::mutex> lock(mMirrorDisplayLock);
-        mMirrorDisplays.emplace_back(layerStack, outResult.handle, args.client);
-    }
-
     setTransactionFlags(eTransactionFlushNeeded);
     return NO_ERROR;
 }
@@ -6023,7 +6047,11 @@
         mDestroyedHandles.emplace_back(layerId, layer->getDebugName());
     }
 
-    mTransactionHandler.onLayerDestroyed(layerId);
+    {
+        // Used to remove stalled transactions which uses an internal lock.
+        ftl::FakeGuard guard(kMainThreadContext);
+        mTransactionHandler.onLayerDestroyed(layerId);
+    }
 
     Mutex::Autolock lock(mStateLock);
     markLayerPendingRemovalLocked(layer);
@@ -6053,9 +6081,7 @@
     std::vector<TransactionState> transactions;
     transactions.emplace_back(state);
 
-    if (mLegacyFrontEndEnabled) {
-        applyTransactions(transactions, VsyncId{0});
-    } else {
+    {
         Mutex::Autolock lock(mStateLock);
         applyAndCommitDisplayTransactionStatesLocked(transactions);
     }
@@ -6607,17 +6633,6 @@
         }
     }
 
-    if (mLegacyFrontEndEnabled) {
-        perfetto::protos::LayersProto layersProto;
-        for (const sp<Layer>& layer : mDrawingState.layersSortedByZ) {
-            if (stackIdsToSkip.find(layer->getLayerStack().id) != stackIdsToSkip.end()) {
-                continue;
-            }
-            layer->writeToProto(layersProto, traceFlags);
-        }
-        return layersProto;
-    }
-
     return LayerProtoFromSnapshotGenerator(mLayerSnapshotBuilder, mFrontEndDisplayInfos,
                                            mLegacyLayers, traceFlags)
             .generate(mLayerHierarchyBuilder.getHierarchy());
@@ -6675,7 +6690,11 @@
 }
 
 perfetto::protos::LayersProto SurfaceFlinger::dumpProtoFromMainThread(uint32_t traceFlags) {
-    return mScheduler->schedule([=, this] { return dumpDrawingStateProto(traceFlags); }).get();
+    return mScheduler
+            ->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) {
+                return dumpDrawingStateProto(traceFlags);
+            })
+            .get();
 }
 
 void SurfaceFlinger::dumpOffscreenLayers(std::string& result) {
@@ -6724,17 +6743,18 @@
         Layer::miniDumpHeader(result);
 
         const DisplayDevice& ref = *display;
-        mLayerSnapshotBuilder.forEachVisibleSnapshot([&](const frontend::LayerSnapshot& snapshot) {
-            if (!snapshot.hasSomethingToDraw() ||
-                ref.getLayerStack() != snapshot.outputFilter.layerStack) {
-                return;
-            }
-            auto it = mLegacyLayers.find(snapshot.sequence);
-            LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
-                                            "Couldnt find layer object for %s",
-                                            snapshot.getDebugString().c_str());
-            it->second->miniDump(result, snapshot, ref);
-        });
+        mLayerSnapshotBuilder.forEachVisibleSnapshot(
+                [&](const frontend::LayerSnapshot& snapshot) FTL_FAKE_GUARD(kMainThreadContext) {
+                    if (!snapshot.hasSomethingToDraw() ||
+                        ref.getLayerStack() != snapshot.outputFilter.layerStack) {
+                        return;
+                    }
+                    auto it = mLegacyLayers.find(snapshot.sequence);
+                    LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
+                                                    "Couldnt find layer object for %s",
+                                                    snapshot.getDebugString().c_str());
+                    it->second->miniDump(result, snapshot, ref);
+                });
         result.append("\n");
     }
 }
@@ -6831,24 +6851,23 @@
     StringAppendF(&result, "  transaction-flags         : %08x\n", mTransactionFlags.load());
 
     if (const auto display = getDefaultDisplayDeviceLocked()) {
-        std::string fps, xDpi, yDpi;
-        if (const auto activeModePtr =
-                    display->refreshRateSelector().getActiveMode().modePtr.get()) {
-            fps = to_string(activeModePtr->getVsyncRate());
-
+        std::string peakFps, xDpi, yDpi;
+        const auto activeMode = display->refreshRateSelector().getActiveMode();
+        if (const auto activeModePtr = activeMode.modePtr.get()) {
+            peakFps = to_string(activeMode.modePtr->getPeakFps());
             const auto dpi = activeModePtr->getDpi();
             xDpi = base::StringPrintf("%.2f", dpi.x);
             yDpi = base::StringPrintf("%.2f", dpi.y);
         } else {
-            fps = "unknown";
+            peakFps = "unknown";
             xDpi = "unknown";
             yDpi = "unknown";
         }
         StringAppendF(&result,
-                      "  refresh-rate              : %s\n"
+                      "  peak-refresh-rate         : %s\n"
                       "  x-dpi                     : %s\n"
                       "  y-dpi                     : %s\n",
-                      fps.c_str(), xDpi.c_str(), yDpi.c_str());
+                      peakFps.c_str(), xDpi.c_str(), yDpi.c_str());
     }
 
     StringAppendF(&result, "  transaction time: %f us\n", inTransactionDuration / 1000.0);
@@ -6862,10 +6881,6 @@
     }
     result.push_back('\n');
 
-    if (mLegacyFrontEndEnabled) {
-        dumpHwcLayersMinidumpLockedLegacy(result);
-    }
-
     {
         DumpArgs plannerArgs;
         plannerArgs.add(); // first argument is ignored
@@ -7770,20 +7785,6 @@
         return NO_ERROR;
     }
 
-    // Currently, there is no wrapper in bionic: b/183240349.
-    struct sched_attr {
-        uint32_t size;
-        uint32_t sched_policy;
-        uint64_t sched_flags;
-        int32_t sched_nice;
-        uint32_t sched_priority;
-        uint64_t sched_runtime;
-        uint64_t sched_deadline;
-        uint64_t sched_period;
-        uint32_t sched_util_min;
-        uint32_t sched_util_max;
-    };
-
     sched_attr attr = {};
     attr.size = sizeof(attr);
 
@@ -8049,28 +8050,24 @@
         return;
     }
 
-    bool childrenOnly = args.childrenOnly;
-    RenderAreaFuture renderAreaFuture = ftl::defer([=, this]() -> std::unique_ptr<RenderArea> {
+    RenderAreaFuture renderAreaFuture = ftl::defer([=, this]() FTL_FAKE_GUARD(kMainThreadContext)
+                                                           -> std::unique_ptr<RenderArea> {
         ui::Transform layerTransform;
         Rect layerBufferSize;
-        if (mLayerLifecycleManagerEnabled) {
-            frontend::LayerSnapshot* snapshot =
-                    mLayerSnapshotBuilder.getSnapshot(parent->getSequence());
-            if (!snapshot) {
-                ALOGW("Couldn't find layer snapshot for %d", parent->getSequence());
-            } else {
-                layerTransform = snapshot->localTransform;
-                layerBufferSize = snapshot->bufferSize;
-            }
+        frontend::LayerSnapshot* snapshot =
+                mLayerSnapshotBuilder.getSnapshot(parent->getSequence());
+        if (!snapshot) {
+            ALOGW("Couldn't find layer snapshot for %d", parent->getSequence());
         } else {
-            layerTransform = parent->getTransform();
-            layerBufferSize = parent->getBufferSize(parent->getDrawingState());
+            if (!args.childrenOnly) {
+                layerTransform = snapshot->localTransform.inverse();
+            }
+            layerBufferSize = snapshot->bufferSize;
         }
 
-        return std::make_unique<LayerRenderArea>(*this, parent, crop, reqSize, dataspace,
-                                                 childrenOnly, args.captureSecureLayers,
-                                                 layerTransform, layerBufferSize,
-                                                 args.hintForSeamlessTransition);
+        return std::make_unique<LayerRenderArea>(parent, crop, reqSize, dataspace,
+                                                 args.captureSecureLayers, layerTransform,
+                                                 layerBufferSize, args.hintForSeamlessTransition);
     });
     GetLayerSnapshotsFunction getLayerSnapshots;
     if (mLayerLifecycleManagerEnabled) {
@@ -8223,11 +8220,9 @@
             return ftl::yield<FenceResult>(base::unexpected(NO_ERROR)).share();
         }
 
-        ftl::SharedFuture<FenceResult> renderFuture;
-        renderArea->render([&]() FTL_FAKE_GUARD(kMainThreadContext) {
-            renderFuture = renderScreenImpl(renderArea, buffer, regionSampling, grayscale,
-                                            isProtected, captureResults, layers);
-        });
+        ftl::SharedFuture<FenceResult> renderFuture =
+                renderScreenImpl(renderArea, buffer, regionSampling, grayscale, isProtected,
+                                 captureResults, layers);
 
         if (captureListener) {
             // Defer blocking on renderFuture back to the Binder thread.
@@ -9022,6 +9017,8 @@
 
 status_t SurfaceFlinger::getStalledTransactionInfo(
         int pid, std::optional<TransactionHandler::StalledTransactionInfo>& result) {
+    // Used to add a stalled transaction which uses an internal lock.
+    ftl::FakeGuard guard(kMainThreadContext);
     result = mTransactionHandler.getStalledTransactionInfo(pid);
     return NO_ERROR;
 }
@@ -9149,7 +9146,7 @@
                 Mutex::Autolock lock(mStateLock);
                 createEffectLayer(mirrorArgs, &unused, &childMirror);
                 MUTEX_ALIAS(mStateLock, childMirror->mFlinger->mStateLock);
-                childMirror->setClonedChild(layer->createClone(childMirror->getSequence()));
+                childMirror->setClonedChild(layer->createClone());
                 childMirror->reparent(mirrorDisplay.rootHandle);
             }
             // lock on mStateLock needs to be released before binder handle gets destroyed
@@ -9209,7 +9206,7 @@
             snapshots[i] = std::move(layerFE->mSnapshot);
         }
     }
-    if (mLegacyFrontEndEnabled && !mLayerLifecycleManagerEnabled) {
+    if (!mLayerLifecycleManagerEnabled) {
         for (auto [layer, layerFE] : layers) {
             layer->updateLayerSnapshot(std::move(layerFE->mSnapshot));
         }
@@ -9222,7 +9219,8 @@
     if (mLayerLifecycleManagerEnabled) {
         nsecs_t currentTime = systemTime();
         mLayerSnapshotBuilder.forEachVisibleSnapshot(
-                [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
+                [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) FTL_FAKE_GUARD(
+                        kMainThreadContext) {
                     if (cursorOnly &&
                         snapshot->compositionType !=
                                 aidl::android::hardware::graphics::composer3::Composition::CURSOR) {
@@ -9245,7 +9243,7 @@
                     layers.emplace_back(legacyLayer.get(), layerFE.get());
                 });
     }
-    if (mLegacyFrontEndEnabled && !mLayerLifecycleManagerEnabled) {
+    if (!mLayerLifecycleManagerEnabled) {
         auto moveSnapshots = [&layers, &refreshArgs, cursorOnly](Layer* layer) {
             if (const auto& layerFE = layer->getCompositionEngineLayerFE()) {
                 if (cursorOnly &&
@@ -9283,11 +9281,12 @@
         std::optional<ui::LayerStack> layerStack, uint32_t uid,
         std::function<bool(const frontend::LayerSnapshot&, bool& outStopTraversal)>
                 snapshotFilterFn) {
-    return [&, layerStack, uid]() {
+    return [&, layerStack, uid]() FTL_FAKE_GUARD(kMainThreadContext) {
         std::vector<std::pair<Layer*, sp<LayerFE>>> layers;
         bool stopTraversal = false;
         mLayerSnapshotBuilder.forEachVisibleSnapshot(
-                [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
+                [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) FTL_FAKE_GUARD(
+                        kMainThreadContext) {
                     if (stopTraversal) {
                         return;
                     }
@@ -9309,7 +9308,7 @@
                                                     "Couldnt find layer object for %s",
                                                     snapshot->getDebugString().c_str());
                     Layer* legacyLayer = (it == mLegacyLayers.end()) ? nullptr : it->second.get();
-                    sp<LayerFE> layerFE = getFactory().createLayerFE(snapshot->name);
+                    sp<LayerFE> layerFE = getFactory().createLayerFE(snapshot->name, legacyLayer);
                     layerFE->mSnapshot = std::make_unique<frontend::LayerSnapshot>(*snapshot);
                     layers.emplace_back(legacyLayer, std::move(layerFE));
                 });
@@ -9322,7 +9321,8 @@
 SurfaceFlinger::getLayerSnapshotsForScreenshots(std::optional<ui::LayerStack> layerStack,
                                                 uint32_t uid,
                                                 std::unordered_set<uint32_t> excludeLayerIds) {
-    return [&, layerStack, uid, excludeLayerIds = std::move(excludeLayerIds)]() {
+    return [&, layerStack, uid,
+            excludeLayerIds = std::move(excludeLayerIds)]() FTL_FAKE_GUARD(kMainThreadContext) {
         if (excludeLayerIds.empty()) {
             auto getLayerSnapshotsFn =
                     getLayerSnapshotsForScreenshots(layerStack, uid, /*snapshotFilterFn=*/nullptr);
@@ -9364,7 +9364,7 @@
                                                 bool childrenOnly,
                                                 const std::optional<FloatRect>& parentCrop) {
     return [&, rootLayerId, uid, excludeLayerIds = std::move(excludeLayerIds), childrenOnly,
-            parentCrop]() {
+            parentCrop]() FTL_FAKE_GUARD(kMainThreadContext) {
         auto root = mLayerHierarchyBuilder.getPartialHierarchy(rootLayerId, childrenOnly);
         frontend::LayerSnapshotBuilder::Args
                 args{.root = root,
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 4a44be6..a707a7a 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -770,24 +770,27 @@
     void updateLayerGeometry();
     void updateLayerMetadataSnapshot();
     std::vector<std::pair<Layer*, LayerFE*>> moveSnapshotsToCompositionArgs(
-            compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly);
+            compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly)
+            REQUIRES(kMainThreadContext);
     void moveSnapshotsFromCompositionArgs(compositionengine::CompositionRefreshArgs& refreshArgs,
-                                          const std::vector<std::pair<Layer*, LayerFE*>>& layers);
+                                          const std::vector<std::pair<Layer*, LayerFE*>>& layers)
+            REQUIRES(kMainThreadContext);
     // Return true if we must composite this frame
     bool updateLayerSnapshotsLegacy(VsyncId vsyncId, nsecs_t frameTimeNs, bool transactionsFlushed,
                                     bool& out) REQUIRES(kMainThreadContext);
     // Return true if we must composite this frame
     bool updateLayerSnapshots(VsyncId vsyncId, nsecs_t frameTimeNs, bool transactionsFlushed,
                               bool& out) REQUIRES(kMainThreadContext);
-    void updateLayerHistory(nsecs_t now);
+    void updateLayerHistory(nsecs_t now) REQUIRES(kMainThreadContext);
     frontend::Update flushLifecycleUpdates() REQUIRES(kMainThreadContext);
 
-    void updateInputFlinger(VsyncId vsyncId, TimePoint frameTime);
+    void updateInputFlinger(VsyncId vsyncId, TimePoint frameTime) REQUIRES(kMainThreadContext);
     void persistDisplayBrightness(bool needsComposite) REQUIRES(kMainThreadContext);
     void buildWindowInfos(std::vector<gui::WindowInfo>& outWindowInfos,
-                          std::vector<gui::DisplayInfo>& outDisplayInfos);
+                          std::vector<gui::DisplayInfo>& outDisplayInfos)
+            REQUIRES(kMainThreadContext);
     void commitInputWindowCommands() REQUIRES(mStateLock);
-    void updateCursorAsync();
+    void updateCursorAsync() REQUIRES(kMainThreadContext);
 
     void initScheduler(const sp<const DisplayDevice>&) REQUIRES(kMainThreadContext, mStateLock);
 
@@ -803,7 +806,7 @@
                                const int64_t postTime, bool hasListenerCallbacks,
                                const std::vector<ListenerCallbacks>& listenerCallbacks,
                                int originPid, int originUid, uint64_t transactionId)
-            REQUIRES(mStateLock);
+            REQUIRES(mStateLock, kMainThreadContext);
     // Flush pending transactions that were presented after desiredPresentTime.
     // For test only
     bool flushTransactionQueues(VsyncId) REQUIRES(kMainThreadContext);
@@ -813,8 +816,8 @@
             REQUIRES(kMainThreadContext, mStateLock);
 
     // Returns true if there is at least one transaction that needs to be flushed
-    bool transactionFlushNeeded();
-    void addTransactionReadyFilters();
+    bool transactionFlushNeeded() REQUIRES(kMainThreadContext);
+    void addTransactionReadyFilters() REQUIRES(kMainThreadContext);
     TransactionHandler::TransactionReadiness transactionReadyTimelineCheck(
             const TransactionHandler::TransactionFlushState& flushState)
             REQUIRES(kMainThreadContext);
@@ -831,7 +834,7 @@
     uint32_t updateLayerCallbacksAndStats(const FrameTimelineInfo&, ResolvedComposerState&,
                                           int64_t desiredPresentTime, bool isAutoTimestamp,
                                           int64_t postTime, uint64_t transactionId)
-            REQUIRES(mStateLock);
+            REQUIRES(mStateLock, kMainThreadContext);
     uint32_t getTransactionFlags() const;
 
     // Sets the masked bits, and schedules a commit if needed.
@@ -847,7 +850,7 @@
     static LatchUnsignaledConfig getLatchUnsignaledConfig();
     bool shouldLatchUnsignaled(const layer_state_t&, size_t numStates, bool firstTransaction) const;
     bool applyTransactionsLocked(std::vector<TransactionState>& transactions, VsyncId)
-            REQUIRES(mStateLock);
+            REQUIRES(mStateLock, kMainThreadContext);
     uint32_t setDisplayStateLocked(const DisplayState& s) REQUIRES(mStateLock);
     uint32_t addInputWindowCommands(const InputWindowCommands& inputWindowCommands)
             REQUIRES(mStateLock);
@@ -1056,10 +1059,11 @@
     bool configureLocked() REQUIRES(mStateLock) REQUIRES(kMainThreadContext)
             EXCLUDES(mHotplugMutex);
 
-    // Returns a string describing the hotplug, or nullptr if it was rejected.
-    const char* processHotplug(PhysicalDisplayId, hal::HWDisplayId, bool connected,
-                               DisplayIdentificationInfo&&) REQUIRES(mStateLock)
-            REQUIRES(kMainThreadContext);
+    // Returns false on hotplug failure.
+    bool processHotplugConnect(PhysicalDisplayId, hal::HWDisplayId, DisplayIdentificationInfo&&,
+                               const char* displayString) REQUIRES(mStateLock, kMainThreadContext);
+    void processHotplugDisconnect(PhysicalDisplayId, const char* displayString)
+            REQUIRES(mStateLock, kMainThreadContext);
 
     sp<DisplayDevice> setupNewDisplayDeviceInternal(
             const wp<IBinder>& displayToken,
@@ -1136,9 +1140,10 @@
     void dumpHwcLayersMinidumpLockedLegacy(std::string& result) const REQUIRES(mStateLock);
 
     void appendSfConfigString(std::string& result) const;
-    void listLayers(std::string& result) const;
-    void dumpStats(const DumpArgs& args, std::string& result) const REQUIRES(mStateLock);
-    void clearStats(const DumpArgs& args, std::string& result);
+    void listLayers(std::string& result) const REQUIRES(kMainThreadContext);
+    void dumpStats(const DumpArgs& args, std::string& result) const
+            REQUIRES(mStateLock, kMainThreadContext);
+    void clearStats(const DumpArgs& args, std::string& result) REQUIRES(kMainThreadContext);
     void dumpTimeStats(const DumpArgs& args, bool asProto, std::string& result) const;
     void dumpFrameTimeline(const DumpArgs& args, std::string& result) const;
     void logFrameStats(TimePoint now) REQUIRES(kMainThreadContext);
@@ -1156,7 +1161,8 @@
     void dumpFrontEnd(std::string& result) REQUIRES(kMainThreadContext);
     void dumpVisibleFrontEnd(std::string& result) REQUIRES(mStateLock, kMainThreadContext);
 
-    perfetto::protos::LayersProto dumpDrawingStateProto(uint32_t traceFlags) const;
+    perfetto::protos::LayersProto dumpDrawingStateProto(uint32_t traceFlags) const
+            REQUIRES(kMainThreadContext);
     void dumpOffscreenLayersProto(perfetto::protos::LayersProto& layersProto,
                                   uint32_t traceFlags = LayerTracing::TRACE_ALL) const;
     google::protobuf::RepeatedPtrField<perfetto::protos::DisplayProto> dumpDisplayProto() const;
@@ -1204,7 +1210,8 @@
 
     ui::Rotation getPhysicalDisplayOrientation(DisplayId, bool isPrimary) const
             REQUIRES(mStateLock);
-    void traverseLegacyLayers(const LayerVector::Visitor& visitor) const;
+    void traverseLegacyLayers(const LayerVector::Visitor& visitor) const
+            REQUIRES(kMainThreadContext);
 
     void initBootProperties();
     void initTransactionTraceWriter();
@@ -1480,25 +1487,26 @@
     bool mPowerHintSessionEnabled;
 
     bool mLayerLifecycleManagerEnabled = false;
-    bool mLegacyFrontEndEnabled = true;
 
-    frontend::LayerLifecycleManager mLayerLifecycleManager;
-    frontend::LayerHierarchyBuilder mLayerHierarchyBuilder;
-    frontend::LayerSnapshotBuilder mLayerSnapshotBuilder;
+    frontend::LayerLifecycleManager mLayerLifecycleManager GUARDED_BY(kMainThreadContext);
+    frontend::LayerHierarchyBuilder mLayerHierarchyBuilder GUARDED_BY(kMainThreadContext);
+    frontend::LayerSnapshotBuilder mLayerSnapshotBuilder GUARDED_BY(kMainThreadContext);
 
-    std::vector<std::pair<uint32_t, std::string>> mDestroyedHandles;
-    std::vector<std::unique_ptr<frontend::RequestedLayerState>> mNewLayers;
-    std::vector<LayerCreationArgs> mNewLayerArgs;
+    std::vector<std::pair<uint32_t, std::string>> mDestroyedHandles GUARDED_BY(mCreatedLayersLock);
+    std::vector<std::unique_ptr<frontend::RequestedLayerState>> mNewLayers
+            GUARDED_BY(mCreatedLayersLock);
+    std::vector<LayerCreationArgs> mNewLayerArgs GUARDED_BY(mCreatedLayersLock);
     // These classes do not store any client state but help with managing transaction callbacks
     // and stats.
-    std::unordered_map<uint32_t, sp<Layer>> mLegacyLayers;
+    std::unordered_map<uint32_t, sp<Layer>> mLegacyLayers GUARDED_BY(kMainThreadContext);
 
-    TransactionHandler mTransactionHandler;
-    ui::DisplayMap<ui::LayerStack, frontend::DisplayInfo> mFrontEndDisplayInfos;
-    bool mFrontEndDisplayInfosChanged = false;
+    TransactionHandler mTransactionHandler GUARDED_BY(kMainThreadContext);
+    ui::DisplayMap<ui::LayerStack, frontend::DisplayInfo> mFrontEndDisplayInfos
+            GUARDED_BY(kMainThreadContext);
+    bool mFrontEndDisplayInfosChanged GUARDED_BY(kMainThreadContext) = false;
 
     // WindowInfo ids visible during the last commit.
-    std::unordered_set<int32_t> mVisibleWindowIds;
+    std::unordered_set<int32_t> mVisibleWindowIds GUARDED_BY(kMainThreadContext);
 
     // Mirroring
     // Map of displayid to mirrorRoot
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
index 50b167d..b1d8ba9 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
@@ -85,7 +85,7 @@
     return sp<Layer>::make(args);
 }
 
-sp<LayerFE> DefaultFactory::createLayerFE(const std::string& layerName) {
+sp<LayerFE> DefaultFactory::createLayerFE(const std::string& layerName, const Layer* /* owner */) {
     return sp<LayerFE>::make(layerName);
 }
 
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
index 540dec8..7ebf10f 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
@@ -41,7 +41,7 @@
     std::unique_ptr<compositionengine::CompositionEngine> createCompositionEngine() override;
     sp<Layer> createBufferStateLayer(const LayerCreationArgs& args) override;
     sp<Layer> createEffectLayer(const LayerCreationArgs& args) override;
-    sp<LayerFE> createLayerFE(const std::string& layerName) override;
+    sp<LayerFE> createLayerFE(const std::string& layerName, const Layer* owner) override;
     std::unique_ptr<FrameTracer> createFrameTracer() override;
     std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
             std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid) override;
diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h
index f1fbf01..c7d1fa0 100644
--- a/services/surfaceflinger/SurfaceFlingerFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerFactory.h
@@ -85,7 +85,7 @@
 
     virtual sp<Layer> createBufferStateLayer(const LayerCreationArgs& args) = 0;
     virtual sp<Layer> createEffectLayer(const LayerCreationArgs& args) = 0;
-    virtual sp<LayerFE> createLayerFE(const std::string& layerName) = 0;
+    virtual sp<LayerFE> createLayerFE(const std::string& layerName, const Layer* owner) = 0;
     virtual std::unique_ptr<FrameTracer> createFrameTracer() = 0;
     virtual std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
             std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid) = 0;
diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h
index 31cd2d7..89a8f92 100644
--- a/services/surfaceflinger/TransactionState.h
+++ b/services/surfaceflinger/TransactionState.h
@@ -86,7 +86,7 @@
     }
 
     template <typename Visitor>
-    void traverseStatesWithBuffersWhileTrue(Visitor&& visitor) {
+    void traverseStatesWithBuffersWhileTrue(Visitor&& visitor) NO_THREAD_SAFETY_ANALYSIS {
         for (auto state = states.begin(); state != states.end();) {
             if (state->state.hasBufferChanges() && state->externalTexture && state->state.surface) {
                 int result = visitor(*state);
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index 12043d4..f074b7d 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -141,6 +141,9 @@
     DUMP_READ_ONLY_FLAG(idle_screen_refresh_rate_timeout);
     DUMP_READ_ONLY_FLAG(graphite_renderengine);
     DUMP_READ_ONLY_FLAG(latch_unsignaled_with_auto_refresh_changed);
+    DUMP_READ_ONLY_FLAG(deprecate_vsync_sf);
+    DUMP_READ_ONLY_FLAG(allow_n_vsyncs_in_targeter);
+    DUMP_READ_ONLY_FLAG(detached_mirror);
 
 #undef DUMP_READ_ONLY_FLAG
 #undef DUMP_SERVER_FLAG
@@ -232,6 +235,9 @@
 FLAG_MANAGER_READ_ONLY_FLAG(ce_fence_promise, "");
 FLAG_MANAGER_READ_ONLY_FLAG(graphite_renderengine, "debug.renderengine.graphite")
 FLAG_MANAGER_READ_ONLY_FLAG(latch_unsignaled_with_auto_refresh_changed, "");
+FLAG_MANAGER_READ_ONLY_FLAG(deprecate_vsync_sf, "");
+FLAG_MANAGER_READ_ONLY_FLAG(allow_n_vsyncs_in_targeter, "");
+FLAG_MANAGER_READ_ONLY_FLAG(detached_mirror, "");
 
 /// Trunk stable server flags ///
 FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "")
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index 0239eb0..0acf754 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -79,6 +79,9 @@
     bool idle_screen_refresh_rate_timeout() const;
     bool graphite_renderengine() const;
     bool latch_unsignaled_with_auto_refresh_changed() const;
+    bool deprecate_vsync_sf() const;
+    bool allow_n_vsyncs_in_targeter() const;
+    bool detached_mirror() const;
 
 protected:
     // overridden for unit tests
diff --git a/services/surfaceflinger/surfaceflinger_flags.aconfig b/services/surfaceflinger/surfaceflinger_flags.aconfig
index 5174fa7..dea74d0 100644
--- a/services/surfaceflinger/surfaceflinger_flags.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags.aconfig
@@ -222,4 +222,15 @@
   }
 }
 
+flag {
+  name: "allow_n_vsyncs_in_targeter"
+  namespace: "core_graphics"
+  description: "This flag will enable utilizing N vsyncs in the FrameTargeter for past vsyncs"
+  bug: "308858993"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
 # This file is locked and should not be changed. Use surfaceflinger_flags_new.aconfig
diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
index 0b9fd58..4d3195d 100644
--- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
@@ -21,6 +21,28 @@
   }
  } # ce_fence_promise
 
+ flag {
+  name: "deprecate_vsync_sf"
+  namespace: "core_graphics"
+  description: "Depracate eVsyncSourceSurfaceFlinger and use vsync_app everywhere"
+  bug: "162235855"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # deprecate_vsync_sf
+
+ flag {
+  name: "detached_mirror"
+  namespace: "window_surfaces"
+  description: "Ignore local transform when mirroring a partial hierarchy"
+  bug: "337845753"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # deprecate_vsync_sf
+
 flag {
   name: "frame_rate_category_mrr"
   namespace: "core_graphics"
diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp
index 3b6a51a..c1ef48e 100644
--- a/services/surfaceflinger/tests/Credentials_test.cpp
+++ b/services/surfaceflinger/tests/Credentials_test.cpp
@@ -358,8 +358,13 @@
                 .apply();
     }
 
-    // Called from non privileged process
-    Transaction().setTrustedOverlay(surfaceControl, true);
+    // Attempt to set a trusted overlay from a non-privileged process. This should fail silently.
+    {
+        UIDFaker f{AID_BIN};
+        Transaction().setTrustedOverlay(surfaceControl, true).apply(/*synchronous=*/true);
+    }
+
+    // Verify that the layer was not made a trusted overlay.
     {
         UIDFaker f(AID_SYSTEM);
         auto windowIsPresentAndNotTrusted = [&](const std::vector<WindowInfo>& windowInfos) {
@@ -370,12 +375,14 @@
             }
             return !foundWindowInfo->inputConfig.test(WindowInfo::InputConfig::TRUSTED_OVERLAY);
         };
-        windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndNotTrusted);
+        ASSERT_TRUE(
+                windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndNotTrusted));
     }
 
+    // Verify that privileged processes are able to set trusted overlays.
     {
         UIDFaker f(AID_SYSTEM);
-        Transaction().setTrustedOverlay(surfaceControl, true);
+        Transaction().setTrustedOverlay(surfaceControl, true).apply(/*synchronous=*/true);
         auto windowIsPresentAndTrusted = [&](const std::vector<WindowInfo>& windowInfos) {
             auto foundWindowInfo =
                     WindowInfosListenerUtils::findMatchingWindowInfo(windowInfo, windowInfos);
@@ -384,7 +391,8 @@
             }
             return foundWindowInfo->inputConfig.test(WindowInfo::InputConfig::TRUSTED_OVERLAY);
         };
-        windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndTrusted);
+        ASSERT_TRUE(
+                windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndTrusted));
     }
 }
 
diff --git a/services/surfaceflinger/tests/OWNERS b/services/surfaceflinger/tests/OWNERS
new file mode 100644
index 0000000..56f2f1b
--- /dev/null
+++ b/services/surfaceflinger/tests/OWNERS
@@ -0,0 +1,8 @@
+per-file HdrSdrRatioOverlay_test.cpp = alecmouri@google.com, sallyqi@google.com, jreck@google.com
+
+# Most layer-related files are owned by WM
+per-file Layer* = set noparent
+per-file Layer* = pdwilliams@google.com, vishnun@google.com, melodymhsu@google.com
+
+per-file LayerHistoryTest.cpp = file:/services/surfaceflinger/OWNERS
+per-file LayerInfoTest.cpp = file:/services/surfaceflinger/OWNERS
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/CommitTest.cpp b/services/surfaceflinger/tests/unittests/CommitTest.cpp
index df53d19..7f29418 100644
--- a/services/surfaceflinger/tests/unittests/CommitTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CommitTest.cpp
@@ -17,9 +17,17 @@
 #undef LOG_TAG
 #define LOG_TAG "CommitTest"
 
+#include <DisplayHardware/HWComposer.h>
+#include <FrontEnd/LayerCreationArgs.h>
+#include <FrontEnd/RequestedLayerState.h>
+#include <compositionengine/CompositionEngine.h>
+#include <compositionengine/Feature.h>
+#include <compositionengine/mock/CompositionEngine.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
-
+#include <gui/LayerMetadata.h>
+#include <gui/SurfaceComposerClient.h>
+#include <mock/DisplayHardware/MockComposer.h>
 #include <renderengine/mock/RenderEngine.h>
 #include "TestableSurfaceFlinger.h"
 
@@ -27,18 +35,27 @@
 
 class CommitTest : public testing::Test {
 protected:
-    CommitTest() {
+    TestableSurfaceFlinger mFlinger;
+    renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
+
+    void flinger_setup() {
         mFlinger.setupMockScheduler();
         mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
         mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
     }
-    TestableSurfaceFlinger mFlinger;
-    renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
+
+    LayerCreationArgs createArgs(uint32_t id, LayerMetadata metadata, uint32_t parentId) {
+        LayerCreationArgs args(mFlinger.flinger(), nullptr, "layer",
+                               gui::ISurfaceComposerClient::eNoColorFill, metadata, id);
+        args.parentId = parentId;
+        return args;
+    }
 };
 
 namespace {
 
 TEST_F(CommitTest, noUpdatesDoesNotScheduleComposite) {
+    flinger_setup();
     bool unused;
     bool mustComposite = mFlinger.updateLayerSnapshots(VsyncId{1}, /*frameTimeNs=*/0,
                                                        /*transactionsFlushed=*/0, unused);
@@ -47,6 +64,7 @@
 
 // Ensure that we handle eTransactionNeeded correctly
 TEST_F(CommitTest, eTransactionNeededFlagSchedulesComposite) {
+    flinger_setup();
     // update display level color matrix
     mFlinger.setDaltonizerType(ColorBlindnessType::Deuteranomaly);
     bool unused;
@@ -55,5 +73,92 @@
     EXPECT_TRUE(mustComposite);
 }
 
+TEST_F(CommitTest, metadataNotIncluded) {
+    mFlinger.setupMockScheduler();
+    mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
+    compositionengine::mock::CompositionEngine* mCompositionEngine =
+            new compositionengine::mock::CompositionEngine();
+
+    // CompositionEngine setup with unset flag
+    compositionengine::FeatureFlags flags;
+    impl::HWComposer hwc = impl::HWComposer(std::make_unique<Hwc2::mock::Composer>());
+
+    EXPECT_CALL(*mCompositionEngine, getFeatureFlags).WillOnce(testing::Return(flags));
+    EXPECT_THAT(flags.test(compositionengine::Feature::kSnapshotLayerMetadata), false);
+
+    EXPECT_CALL(*mCompositionEngine, getHwComposer).WillOnce(testing::ReturnRef(hwc));
+
+    mFlinger.setupCompositionEngine(
+            std::unique_ptr<compositionengine::CompositionEngine>(mCompositionEngine));
+
+    // Create a parent layer with metadata and a child layer without. Metadata should not
+    // be included in the child layer when the flag is not set.
+    std::unordered_map<uint32_t, std::vector<uint8_t>> metadata = {{1, {'a', 'b'}}};
+    auto parentArgs = createArgs(1, LayerMetadata(metadata), UNASSIGNED_LAYER_ID);
+    auto parent = std::make_unique<frontend::RequestedLayerState>(parentArgs);
+    mFlinger.addLayer(parent);
+    mFlinger.injectLegacyLayer(sp<Layer>::make(parentArgs));
+
+    auto childArgs = createArgs(11, LayerMetadata(), 1);
+    auto child = std::make_unique<frontend::RequestedLayerState>(childArgs);
+    mFlinger.addLayer(child);
+    mFlinger.injectLegacyLayer(sp<Layer>::make(childArgs));
+
+    bool unused;
+    bool mustComposite = mFlinger.updateLayerSnapshots(VsyncId{1}, /*frameTimeNs=*/0,
+                                                       /*transactionsFlushed=*/1, unused);
+    EXPECT_TRUE(mustComposite);
+
+    auto parentMetadata = mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->layerMetadata.mMap;
+    auto childMetadata = mFlinger.mutableLayerSnapshotBuilder().getSnapshot(11)->layerMetadata.mMap;
+
+    EXPECT_EQ(metadata.at(1), parentMetadata.at(1));
+    EXPECT_NE(parentMetadata, childMetadata);
+}
+
+TEST_F(CommitTest, metadataIsIncluded) {
+    mFlinger.setupMockScheduler();
+    mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
+    compositionengine::mock::CompositionEngine* mCompositionEngine =
+            new compositionengine::mock::CompositionEngine();
+
+    // CompositionEngine setup with set flag
+    compositionengine::FeatureFlags flags;
+    flags |= compositionengine::Feature::kSnapshotLayerMetadata;
+    impl::HWComposer hwc = impl::HWComposer(std::make_unique<Hwc2::mock::Composer>());
+
+    EXPECT_CALL(*mCompositionEngine, getFeatureFlags).WillOnce(testing::Return(flags));
+    EXPECT_THAT(flags.test(compositionengine::Feature::kSnapshotLayerMetadata), true);
+
+    EXPECT_CALL(*mCompositionEngine, getHwComposer).WillOnce(testing::ReturnRef(hwc));
+
+    mFlinger.setupCompositionEngine(
+            std::unique_ptr<compositionengine::CompositionEngine>(mCompositionEngine));
+
+    // Create a parent layer with metadata and a child layer without. Metadata from the
+    // parent should be included in the child layer when the flag is set.
+    std::unordered_map<uint32_t, std::vector<uint8_t>> metadata = {{1, {'a', 'b'}}};
+    auto parentArgs = createArgs(1, LayerMetadata(metadata), UNASSIGNED_LAYER_ID);
+    auto parent = std::make_unique<frontend::RequestedLayerState>(parentArgs);
+    mFlinger.addLayer(parent);
+    mFlinger.injectLegacyLayer(sp<Layer>::make(parentArgs));
+
+    auto childArgs = createArgs(11, LayerMetadata(), 1);
+    auto child = std::make_unique<frontend::RequestedLayerState>(childArgs);
+    mFlinger.addLayer(child);
+    mFlinger.injectLegacyLayer(sp<Layer>::make(childArgs));
+
+    bool unused;
+    bool mustComposite = mFlinger.updateLayerSnapshots(VsyncId{1}, /*frameTimeNs=*/0,
+                                                       /*transactionsFlushed=*/1, unused);
+    EXPECT_TRUE(mustComposite);
+
+    auto parentMetadata = mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->layerMetadata.mMap;
+    auto childMetadata = mFlinger.mutableLayerSnapshotBuilder().getSnapshot(11)->layerMetadata.mMap;
+
+    EXPECT_EQ(metadata.at(1), parentMetadata.at(1));
+    EXPECT_EQ(parentMetadata, childMetadata);
+}
+
 } // namespace
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index 3eabe1f..625d2e6 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -493,7 +493,7 @@
     EXPECT_CALL(mockTracker, nextAnticipatedVSyncTimeFrom(_, _))
             .WillOnce(Return(preferredExpectedPresentationTime));
 
-    VsyncEventData vsyncEventData = mThread->getLatestVsyncEventData(mConnection);
+    VsyncEventData vsyncEventData = mThread->getLatestVsyncEventData(mConnection, now);
 
     // Check EventThread immediately requested a resync.
     EXPECT_TRUE(mResyncCallRecorder.waitForCall().has_value());
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
index e8e7667..fd15eef 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
@@ -155,6 +155,17 @@
         mLifecycleManager.applyTransactions(transactions);
     }
 
+    void setPosition(uint32_t id, float x, float y) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+        transactions.back().states.front().state.what = layer_state_t::ePositionChanged;
+        transactions.back().states.front().state.x = x;
+        transactions.back().states.front().state.y = y;
+        transactions.back().states.front().layerId = id;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
     virtual void mirrorLayer(uint32_t id, uint32_t parentId, uint32_t layerIdToMirror) {
         std::vector<std::unique_ptr<RequestedLayerState>> layers;
         layers.emplace_back(std::make_unique<RequestedLayerState>(
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
index 2fb80b1..a61fa1e 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
@@ -1003,8 +1003,6 @@
         mappings.push_back(std::make_pair(kAppId1, kThreshold1));
         mappings.push_back(std::make_pair(kAppId2, kThreshold2));
 
-        mFlinger.enableNewFrontEnd();
-
         mScheduler->onActiveDisplayAreaChanged(DISPLAY_WIDTH * DISPLAY_HEIGHT);
         mScheduler->updateSmallAreaDetection(mappings);
     }
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index c63aaeb..088d0d2 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -46,6 +46,7 @@
 using MockLayer = android::mock::MockLayer;
 
 using android::mock::createDisplayMode;
+using android::mock::createVrrDisplayMode;
 
 // WARNING: LEGACY TESTS FOR LEGACY FRONT END
 // Update LayerHistoryIntegrationTest instead
@@ -138,12 +139,14 @@
         ASSERT_EQ(desiredRefreshRate, summary[0].desiredRefreshRate);
     }
 
-    std::shared_ptr<RefreshRateSelector> mSelector =
-            std::make_shared<RefreshRateSelector>(makeModes(createDisplayMode(DisplayModeId(0),
-                                                                              LO_FPS),
-                                                            createDisplayMode(DisplayModeId(1),
-                                                                              HI_FPS)),
-                                                  DisplayModeId(0));
+    static constexpr auto kVrrModeId = DisplayModeId(2);
+    std::shared_ptr<RefreshRateSelector> mSelector = std::make_shared<RefreshRateSelector>(
+            makeModes(createDisplayMode(DisplayModeId(0), LO_FPS),
+                      createDisplayMode(DisplayModeId(1), HI_FPS),
+                      createVrrDisplayMode(kVrrModeId, HI_FPS,
+                                           hal::VrrConfig{.minFrameIntervalNs =
+                                                                  HI_FPS.getPeriodNsecs()})),
+            DisplayModeId(0));
 
     mock::SchedulerCallback mSchedulerCallback;
     TestableSurfaceFlinger mFlinger;
@@ -503,7 +506,7 @@
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
-    // layer became inactive, but the vote stays
+    // layer became infrequent, but the vote stays
     setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
     time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
@@ -537,7 +540,7 @@
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
-    // layer became inactive, but the vote stays
+    // layer became infrequent, but the vote stays
     setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
     time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
@@ -548,6 +551,87 @@
     EXPECT_EQ(0, frequentLayerCount(time));
 }
 
+TEST_F(LayerHistoryTest, oneLayerExplicitGte_vrr) {
+    // Set the test to be on a vrr mode.
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    mSelector->setActiveMode(kVrrModeId, HI_FPS);
+
+    auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree())
+            .WillRepeatedly(Return(Layer::FrameRate(33_Hz, Layer::FrameRateCompatibility::Gte,
+                                                    Seamlessness::OnlySeamless,
+                                                    FrameRateCategory::Default)));
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitGte, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(33_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[0].frameRateCategory);
+
+    // layer became inactive, but the vote stays
+    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitGte, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(33_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[0].frameRateCategory);
+}
+
+// Test for MRR device with VRR features enabled.
+TEST_F(LayerHistoryTest, oneLayerExplicitGte_nonVrr) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+    // The vrr_config flag is explicitly not set false because this test for an MRR device
+    // should still work in a VRR-capable world.
+
+    auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree())
+            .WillRepeatedly(Return(Layer::FrameRate(33_Hz, Layer::FrameRateCompatibility::Gte,
+                                                    Seamlessness::OnlySeamless,
+                                                    FrameRateCategory::Default)));
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[0].frameRateCategory);
+
+    // layer became infrequent, but the vote stays
+    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[0].frameRateCategory);
+}
+
 TEST_F(LayerHistoryTest, oneLayerExplicitVoteWithCategory_vrrFeatureOff) {
     SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, false);
 
@@ -608,7 +692,7 @@
     EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
     EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[0].frameRateCategory);
 
-    // layer became inactive, but the vote stays
+    // layer became infrequent, but the vote stays
     setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
     time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
@@ -645,7 +729,7 @@
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
-    // layer became inactive
+    // layer became infrequent
     time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
     EXPECT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(1, activeLayerCount());
@@ -686,7 +770,7 @@
     EXPECT_EQ(73.4_Hz, summarizeLayerHistory(time)[1].desiredRefreshRate);
     EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[1].frameRateCategory);
 
-    // layer became inactive, but the vote stays
+    // layer became infrequent, but the vote stays
     setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
     time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
     ASSERT_EQ(2, summarizeLayerHistory(time).size());
@@ -1091,7 +1175,7 @@
     EXPECT_EQ(0, frequentLayerCount(time));
     EXPECT_EQ(0, animatingLayerCount(time));
 
-    // layer became inactive
+    // Layer still active due to front buffering, but it's infrequent.
     time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index ae9a89c..7c6cff0 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -1302,4 +1302,33 @@
     EXPECT_EQ(getSnapshot(1221)->inputInfo.canOccludePresentation, true);
 }
 
+TEST_F(LayerSnapshotTest, mirroredHierarchyIgnoresLocalTransform) {
+    SET_FLAG_FOR_TEST(flags::detached_mirror, true);
+    reparentLayer(12, UNASSIGNED_LAYER_ID);
+    setPosition(11, 2, 20);
+    setPosition(111, 20, 200);
+    mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
+    std::vector<uint32_t> expected = {1, 11, 111, 13, 14, 11, 111, 2};
+    UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+
+    // mirror root has no position set
+    EXPECT_EQ(getSnapshot({.id = 11, .mirrorRootIds = 14u})->localTransform.tx(), 0);
+    EXPECT_EQ(getSnapshot({.id = 11, .mirrorRootIds = 14u})->localTransform.ty(), 0);
+    // original root still has a position
+    EXPECT_EQ(getSnapshot({.id = 11})->localTransform.tx(), 2);
+    EXPECT_EQ(getSnapshot({.id = 11})->localTransform.ty(), 20);
+
+    // mirror child still has the correct position
+    EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootIds = 14u})->localTransform.tx(), 20);
+    EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootIds = 14u})->localTransform.ty(), 200);
+    EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootIds = 14u})->geomLayerTransform.tx(), 20);
+    EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootIds = 14u})->geomLayerTransform.ty(), 200);
+
+    // original child still has the correct position including its parent's position
+    EXPECT_EQ(getSnapshot({.id = 111})->localTransform.tx(), 20);
+    EXPECT_EQ(getSnapshot({.id = 111})->localTransform.ty(), 200);
+    EXPECT_EQ(getSnapshot({.id = 111})->geomLayerTransform.tx(), 22);
+    EXPECT_EQ(getSnapshot({.id = 111})->geomLayerTransform.ty(), 220);
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp
index f127213..5852b1c 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp
@@ -28,6 +28,7 @@
 class ColorMatrixTest : public CommitAndCompositeTest {};
 
 TEST_F(ColorMatrixTest, colorMatrixChanged) {
+    mFlinger.enableLayerLifecycleManager();
     EXPECT_COLOR_MATRIX_CHANGED(true, true);
     mFlinger.mutableTransactionFlags() |= eTransactionNeeded;
 
@@ -45,6 +46,7 @@
 }
 
 TEST_F(ColorMatrixTest, colorMatrixChangedAfterDisplayTransaction) {
+    mFlinger.enableLayerLifecycleManager();
     EXPECT_COLOR_MATRIX_CHANGED(true, true);
     mFlinger.mutableTransactionFlags() |= eTransactionNeeded;
 
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp
index 22b72f9..f2e2c8a 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp
@@ -33,24 +33,45 @@
 TEST_F(DisplayTransactionTest, notifyPowerBoostNotifiesTouchEvent) {
     using namespace std::chrono_literals;
 
+    std::mutex timerMutex;
+    std::condition_variable cv;
+
     injectDefaultInternalDisplay([](FakeDisplayDeviceInjector&) {});
 
-    mFlinger.scheduler()->replaceTouchTimer(100);
-    std::this_thread::sleep_for(10ms);                  // wait for callback to be triggered
+    std::unique_lock lock(timerMutex);
+    bool didReset = false; // keeps track of what the most recent call was
+
+    auto waitForTimerReset = [&] { cv.wait_for(lock, 100ms, [&] { return didReset; }); };
+    auto waitForTimerExpired = [&] { cv.wait_for(lock, 100ms, [&] { return !didReset; }); };
+
+    // Add extra logic to unblock the test when the timer callbacks get called
+    mFlinger.scheduler()->replaceTouchTimer(10, [&](bool isReset) {
+        {
+            std::unique_lock lock(timerMutex); // guarantee we're waiting on the cv
+            didReset = isReset;
+        }
+        cv.notify_one();                   // wake the cv
+        std::unique_lock lock(timerMutex); // guarantee we finished the cv logic
+    });
+
+    waitForTimerReset();
     EXPECT_TRUE(mFlinger.scheduler()->isTouchActive()); // Starting timer activates touch
 
-    std::this_thread::sleep_for(110ms); // wait for reset touch timer to expire and trigger callback
-    EXPECT_FALSE(mFlinger.scheduler()->isTouchActive());
+    waitForTimerExpired();
+    EXPECT_FALSE(mFlinger.scheduler()->isTouchActive()); // Stopping timer deactivates touch
 
     EXPECT_EQ(NO_ERROR, mFlinger.notifyPowerBoost(static_cast<int32_t>(Boost::CAMERA_SHOT)));
-    std::this_thread::sleep_for(10ms); // wait for callback to maybe be triggered
-    EXPECT_FALSE(mFlinger.scheduler()->isTouchActive());
 
-    std::this_thread::sleep_for(110ms); // wait for reset touch timer to expire and trigger callback
+    EXPECT_FALSE(mFlinger.scheduler()->isTouchActive());
+    // Wait for the timer to start just in case
+    waitForTimerReset();
+    EXPECT_FALSE(mFlinger.scheduler()->isTouchActive());
+    // Wait for the timer to stop, again just in case
+    waitForTimerExpired();
     EXPECT_FALSE(mFlinger.scheduler()->isTouchActive());
 
     EXPECT_EQ(NO_ERROR, mFlinger.notifyPowerBoost(static_cast<int32_t>(Boost::INTERACTION)));
-    std::this_thread::sleep_for(10ms); // wait for callback to be triggered.
+    waitForTimerReset();
     EXPECT_TRUE(mFlinger.scheduler()->isTouchActive());
 }
 
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 1472ebf..1e02c67 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -140,14 +140,25 @@
         return mLayerHistory.mActiveLayerInfos.size();
     }
 
-    void replaceTouchTimer(int64_t millis) {
+    void replaceTouchTimer(int64_t millis,
+                           std::function<void(bool isReset)>&& testCallback = nullptr) {
         if (mTouchTimer) {
             mTouchTimer.reset();
         }
         mTouchTimer.emplace(
                 "Testable Touch timer", std::chrono::milliseconds(millis),
-                [this] { touchTimerCallback(TimerState::Reset); },
-                [this] { touchTimerCallback(TimerState::Expired); });
+                [this, testCallback] {
+                    touchTimerCallback(TimerState::Reset);
+                    if (testCallback != nullptr) {
+                        testCallback(true);
+                    }
+                },
+                [this, testCallback] {
+                    touchTimerCallback(TimerState::Expired);
+                    if (testCallback != nullptr) {
+                        testCallback(false);
+                    }
+                });
         mTouchTimer->start();
     }
 
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 82023b0..a73b7d0 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -18,10 +18,12 @@
 
 #include <algorithm>
 #include <chrono>
+#include <memory>
 #include <variant>
 
 #include <ftl/fake_guard.h>
 #include <ftl/match.h>
+#include <gui/LayerMetadata.h>
 #include <gui/ScreenCaptureResults.h>
 #include <ui/DynamicDisplayInfo.h>
 
@@ -38,6 +40,7 @@
 #include "FrameTracer/FrameTracer.h"
 #include "FrontEnd/LayerCreationArgs.h"
 #include "FrontEnd/LayerHandle.h"
+#include "FrontEnd/RequestedLayerState.h"
 #include "Layer.h"
 #include "NativeWindowSurface.h"
 #include "RenderArea.h"
@@ -45,6 +48,7 @@
 #include "Scheduler/RefreshRateSelector.h"
 #include "SurfaceFlinger.h"
 #include "TestableScheduler.h"
+#include "android/gui/ISurfaceComposerClient.h"
 #include "mock/DisplayHardware/MockComposer.h"
 #include "mock/DisplayHardware/MockDisplayMode.h"
 #include "mock/DisplayHardware/MockPowerAdvisor.h"
@@ -127,7 +131,7 @@
 
     sp<Layer> createEffectLayer(const LayerCreationArgs&) override { return nullptr; }
 
-    sp<LayerFE> createLayerFE(const std::string& layerName) override {
+    sp<LayerFE> createLayerFE(const std::string& layerName, const Layer* /* owner */) override {
         return sp<LayerFE>::make(layerName);
     }
 
@@ -197,6 +201,11 @@
         mFlinger->mCompositionEngine->setTimeStats(timeStats);
     }
 
+    void setupCompositionEngine(
+            std::unique_ptr<compositionengine::CompositionEngine> compositionEngine) {
+        mFlinger->mCompositionEngine = std::move(compositionEngine);
+    }
+
     enum class SchedulerCallbackImpl { kNoOp, kMock };
 
     struct DefaultDisplayMode {
@@ -492,9 +501,11 @@
 
     auto& getTransactionQueue() { return mFlinger->mTransactionHandler.mLocklessTransactionQueue; }
     auto& getPendingTransactionQueue() {
+        ftl::FakeGuard guard(kMainThreadContext);
         return mFlinger->mTransactionHandler.mPendingTransactionQueues;
     }
     size_t getPendingTransactionCount() {
+        ftl::FakeGuard guard(kMainThreadContext);
         return mFlinger->mTransactionHandler.mPendingTransactionCount.load();
     }
 
@@ -513,7 +524,9 @@
     }
 
     auto setTransactionStateInternal(TransactionState& transaction) {
-        return mFlinger->mTransactionHandler.queueTransaction(std::move(transaction));
+        return FTL_FAKE_GUARD(kMainThreadContext,
+                              mFlinger->mTransactionHandler.queueTransaction(
+                                      std::move(transaction)));
     }
 
     auto flushTransactionQueues() {
@@ -583,6 +596,13 @@
         return mFlinger->getDisplayStats(displayToken, outInfo);
     }
 
+    // Used to add a layer before updateLayerSnapshots is called.
+    // Must have transactionsFlushed enabled for the new layer to be updated.
+    void addLayer(std::unique_ptr<frontend::RequestedLayerState>& layer) {
+        std::scoped_lock<std::mutex> lock(mFlinger->mCreatedLayersLock);
+        mFlinger->mNewLayers.emplace_back(std::move(layer));
+    }
+
     /* ------------------------------------------------------------------------
      * Read-only access to private data to assert post-conditions.
      */
@@ -598,15 +618,20 @@
     }
 
     void injectLegacyLayer(sp<Layer> layer) {
-        mFlinger->mLegacyLayers[static_cast<uint32_t>(layer->sequence)] = layer;
+        FTL_FAKE_GUARD(kMainThreadContext,
+                       mFlinger->mLegacyLayers[static_cast<uint32_t>(layer->sequence)] = layer);
     };
 
-    void releaseLegacyLayer(uint32_t sequence) { mFlinger->mLegacyLayers.erase(sequence); };
+    void releaseLegacyLayer(uint32_t sequence) {
+        FTL_FAKE_GUARD(kMainThreadContext, mFlinger->mLegacyLayers.erase(sequence));
+    };
 
     auto setLayerHistoryDisplayArea(uint32_t displayArea) {
         return mFlinger->mScheduler->onActiveDisplayAreaChanged(displayArea);
     };
-    auto updateLayerHistory(nsecs_t now) { return mFlinger->updateLayerHistory(now); };
+    auto updateLayerHistory(nsecs_t now) {
+        return FTL_FAKE_GUARD(kMainThreadContext, mFlinger->updateLayerHistory(now));
+    };
     auto setDaltonizerType(ColorBlindnessType type) {
         mFlinger->mDaltonizer.setType(type);
         return mFlinger->updateColorMatrixLocked();
@@ -666,10 +691,9 @@
         return mFlinger->initTransactionTraceWriter();
     }
 
-    void enableNewFrontEnd() {
-        mFlinger->mLayerLifecycleManagerEnabled = true;
-        mFlinger->mLegacyFrontEndEnabled = false;
-    }
+    // Needed since mLayerLifecycleManagerEnabled is false by default and must
+    // be enabled for tests to go through the new front end path.
+    void enableLayerLifecycleManager() { mFlinger->mLayerLifecycleManagerEnabled = true; }
 
     void notifyExpectedPresentIfRequired(PhysicalDisplayId displayId, Period vsyncPeriod,
                                          TimePoint expectedPresentTime, Fps frameInterval,
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
index e8630ba..4efdfe8 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
@@ -36,6 +36,7 @@
     MOCK_METHOD(void, notifyDisplayUpdateImminentAndCpuReset, (), (override));
     MOCK_METHOD(bool, usePowerHintSession, (), (override));
     MOCK_METHOD(bool, supportsPowerHintSession, (), (override));
+    MOCK_METHOD(bool, supportsGpuReporting, (), (override));
     MOCK_METHOD(void, updateTargetWorkDuration, (Duration targetDuration), (override));
     MOCK_METHOD(void, reportActualWorkDuration, (), (override));
     MOCK_METHOD(void, enablePowerHintSession, (bool enabled), (override));
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
index e2b0ed1..8dd1a34 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -55,7 +55,7 @@
                 (override));
     MOCK_METHOD(void, requestNextVsync, (const sp<android::EventThreadConnection>&), (override));
     MOCK_METHOD(VsyncEventData, getLatestVsyncEventData,
-                (const sp<android::EventThreadConnection>&), (const, override));
+                (const sp<android::EventThreadConnection>&, nsecs_t), (const, override));
     MOCK_METHOD(void, requestLatestConfig, (const sp<android::EventThreadConnection>&));
     MOCK_METHOD(void, pauseVsyncCallback, (bool));
     MOCK_METHOD(void, onNewVsyncSchedule, (std::shared_ptr<scheduler::VsyncSchedule>), (override));
diff --git a/services/surfaceflinger/tests/unittests/mock/MockLayer.h b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
index 4204aa0..002fa9f 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockLayer.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
@@ -40,7 +40,7 @@
     MOCK_CONST_METHOD0(getType, const char*());
     MOCK_METHOD0(getFrameSelectionPriority, int32_t());
     MOCK_CONST_METHOD0(isVisible, bool());
-    MOCK_METHOD1(createClone, sp<Layer>(uint32_t));
+    MOCK_METHOD0(createClone, sp<Layer>());
     MOCK_CONST_METHOD0(getFrameRateForLayerTree, FrameRate());
     MOCK_CONST_METHOD0(getDefaultFrameRateCompatibility, scheduler::FrameRateCompatibility());
     MOCK_CONST_METHOD0(getOwnerUid, uid_t());
diff --git a/services/vibratorservice/TEST_MAPPING b/services/vibratorservice/TEST_MAPPING
index 81c37b0..af48673 100644
--- a/services/vibratorservice/TEST_MAPPING
+++ b/services/vibratorservice/TEST_MAPPING
@@ -1,17 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "libvibratorservice_test",
-      "options": [
-        {
-          // TODO(b/293603710): Fix flakiness
-          "exclude-filter": "VibratorCallbackSchedulerTest#TestScheduleRunsOnlyAfterDelay"
-        },
-        {
-          // TODO(b/293623689): Fix flakiness
-          "exclude-filter": "VibratorCallbackSchedulerTest#TestScheduleMultipleCallbacksRunsInDelayOrder"
-        }
-      ]
+      "name": "libvibratorservice_test"
     }
   ],
   "postsubmit": [
diff --git a/services/vibratorservice/VibratorCallbackScheduler.cpp b/services/vibratorservice/VibratorCallbackScheduler.cpp
index 7eda9ef..b2b1988 100644
--- a/services/vibratorservice/VibratorCallbackScheduler.cpp
+++ b/services/vibratorservice/VibratorCallbackScheduler.cpp
@@ -87,13 +87,13 @@
             lock.lock();
         }
         if (mQueue.empty()) {
-            // Wait until a new callback is scheduled.
-            mCondition.wait(mMutex);
+            // Wait until a new callback is scheduled or destructor was called.
+            mCondition.wait(lock, [this] { return mFinished || !mQueue.empty(); });
         } else {
-            // Wait until next callback expires, or a new one is scheduled.
+            // Wait until next callback expires or a new one is scheduled.
             // Use the monotonic steady clock to wait for the measured delay interval via wait_for
             // instead of using a wall clock via wait_until.
-            mCondition.wait_for(mMutex, mQueue.top().getWaitForExpirationDuration());
+            mCondition.wait_for(lock, mQueue.top().getWaitForExpirationDuration());
         }
     }
 }
diff --git a/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp b/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp
index e11a809..9b30337 100644
--- a/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp
+++ b/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp
@@ -17,7 +17,9 @@
 #define LOG_TAG "VibratorHalControllerBenchmarks"
 
 #include <benchmark/benchmark.h>
+#include <binder/ProcessState.h>
 #include <vibratorservice/VibratorHalController.h>
+#include <future>
 
 using ::android::enum_range;
 using ::android::hardware::vibrator::CompositeEffect;
@@ -30,14 +32,99 @@
 using ::benchmark::State;
 using ::benchmark::internal::Benchmark;
 
+using std::chrono::milliseconds;
+
 using namespace android;
 using namespace std::chrono_literals;
 
+// Fixed number of iterations for benchmarks that trigger a vibration on the loop.
+// They require slow cleanup to ensure a stable state on each run and less noisy metrics.
+static constexpr auto VIBRATION_ITERATIONS = 500;
+
+// Timeout to wait for vibration callback completion.
+static constexpr auto VIBRATION_CALLBACK_TIMEOUT = 100ms;
+
+// Max duration the vibrator can be turned on, in milliseconds.
+static constexpr auto MAX_ON_DURATION_MS = milliseconds(UINT16_MAX);
+
+// Helper to wait for the vibrator to become idle between vibrate bench iterations.
+class HalCallback {
+public:
+    HalCallback(std::function<void()>&& waitFn, std::function<void()>&& completeFn)
+          : mWaitFn(std::move(waitFn)), mCompleteFn(std::move(completeFn)) {}
+    ~HalCallback() = default;
+
+    std::function<void()> completeFn() const { return mCompleteFn; }
+
+    void waitForComplete() const { mWaitFn(); }
+
+private:
+    std::function<void()> mWaitFn;
+    std::function<void()> mCompleteFn;
+};
+
+// Helper for vibration callbacks, kept by the Fixture until all pending callbacks are done.
+class HalCallbacks {
+public:
+    HalCallback next() {
+        std::unique_lock<std::mutex> lock(mMutex);
+        auto id = mCurrentId++;
+        mPendingPromises[id] = std::promise<void>();
+        mPendingFutures[id] = mPendingPromises[id].get_future(); // Can only be called once.
+        return HalCallback([&, id]() { waitForComplete(id); }, [&, id]() { onComplete(id); });
+    }
+
+    void onComplete(int32_t id) {
+        std::unique_lock<std::mutex> lock(mMutex);
+        auto promise = mPendingPromises.find(id);
+        if (promise != mPendingPromises.end()) {
+            promise->second.set_value();
+            mPendingPromises.erase(promise);
+        }
+    }
+
+    void waitForComplete(int32_t id) {
+        // Wait until the HAL has finished processing previous vibration before starting a new one,
+        // so the HAL state is consistent on each run and metrics are less noisy. Some of the newest
+        // HAL implementations are waiting on previous vibration cleanup and might be significantly
+        // slower, so make sure we measure vibrations on a clean slate.
+        if (mPendingFutures[id].wait_for(VIBRATION_CALLBACK_TIMEOUT) == std::future_status::ready) {
+            mPendingFutures.erase(id);
+        }
+    }
+
+    void waitForPending() {
+        // Wait for pending callbacks from the test, possibly skipped with error.
+        for (auto& [id, future] : mPendingFutures) {
+            future.wait_for(VIBRATION_CALLBACK_TIMEOUT);
+        }
+        mPendingFutures.clear();
+        {
+            std::unique_lock<std::mutex> lock(mMutex);
+            mPendingPromises.clear();
+        }
+    }
+
+private:
+    std::mutex mMutex;
+    std::map<int32_t, std::promise<void>> mPendingPromises GUARDED_BY(mMutex);
+    std::map<int32_t, std::future<void>> mPendingFutures;
+    int32_t mCurrentId;
+};
+
 class VibratorBench : public Fixture {
 public:
-    void SetUp(State& /*state*/) override { mController.init(); }
+    void SetUp(State& /*state*/) override {
+        android::ProcessState::self()->setThreadPoolMaxThreadCount(1);
+        android::ProcessState::self()->startThreadPool();
+        mController.init();
+    }
 
-    void TearDown(State& state) override { turnVibratorOff(state); }
+    void TearDown(State& /*state*/) override {
+        turnVibratorOff();
+        disableExternalControl();
+        mCallbacks.waitForPending();
+    }
 
     static void DefaultConfig(Benchmark* b) { b->Unit(kMicrosecond); }
 
@@ -47,38 +134,59 @@
 
 protected:
     vibrator::HalController mController;
+    HalCallbacks mCallbacks;
+
+    static void SlowBenchConfig(Benchmark* b) { b->Iterations(VIBRATION_ITERATIONS); }
 
     auto getOtherArg(const State& state, std::size_t index) const { return state.range(index + 0); }
 
-    bool hasCapabilities(vibrator::Capabilities&& query, State& state) {
+    vibrator::HalResult<void> turnVibratorOff() {
+        return mController.doWithRetry<void>([](auto hal) { return hal->off(); }, "off");
+    }
+
+    vibrator::HalResult<void> disableExternalControl() {
+        auto disableExternalControlFn = [](auto hal) { return hal->setExternalControl(false); };
+        return mController.doWithRetry<void>(disableExternalControlFn, "setExternalControl false");
+    }
+
+    bool shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities query, State& state) {
         auto result = mController.getInfo().capabilities;
         if (result.isFailed()) {
             state.SkipWithError(result.errorMessage());
-            return false;
+            return true;
         }
         if (!result.isOk()) {
-            return false;
+            state.SkipWithMessage("capability result is unsupported");
+            return true;
         }
-        return (result.value() & query) == query;
-    }
-
-    void turnVibratorOff(State& state) {
-        checkHalResult(halCall<void>(mController, [](auto hal) { return hal->off(); }), state);
+        if ((result.value() & query) != query) {
+            state.SkipWithMessage("missing capability");
+            return true;
+        }
+        return false;
     }
 
     template <class R>
-    bool checkHalResult(const vibrator::HalResult<R>& result, State& state) {
+    bool shouldSkipWithError(const vibrator::HalFunction<vibrator::HalResult<R>>& halFn,
+                             const char* label, State& state) {
+        return shouldSkipWithError(mController.doWithRetry<R>(halFn, label), state);
+    }
+
+    template <class R>
+    bool shouldSkipWithError(const vibrator::HalResult<R>& result, State& state) {
         if (result.isFailed()) {
             state.SkipWithError(result.errorMessage());
-            return false;
+            return true;
         }
-        return true;
+        return false;
     }
+};
 
-    template <class R>
-    vibrator::HalResult<R> halCall(vibrator::HalController& controller,
-                                   const vibrator::HalFunction<vibrator::HalResult<R>>& halFn) {
-        return controller.doWithRetry<R>(halFn, "benchmark");
+class SlowVibratorBench : public VibratorBench {
+public:
+    static void DefaultConfig(Benchmark* b) {
+        VibratorBench::DefaultConfig(b);
+        SlowBenchConfig(b);
     }
 };
 
@@ -91,25 +199,32 @@
 
 BENCHMARK_WRAPPER(VibratorBench, init, {
     for (auto _ : state) {
+        // Setup
         state.PauseTiming();
         vibrator::HalController controller;
         state.ResumeTiming();
+
+        // Test
         controller.init();
     }
 });
 
 BENCHMARK_WRAPPER(VibratorBench, initCached, {
+    // First call to cache values.
+    mController.init();
+
     for (auto _ : state) {
         mController.init();
     }
 });
 
 BENCHMARK_WRAPPER(VibratorBench, ping, {
+    auto pingFn = [](auto hal) { return hal->ping(); };
+
     for (auto _ : state) {
-        state.ResumeTiming();
-        auto ret = halCall<void>(mController, [](auto hal) { return hal->ping(); });
-        state.PauseTiming();
-        checkHalResult(ret, state);
+        if (shouldSkipWithError<void>(pingFn, "ping", state)) {
+            return;
+        }
     }
 });
 
@@ -119,164 +234,131 @@
     }
 });
 
-BENCHMARK_WRAPPER(VibratorBench, on, {
-    auto duration = 60s;
-    auto callback = []() {};
+BENCHMARK_WRAPPER(SlowVibratorBench, on, {
+    auto duration = MAX_ON_DURATION_MS;
 
     for (auto _ : state) {
-        state.ResumeTiming();
-        auto ret =
-                halCall<void>(mController, [&](auto hal) { return hal->on(duration, callback); });
+        // Setup
         state.PauseTiming();
-        if (checkHalResult(ret, state)) {
-            turnVibratorOff(state);
+        auto cb = mCallbacks.next();
+        auto onFn = [&](auto hal) { return hal->on(duration, cb.completeFn()); };
+        state.ResumeTiming();
+
+        // Test
+        if (shouldSkipWithError<void>(onFn, "on", state)) {
+            return;
         }
+
+        // Cleanup
+        state.PauseTiming();
+        if (shouldSkipWithError(turnVibratorOff(), state)) {
+            return;
+        }
+        cb.waitForComplete();
+        state.ResumeTiming();
     }
 });
 
-BENCHMARK_WRAPPER(VibratorBench, off, {
-    auto duration = 60s;
-    auto callback = []() {};
+BENCHMARK_WRAPPER(SlowVibratorBench, off, {
+    auto duration = MAX_ON_DURATION_MS;
 
     for (auto _ : state) {
+        // Setup
         state.PauseTiming();
-        auto ret =
-                halCall<void>(mController, [&](auto hal) { return hal->on(duration, callback); });
-        if (!checkHalResult(ret, state)) {
-            continue;
+        auto cb = mCallbacks.next();
+        auto onFn = [&](auto hal) { return hal->on(duration, cb.completeFn()); };
+        if (shouldSkipWithError<void>(onFn, "on", state)) {
+            return;
         }
+        auto offFn = [&](auto hal) { return hal->off(); };
         state.ResumeTiming();
-        turnVibratorOff(state);
+
+        // Test
+        if (shouldSkipWithError<void>(offFn, "off", state)) {
+            return;
+        }
+
+        // Cleanup
+        state.PauseTiming();
+        cb.waitForComplete();
+        state.ResumeTiming();
     }
 });
 
 BENCHMARK_WRAPPER(VibratorBench, setAmplitude, {
-    if (!hasCapabilities(vibrator::Capabilities::AMPLITUDE_CONTROL, state)) {
-        state.SkipWithMessage("missing capability");
+    if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::AMPLITUDE_CONTROL, state)) {
         return;
     }
 
-    auto duration = 60s;
-    auto callback = []() {};
+    auto duration = MAX_ON_DURATION_MS;
     auto amplitude = 1.0f;
+    auto setAmplitudeFn = [&](auto hal) { return hal->setAmplitude(amplitude); };
 
-    for (auto _ : state) {
-        state.PauseTiming();
-        vibrator::HalController controller;
-        controller.init();
-        auto result =
-                halCall<void>(controller, [&](auto hal) { return hal->on(duration, callback); });
-        if (!checkHalResult(result, state)) {
-            continue;
-        }
-        state.ResumeTiming();
-        auto ret =
-                halCall<void>(controller, [&](auto hal) { return hal->setAmplitude(amplitude); });
-        state.PauseTiming();
-        if (checkHalResult(ret, state)) {
-            turnVibratorOff(state);
-        }
-    }
-});
-
-BENCHMARK_WRAPPER(VibratorBench, setAmplitudeCached, {
-    if (!hasCapabilities(vibrator::Capabilities::AMPLITUDE_CONTROL, state)) {
-        state.SkipWithMessage("missing capability");
+    auto onFn = [&](auto hal) { return hal->on(duration, [&]() {}); };
+    if (shouldSkipWithError<void>(onFn, "on", state)) {
         return;
     }
 
-    auto duration = 60s;
-    auto callback = []() {};
-    auto amplitude = 1.0f;
-
-    auto onResult =
-            halCall<void>(mController, [&](auto hal) { return hal->on(duration, callback); });
-    checkHalResult(onResult, state);
-
     for (auto _ : state) {
-        auto ret =
-                halCall<void>(mController, [&](auto hal) { return hal->setAmplitude(amplitude); });
-        checkHalResult(ret, state);
+        if (shouldSkipWithError<void>(setAmplitudeFn, "setAmplitude", state)) {
+            return;
+        }
     }
 });
 
 BENCHMARK_WRAPPER(VibratorBench, setExternalControl, {
-    if (!hasCapabilities(vibrator::Capabilities::EXTERNAL_CONTROL, state)) {
-        state.SkipWithMessage("missing capability");
+    if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::EXTERNAL_CONTROL, state)) {
         return;
     }
 
+    auto enableExternalControlFn = [](auto hal) { return hal->setExternalControl(true); };
+
     for (auto _ : state) {
-        state.PauseTiming();
-        vibrator::HalController controller;
-        controller.init();
-        state.ResumeTiming();
-        auto ret =
-                halCall<void>(controller, [](auto hal) { return hal->setExternalControl(true); });
-        state.PauseTiming();
-        if (checkHalResult(ret, state)) {
-            auto result = halCall<void>(controller,
-                                        [](auto hal) { return hal->setExternalControl(false); });
-            checkHalResult(result, state);
+        // Test
+        if (shouldSkipWithError<void>(enableExternalControlFn, "setExternalControl true", state)) {
+            return;
         }
+
+        // Cleanup
+        state.PauseTiming();
+        if (shouldSkipWithError(disableExternalControl(), state)) {
+            return;
+        }
+        state.ResumeTiming();
     }
 });
 
-BENCHMARK_WRAPPER(VibratorBench, setExternalControlCached, {
-    if (!hasCapabilities(vibrator::Capabilities::EXTERNAL_CONTROL, state)) {
-        state.SkipWithMessage("missing capability");
-        return;
-    }
-
-    for (auto _ : state) {
-        state.ResumeTiming();
-        auto result =
-                halCall<void>(mController, [](auto hal) { return hal->setExternalControl(true); });
-        state.PauseTiming();
-        if (checkHalResult(result, state)) {
-            auto ret = halCall<void>(mController,
-                                     [](auto hal) { return hal->setExternalControl(false); });
-            checkHalResult(ret, state);
-        }
-    }
-});
-
-BENCHMARK_WRAPPER(VibratorBench, setExternalAmplitudeCached, {
-    if (!hasCapabilities(vibrator::Capabilities::EXTERNAL_AMPLITUDE_CONTROL, state)) {
-        state.SkipWithMessage("missing capability");
+BENCHMARK_WRAPPER(VibratorBench, setExternalAmplitude, {
+    auto externalAmplitudeControl = vibrator::Capabilities::EXTERNAL_CONTROL &
+            vibrator::Capabilities::EXTERNAL_AMPLITUDE_CONTROL;
+    if (shouldSkipWithMissingCapabilityMessage(externalAmplitudeControl, state)) {
         return;
     }
 
     auto amplitude = 1.0f;
+    auto setAmplitudeFn = [&](auto hal) { return hal->setAmplitude(amplitude); };
+    auto enableExternalControlFn = [](auto hal) { return hal->setExternalControl(true); };
 
-    auto onResult =
-            halCall<void>(mController, [](auto hal) { return hal->setExternalControl(true); });
-    checkHalResult(onResult, state);
-
-    for (auto _ : state) {
-        auto ret =
-                halCall<void>(mController, [&](auto hal) { return hal->setAmplitude(amplitude); });
-        checkHalResult(ret, state);
+    if (shouldSkipWithError<void>(enableExternalControlFn, "setExternalControl true", state)) {
+        return;
     }
 
-    auto offResult =
-            halCall<void>(mController, [](auto hal) { return hal->setExternalControl(false); });
-    checkHalResult(offResult, state);
+    for (auto _ : state) {
+        if (shouldSkipWithError<void>(setAmplitudeFn, "setExternalAmplitude", state)) {
+            return;
+        }
+    }
 });
 
 BENCHMARK_WRAPPER(VibratorBench, getInfo, {
     for (auto _ : state) {
+        // Setup
         state.PauseTiming();
         vibrator::HalController controller;
         controller.init();
         state.ResumeTiming();
-        auto result = controller.getInfo();
-        checkHalResult(result.capabilities, state);
-        checkHalResult(result.supportedEffects, state);
-        checkHalResult(result.supportedPrimitives, state);
-        checkHalResult(result.primitiveDurations, state);
-        checkHalResult(result.resonantFrequency, state);
-        checkHalResult(result.qFactor, state);
+
+        controller.getInfo();
     }
 });
 
@@ -285,13 +367,7 @@
     mController.getInfo();
 
     for (auto _ : state) {
-        auto result = mController.getInfo();
-        checkHalResult(result.capabilities, state);
-        checkHalResult(result.supportedEffects, state);
-        checkHalResult(result.supportedPrimitives, state);
-        checkHalResult(result.primitiveDurations, state);
-        checkHalResult(result.resonantFrequency, state);
-        checkHalResult(result.qFactor, state);
+        mController.getInfo();
     }
 });
 
@@ -334,9 +410,16 @@
     }
 };
 
+class SlowVibratorEffectsBench : public VibratorEffectsBench {
+public:
+    static void DefaultConfig(Benchmark* b) {
+        VibratorBench::DefaultConfig(b);
+        SlowBenchConfig(b);
+    }
+};
+
 BENCHMARK_WRAPPER(VibratorEffectsBench, alwaysOnEnable, {
-    if (!hasCapabilities(vibrator::Capabilities::ALWAYS_ON_CONTROL, state)) {
-        state.SkipWithMessage("missing capability");
+    if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::ALWAYS_ON_CONTROL, state)) {
         return;
     }
     if (!hasArgs(state)) {
@@ -347,24 +430,26 @@
     int32_t id = 1;
     auto effect = getEffect(state);
     auto strength = getStrength(state);
+    auto enableFn = [&](auto hal) { return hal->alwaysOnEnable(id, effect, strength); };
+    auto disableFn = [&](auto hal) { return hal->alwaysOnDisable(id); };
 
     for (auto _ : state) {
-        state.ResumeTiming();
-        auto ret = halCall<void>(mController, [&](auto hal) {
-            return hal->alwaysOnEnable(id, effect, strength);
-        });
-        state.PauseTiming();
-        if (checkHalResult(ret, state)) {
-            auto disableResult =
-                    halCall<void>(mController, [&](auto hal) { return hal->alwaysOnDisable(id); });
-            checkHalResult(disableResult, state);
+        // Test
+        if (shouldSkipWithError<void>(enableFn, "alwaysOnEnable", state)) {
+            return;
         }
+
+        // Cleanup
+        state.PauseTiming();
+        if (shouldSkipWithError<void>(disableFn, "alwaysOnDisable", state)) {
+            return;
+        }
+        state.ResumeTiming();
     }
 });
 
 BENCHMARK_WRAPPER(VibratorEffectsBench, alwaysOnDisable, {
-    if (!hasCapabilities(vibrator::Capabilities::ALWAYS_ON_CONTROL, state)) {
-        state.SkipWithMessage("missing capability");
+    if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::ALWAYS_ON_CONTROL, state)) {
         return;
     }
     if (!hasArgs(state)) {
@@ -375,23 +460,25 @@
     int32_t id = 1;
     auto effect = getEffect(state);
     auto strength = getStrength(state);
+    auto enableFn = [&](auto hal) { return hal->alwaysOnEnable(id, effect, strength); };
+    auto disableFn = [&](auto hal) { return hal->alwaysOnDisable(id); };
 
     for (auto _ : state) {
+        // Setup
         state.PauseTiming();
-        auto enableResult = halCall<void>(mController, [&](auto hal) {
-            return hal->alwaysOnEnable(id, effect, strength);
-        });
-        if (!checkHalResult(enableResult, state)) {
-            continue;
+        if (shouldSkipWithError<void>(enableFn, "alwaysOnEnable", state)) {
+            return;
         }
         state.ResumeTiming();
-        auto disableResult =
-                halCall<void>(mController, [&](auto hal) { return hal->alwaysOnDisable(id); });
-        checkHalResult(disableResult, state);
+
+        // Test
+        if (shouldSkipWithError<void>(disableFn, "alwaysOnDisable", state)) {
+            return;
+        }
     }
 });
 
-BENCHMARK_WRAPPER(VibratorEffectsBench, performEffect, {
+BENCHMARK_WRAPPER(SlowVibratorEffectsBench, performEffect, {
     if (!hasArgs(state)) {
         state.SkipWithMessage("missing args");
         return;
@@ -399,22 +486,38 @@
 
     auto effect = getEffect(state);
     auto strength = getStrength(state);
-    auto callback = []() {};
 
     for (auto _ : state) {
-        state.ResumeTiming();
-        auto ret = halCall<std::chrono::milliseconds>(mController, [&](auto hal) {
-            return hal->performEffect(effect, strength, callback);
-        });
+        // Setup
         state.PauseTiming();
-        if (checkHalResult(ret, state)) {
-            turnVibratorOff(state);
+        auto cb = mCallbacks.next();
+        auto performFn = [&](auto hal) {
+            return hal->performEffect(effect, strength, cb.completeFn());
+        };
+        state.ResumeTiming();
+
+        // Test
+        if (shouldSkipWithError<milliseconds>(performFn, "performEffect", state)) {
+            return;
         }
+
+        // Cleanup
+        state.PauseTiming();
+        if (shouldSkipWithError(turnVibratorOff(), state)) {
+            return;
+        }
+        cb.waitForComplete();
+        state.ResumeTiming();
     }
 });
 
-class VibratorPrimitivesBench : public VibratorBench {
+class SlowVibratorPrimitivesBench : public VibratorBench {
 public:
+    static void DefaultConfig(Benchmark* b) {
+        VibratorBench::DefaultConfig(b);
+        SlowBenchConfig(b);
+    }
+
     static void DefaultArgs(Benchmark* b) {
         vibrator::HalController controller;
         auto primitivesResult = controller.getInfo().supportedPrimitives;
@@ -449,9 +552,8 @@
     }
 };
 
-BENCHMARK_WRAPPER(VibratorPrimitivesBench, performComposedEffect, {
-    if (!hasCapabilities(vibrator::Capabilities::COMPOSE_EFFECTS, state)) {
-        state.SkipWithMessage("missing capability");
+BENCHMARK_WRAPPER(SlowVibratorPrimitivesBench, performComposedEffect, {
+    if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::COMPOSE_EFFECTS, state)) {
         return;
     }
     if (!hasArgs(state)) {
@@ -464,19 +566,29 @@
     effect.scale = 1.0f;
     effect.delayMs = static_cast<int32_t>(0);
 
-    std::vector<CompositeEffect> effects;
-    effects.push_back(effect);
-    auto callback = []() {};
+    std::vector<CompositeEffect> effects = {effect};
 
     for (auto _ : state) {
-        state.ResumeTiming();
-        auto ret = halCall<std::chrono::milliseconds>(mController, [&](auto hal) {
-            return hal->performComposedEffect(effects, callback);
-        });
+        // Setup
         state.PauseTiming();
-        if (checkHalResult(ret, state)) {
-            turnVibratorOff(state);
+        auto cb = mCallbacks.next();
+        auto performFn = [&](auto hal) {
+            return hal->performComposedEffect(effects, cb.completeFn());
+        };
+        state.ResumeTiming();
+
+        // Test
+        if (shouldSkipWithError<milliseconds>(performFn, "performComposedEffect", state)) {
+            return;
         }
+
+        // Cleanup
+        state.PauseTiming();
+        if (shouldSkipWithError(turnVibratorOff(), state)) {
+            return;
+        }
+        cb.waitForComplete();
+        state.ResumeTiming();
     }
 });
 
diff --git a/services/vibratorservice/test/VibratorCallbackSchedulerTest.cpp b/services/vibratorservice/test/VibratorCallbackSchedulerTest.cpp
index 426cd42..881e321 100644
--- a/services/vibratorservice/test/VibratorCallbackSchedulerTest.cpp
+++ b/services/vibratorservice/test/VibratorCallbackSchedulerTest.cpp
@@ -14,20 +14,13 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "VibratorHalWrapperAidlTest"
-
-#include <android-base/thread_annotations.h>
-#include <android/hardware/vibrator/IVibrator.h>
-#include <condition_variable>
-
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
-#include <utils/Log.h>
-#include <thread>
-
 #include <vibratorservice/VibratorCallbackScheduler.h>
 
+#include "test_utils.h"
+
 using std::chrono::milliseconds;
 using std::chrono::steady_clock;
 using std::chrono::time_point;
@@ -39,29 +32,25 @@
 // -------------------------------------------------------------------------------------------------
 
 // Delay allowed for the scheduler to process callbacks during this test.
-static const auto TEST_TIMEOUT = 50ms;
+static const auto TEST_TIMEOUT = 100ms;
 
 class VibratorCallbackSchedulerTest : public Test {
 public:
-    void SetUp() override {
-        mScheduler = std::make_unique<vibrator::CallbackScheduler>();
-        std::lock_guard<std::mutex> lock(mMutex);
-        mExpiredCallbacks.clear();
-    }
+    void SetUp() override { mScheduler = std::make_unique<vibrator::CallbackScheduler>(); }
 
 protected:
     std::mutex mMutex;
-    std::condition_variable_any mCondition;
     std::unique_ptr<vibrator::CallbackScheduler> mScheduler = nullptr;
+    vibrator::TestCounter mCallbackCounter;
     std::vector<int32_t> mExpiredCallbacks GUARDED_BY(mMutex);
 
     std::function<void()> createCallback(int32_t id) {
-        return [=]() {
+        return [this, id]() {
             {
                 std::lock_guard<std::mutex> lock(mMutex);
                 mExpiredCallbacks.push_back(id);
             }
-            mCondition.notify_all();
+            mCallbackCounter.increment();
         };
     }
 
@@ -71,56 +60,42 @@
     }
 
     int32_t waitForCallbacks(int32_t callbackCount, milliseconds timeout) {
-        time_point<steady_clock> expirationTime = steady_clock::now() + timeout + TEST_TIMEOUT;
-        int32_t expiredCallbackCount = 0;
-        while (steady_clock::now() < expirationTime) {
-            std::lock_guard<std::mutex> lock(mMutex);
-            expiredCallbackCount = mExpiredCallbacks.size();
-            if (callbackCount <= expiredCallbackCount) {
-                return expiredCallbackCount;
-            }
-            auto currentTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(
-                    expirationTime - steady_clock::now());
-            if (currentTimeout > currentTimeout.zero()) {
-                // Use the monotonic steady clock to wait for the requested timeout via wait_for
-                // instead of using a wall clock via wait_until.
-                mCondition.wait_for(mMutex, currentTimeout);
-            }
-        }
-        return expiredCallbackCount;
+        mCallbackCounter.tryWaitUntilCountIsAtLeast(callbackCount, timeout);
+        return mCallbackCounter.get();
     }
 };
 
 // -------------------------------------------------------------------------------------------------
 
 TEST_F(VibratorCallbackSchedulerTest, TestScheduleRunsOnlyAfterDelay) {
+    auto callbackDuration = 50ms;
     time_point<steady_clock> startTime = steady_clock::now();
-    mScheduler->schedule(createCallback(1), 50ms);
+    mScheduler->schedule(createCallback(1), callbackDuration);
 
-    ASSERT_EQ(1, waitForCallbacks(1, 50ms));
+    ASSERT_THAT(waitForCallbacks(1, callbackDuration + TEST_TIMEOUT), Eq(1));
     time_point<steady_clock> callbackTime = steady_clock::now();
 
-    // Callback happened at least 50ms after the beginning of the test.
-    ASSERT_TRUE(startTime + 50ms <= callbackTime);
-    ASSERT_THAT(getExpiredCallbacks(), ElementsAre(1));
+    // Callback took at least the required duration to trigger.
+    ASSERT_THAT(callbackTime, Ge(startTime + callbackDuration));
 }
 
 TEST_F(VibratorCallbackSchedulerTest, TestScheduleMultipleCallbacksRunsInDelayOrder) {
     // Schedule first callbacks long enough that all 3 will be scheduled together and run in order.
-    mScheduler->schedule(createCallback(1), 50ms);
-    mScheduler->schedule(createCallback(2), 40ms);
-    mScheduler->schedule(createCallback(3), 10ms);
+    mScheduler->schedule(createCallback(1), 50ms + 2 * TEST_TIMEOUT);
+    mScheduler->schedule(createCallback(2), 50ms + TEST_TIMEOUT);
+    mScheduler->schedule(createCallback(3), 50ms);
 
-    ASSERT_EQ(3, waitForCallbacks(3, 50ms));
+    // Callbacks triggered in the expected order based on the requested durations.
+    ASSERT_THAT(waitForCallbacks(3, 50ms + 3 * TEST_TIMEOUT), Eq(3));
     ASSERT_THAT(getExpiredCallbacks(), ElementsAre(3, 2, 1));
 }
 
 TEST_F(VibratorCallbackSchedulerTest, TestDestructorDropsPendingCallbacksAndKillsThread) {
     // Schedule callback long enough that scheduler will be destroyed while it's still scheduled.
-    mScheduler->schedule(createCallback(1), 50ms);
+    mScheduler->schedule(createCallback(1), 100ms);
     mScheduler.reset(nullptr);
 
     // Should timeout waiting for callback to run.
-    ASSERT_EQ(0, waitForCallbacks(1, 50ms));
-    ASSERT_TRUE(getExpiredCallbacks().empty());
+    ASSERT_THAT(waitForCallbacks(1, 100ms + TEST_TIMEOUT), Eq(0));
+    ASSERT_THAT(getExpiredCallbacks(), IsEmpty());
 }
diff --git a/services/vibratorservice/test/VibratorHalControllerTest.cpp b/services/vibratorservice/test/VibratorHalControllerTest.cpp
index 9b95d74..15fde91 100644
--- a/services/vibratorservice/test/VibratorHalControllerTest.cpp
+++ b/services/vibratorservice/test/VibratorHalControllerTest.cpp
@@ -255,16 +255,17 @@
                 .WillRepeatedly(Return(vibrator::HalResult<void>::transactionFailed("message")));
     }
 
-    std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
-    auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+    auto counter = vibrator::TestCounter(0);
 
-    auto onFn = [&](vibrator::HalWrapper* hal) { return hal->on(10ms, callback); };
+    auto onFn = [&](vibrator::HalWrapper* hal) {
+        return hal->on(10ms, [&counter] { counter.increment(); });
+    };
     ASSERT_TRUE(mController->doWithRetry<void>(onFn, "on").isOk());
     ASSERT_TRUE(mController->doWithRetry<void>(PING_FN, "ping").isFailed());
     mMockHal.reset();
-    ASSERT_EQ(0, *callbackCounter.get());
+    ASSERT_EQ(0, counter.get());
 
     // Callback triggered even after HalWrapper was reconnected.
-    std::this_thread::sleep_for(15ms);
-    ASSERT_EQ(1, *callbackCounter.get());
+    counter.tryWaitUntilCountIsAtLeast(1, 500ms);
+    ASSERT_EQ(1, counter.get());
 }
diff --git a/services/vibratorservice/test/test_utils.h b/services/vibratorservice/test/test_utils.h
index 1933a11..715c221 100644
--- a/services/vibratorservice/test/test_utils.h
+++ b/services/vibratorservice/test/test_utils.h
@@ -85,10 +85,38 @@
     ~TestFactory() = delete;
 };
 
+class TestCounter {
+public:
+    TestCounter(int32_t init = 0) : mMutex(), mCondVar(), mCount(init) {}
+
+    int32_t get() {
+        std::lock_guard<std::mutex> lock(mMutex);
+        return mCount;
+    }
+
+    void increment() {
+        {
+            std::lock_guard<std::mutex> lock(mMutex);
+            mCount += 1;
+        }
+        mCondVar.notify_all();
+    }
+
+    void tryWaitUntilCountIsAtLeast(int32_t count, std::chrono::milliseconds timeout) {
+        std::unique_lock<std::mutex> lock(mMutex);
+        mCondVar.wait_for(lock, timeout, [&] { return mCount >= count; });
+    }
+
+private:
+    std::mutex mMutex;
+    std::condition_variable mCondVar;
+    int32_t mCount GUARDED_BY(mMutex);
+};
+
 // -------------------------------------------------------------------------------------------------
 
 } // namespace vibrator
 
 } // namespace android
 
-#endif // VIBRATORSERVICE_UNITTEST_UTIL_H_
\ No newline at end of file
+#endif // VIBRATORSERVICE_UNITTEST_UTIL_H_