Merge "PointerChoreographer: Support drawing tablets that report mouse events" into main
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/input/motion_predictor_config.xml b/data/etc/input/motion_predictor_config.xml
index 39772ae..a20993f 100644
--- a/data/etc/input/motion_predictor_config.xml
+++ b/data/etc/input/motion_predictor_config.xml
@@ -31,5 +31,13 @@
        the UX issue mentioned above.
   -->
   <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).
+  -->
+  <low-jerk>0.1</low-jerk>
+  <high-jerk>0.2</high-jerk>
 </motion-predictor>
 
diff --git a/include/input/TfLiteMotionPredictor.h b/include/input/TfLiteMotionPredictor.h
index 2edc138..728a8e1 100644
--- a/include/input/TfLiteMotionPredictor.h
+++ b/include/input/TfLiteMotionPredictor.h
@@ -105,6 +105,11 @@
         // The noise floor for predictions.
         // Distances (r) less than this should be discarded as noise.
         float distanceNoiseFloor = 0;
+
+        // Low and high jerk thresholds (with normalized dt = 1) for predictions.
+        // High jerk means more predictions will be pruned, vice versa for low.
+        float lowJerk = 0;
+        float highJerk = 0;
     };
 
     // Creates a model from an encoded Flatbuffer model.
diff --git a/libs/binder/tests/binderRpcTestCommon.h b/libs/binder/tests/binderRpcTestCommon.h
index 62fe9e5..8832f1a 100644
--- a/libs/binder/tests/binderRpcTestCommon.h
+++ b/libs/binder/tests/binderRpcTestCommon.h
@@ -74,6 +74,12 @@
 }
 
 static inline bool hasExperimentalRpc() {
+#ifdef BINDER_RPC_TO_TRUSTY_TEST
+    // Trusty services do not support the experimental version,
+    // so that we can update the prebuilts separately.
+    // This covers the binderRpcToTrustyTest case on Android.
+    return false;
+#endif
 #ifdef __ANDROID__
     return base::GetProperty("ro.build.version.codename", "") != "REL";
 #else
diff --git a/libs/binder/tests/binderRpcUniversalTests.cpp b/libs/binder/tests/binderRpcUniversalTests.cpp
index 885bb45..f480780 100644
--- a/libs/binder/tests/binderRpcUniversalTests.cpp
+++ b/libs/binder/tests/binderRpcUniversalTests.cpp
@@ -48,11 +48,13 @@
     EXPECT_FALSE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_NEXT + 15));
 }
 
+#ifndef BINDER_RPC_TO_TRUSTY_TEST
 TEST(BinderRpc, CanUseExperimentalWireVersion) {
     auto session = RpcSession::make();
     EXPECT_EQ(hasExperimentalRpc(),
               session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL));
 }
+#endif
 
 TEST_P(BinderRpc, Ping) {
     auto proc = createRpcTestSocketServerProcess({});
diff --git a/libs/debugstore/OWNERS b/libs/debugstore/OWNERS
new file mode 100644
index 0000000..428a1a2
--- /dev/null
+++ b/libs/debugstore/OWNERS
@@ -0,0 +1,3 @@
+benmiles@google.com
+gaillard@google.com
+mohamadmahmoud@google.com
diff --git a/libs/debugstore/rust/Android.bp b/libs/debugstore/rust/Android.bp
new file mode 100644
index 0000000..55ba3c3
--- /dev/null
+++ b/libs/debugstore/rust/Android.bp
@@ -0,0 +1,71 @@
+// 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 {
+    default_team: "trendy_team_android_telemetry_infra",
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+rust_defaults {
+    name: "libdebugstore_defaults",
+    srcs: ["src/lib.rs"],
+    rustlibs: [
+        "libcrossbeam_queue",
+        "libparking_lot",
+        "libonce_cell",
+        "libcxx",
+    ],
+    shared_libs: ["libutils"],
+    edition: "2021",
+}
+
+rust_ffi_static {
+    name: "libdebugstore_rust_ffi",
+    crate_name: "debugstore",
+    defaults: ["libdebugstore_defaults"],
+}
+
+cc_library {
+    name: "libdebugstore_cxx",
+    generated_headers: ["libdebugstore_cxx_bridge_header"],
+    generated_sources: ["libdebugstore_cxx_bridge_code"],
+    export_generated_headers: ["libdebugstore_cxx_bridge_header"],
+    shared_libs: ["libutils"],
+    whole_static_libs: ["libdebugstore_rust_ffi"],
+}
+
+rust_test {
+    name: "libdebugstore_tests",
+    defaults: ["libdebugstore_defaults"],
+    test_options: {
+        unit_test: true,
+    },
+    shared_libs: ["libdebugstore_cxx"],
+}
+
+genrule {
+    name: "libdebugstore_cxx_bridge_header",
+    tools: ["cxxbridge"],
+    cmd: "$(location cxxbridge) $(in) --header >> $(out)",
+    srcs: ["src/lib.rs"],
+    out: ["debugstore/debugstore_cxx_bridge.rs.h"],
+}
+
+genrule {
+    name: "libdebugstore_cxx_bridge_code",
+    tools: ["cxxbridge"],
+    cmd: "$(location cxxbridge) $(in) >> $(out)",
+    srcs: ["src/lib.rs"],
+    out: ["debugstore/debugstore_cxx_bridge.rs.cpp"],
+}
diff --git a/libs/debugstore/rust/Cargo.toml b/libs/debugstore/rust/Cargo.toml
new file mode 100644
index 0000000..23a8d24
--- /dev/null
+++ b/libs/debugstore/rust/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "debugstore"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+[dependencies]
\ No newline at end of file
diff --git a/libs/debugstore/rust/src/core.rs b/libs/debugstore/rust/src/core.rs
new file mode 100644
index 0000000..1dfa512
--- /dev/null
+++ b/libs/debugstore/rust/src/core.rs
@@ -0,0 +1,198 @@
+/*
+ * 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 super::event::Event;
+use super::event_type::EventType;
+use super::storage::Storage;
+use crate::cxxffi::uptimeMillis;
+use once_cell::sync::Lazy;
+use std::fmt;
+use std::sync::atomic::{AtomicU64, Ordering};
+
+//  Lazily initialized static instance of DebugStore.
+static INSTANCE: Lazy<DebugStore> = Lazy::new(DebugStore::new);
+
+/// The `DebugStore` struct is responsible for managing debug events and data.
+pub struct DebugStore {
+    /// Atomic counter for generating unique event IDs.
+    id_generator: AtomicU64,
+    /// Non-blocking storage for debug events.
+    event_store: Storage<Event, { DebugStore::DEFAULT_EVENT_LIMIT }>,
+}
+
+impl DebugStore {
+    /// The default limit for the number of events that can be stored.
+    ///
+    /// This limit is used to initialize the storage for debug events.
+    const DEFAULT_EVENT_LIMIT: usize = 16;
+    /// A designated identifier used for events that cannot be closed.
+    ///
+    /// This ID is used for point/instantaneous events, or events do not have
+    ///  a distinct end.
+    const NON_CLOSABLE_ID: u64 = 0;
+    /// The version number for the encoding of debug store data.
+    ///
+    /// This constant is used as a part of the debug store's data format,
+    /// allowing for version tracking and compatibility checks.
+    const ENCODE_VERSION: u32 = 1;
+
+    /// Creates a new instance of `DebugStore` with specified event limit and maximum delay.
+    fn new() -> Self {
+        Self { id_generator: AtomicU64::new(1), event_store: Storage::new() }
+    }
+
+    /// Returns a shared instance of `DebugStore`.
+    ///
+    /// This method provides a singleton pattern access to `DebugStore`.
+    pub fn get_instance() -> &'static DebugStore {
+        &INSTANCE
+    }
+
+    /// Begins a new debug event with the given name and data.
+    ///
+    /// This method logs the start of a debug event, assigning it a unique ID and marking its start time.
+    /// - `name`: The name of the debug event.
+    /// - `data`: Associated data as key-value pairs.
+    /// - Returns: A unique ID for the debug event.
+    pub fn begin(&self, name: String, data: Vec<(String, String)>) -> u64 {
+        let id = self.generate_id();
+        self.event_store.insert(Event::new(
+            id,
+            Some(name),
+            uptimeMillis(),
+            EventType::DurationStart,
+            data,
+        ));
+        id
+    }
+
+    /// Records a debug event without a specific duration, with the given name and data.
+    ///
+    /// This method logs an instantaneous debug event, useful for events that don't have a duration but are significant.
+    /// - `name`: The name of the debug event.
+    /// - `data`: Associated data as key-value pairs.
+    pub fn record(&self, name: String, data: Vec<(String, String)>) {
+        self.event_store.insert(Event::new(
+            Self::NON_CLOSABLE_ID,
+            Some(name),
+            uptimeMillis(),
+            EventType::Point,
+            data,
+        ));
+    }
+
+    /// Ends a debug event that was previously started with the given ID.
+    ///
+    /// This method marks the end of a debug event, completing its lifecycle.
+    /// - `id`: The unique ID of the debug event to end.
+    /// - `data`: Additional data to log at the end of the event.
+    pub fn end(&self, id: u64, data: Vec<(String, String)>) {
+        if id != Self::NON_CLOSABLE_ID {
+            self.event_store.insert(Event::new(
+                id,
+                None,
+                uptimeMillis(),
+                EventType::DurationEnd,
+                data,
+            ));
+        }
+    }
+
+    fn generate_id(&self) -> u64 {
+        let mut id = self.id_generator.fetch_add(1, Ordering::Relaxed);
+        while id == Self::NON_CLOSABLE_ID {
+            id = self.id_generator.fetch_add(1, Ordering::Relaxed);
+        }
+        id
+    }
+}
+
+impl fmt::Display for DebugStore {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let uptime_now = uptimeMillis();
+        write!(f, "{},{},{}::", Self::ENCODE_VERSION, self.event_store.len(), uptime_now)?;
+
+        write!(
+            f,
+            "{}",
+            self.event_store.fold(String::new(), |mut acc, event| {
+                if !acc.is_empty() {
+                    acc.push_str("||");
+                }
+                acc.push_str(&event.to_string());
+                acc
+            })
+        )
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_begin_event() {
+        let debug_store = DebugStore::new();
+        let _event_id = debug_store.begin("test_event".to_string(), vec![]);
+        let output = debug_store.to_string();
+        assert!(
+            output.contains("test_event"),
+            "The output should contain the event name 'test_event'"
+        );
+    }
+
+    #[test]
+    fn test_unique_event_ids() {
+        let debug_store = DebugStore::new();
+        let event_id1 = debug_store.begin("event1".to_string(), vec![]);
+        let event_id2 = debug_store.begin("event2".to_string(), vec![]);
+        assert_ne!(event_id1, event_id2, "Event IDs should be unique");
+    }
+
+    #[test]
+    fn test_end_event() {
+        let debug_store = DebugStore::new();
+        let event_id = debug_store.begin("test_event".to_string(), vec![]);
+        debug_store.end(event_id, vec![]);
+        let output = debug_store.to_string();
+
+        let id_pattern = format!("ID:{},", event_id);
+        assert!(
+            output.contains("test_event"),
+            "The output should contain the event name 'test_event'"
+        );
+        assert_eq!(
+            output.matches(&id_pattern).count(),
+            2,
+            "The output should contain two events (start and end) associated with the given ID"
+        );
+    }
+
+    #[test]
+    fn test_event_data_handling() {
+        let debug_store = DebugStore::new();
+        debug_store
+            .record("data_event".to_string(), vec![("key".to_string(), "value".to_string())]);
+        let output = debug_store.to_string();
+        assert!(
+            output.contains("data_event"),
+            "The output should contain the event name 'data_event'"
+        );
+        assert!(
+            output.contains("key=value"),
+            "The output should contain the event data 'key=value'"
+        );
+    }
+}
diff --git a/libs/debugstore/rust/src/event.rs b/libs/debugstore/rust/src/event.rs
new file mode 100644
index 0000000..0c16665
--- /dev/null
+++ b/libs/debugstore/rust/src/event.rs
@@ -0,0 +1,72 @@
+/*
+ * 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 super::event_type::EventType;
+use std::fmt;
+
+/// Represents a single debug event within the Debug Store system.
+///
+/// It contains all the necessary information for a debug event.
+#[derive(Clone)]
+pub struct Event {
+    /// The unique identifier for this event.
+    pub id: u64,
+    /// The optional name of the event.
+    pub name: Option<String>,
+    /// The system uptime when the event occurred.
+    pub timestamp: i64,
+    /// The type of the event.
+    pub event_type: EventType,
+    /// Additional data associated with the event, stored in the given order as key-value pairs.
+    data: Vec<(String, String)>,
+}
+
+impl Event {
+    /// Constructs a new `Event`.
+    ///
+    /// - `id`: The unique identifier for the event.
+    /// - `name`: An optional name for the event.
+    /// - `timestamp`: The system uptime when the event occurred.
+    /// - `event_type`: The type of the event.
+    /// - `data`: Additional data for the event, represented as ordered key-value pairs.
+    pub fn new(
+        id: u64,
+        name: Option<String>,
+        timestamp: i64,
+        event_type: EventType,
+        data: Vec<(String, String)>,
+    ) -> Self {
+        Self { id, name, timestamp, event_type, data }
+    }
+}
+
+impl fmt::Display for Event {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "ID:{},C:{},T:{}", self.id, self.event_type, self.timestamp)?;
+
+        if let Some(ref name) = self.name {
+            write!(f, ",N:{}", name)?;
+        }
+
+        if !self.data.is_empty() {
+            let data_str =
+                self.data.iter().map(|(k, v)| format!("{}={}", k, v)).collect::<Vec<_>>().join(";");
+            write!(f, ",D:{}", data_str)?;
+        }
+
+        Ok(())
+    }
+}
diff --git a/libs/debugstore/rust/src/event_type.rs b/libs/debugstore/rust/src/event_type.rs
new file mode 100644
index 0000000..6f4bafb
--- /dev/null
+++ b/libs/debugstore/rust/src/event_type.rs
@@ -0,0 +1,43 @@
+/*
+ * 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 std::fmt;
+
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub enum EventType {
+    /// Marks the an unknown or invalid event, for convenient mapping to a protobuf enum.
+    Invalid,
+    /// Marks the beginning of a duration-based event, indicating the start of a timed operation.
+    DurationStart,
+    /// Marks the end of a duration-based event, indicating the end of a timed operation.
+    DurationEnd,
+    /// Represents a single, instantaneous event with no duration.
+    Point,
+}
+
+impl fmt::Display for EventType {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(
+            f,
+            "{}",
+            match self {
+                EventType::Invalid => "I",
+                EventType::DurationStart => "S",
+                EventType::DurationEnd => "E",
+                EventType::Point => "P",
+            }
+        )
+    }
+}
diff --git a/libs/debugstore/rust/src/lib.rs b/libs/debugstore/rust/src/lib.rs
new file mode 100644
index 0000000..f2195c0
--- /dev/null
+++ b/libs/debugstore/rust/src/lib.rs
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+//! # Debug Store Crate
+/// The Debug Store Crate provides functionalities for storing debug events.
+/// It allows logging and retrieval of debug events, and associated data.
+mod core;
+mod event;
+mod event_type;
+mod storage;
+
+pub use core::*;
+pub use event::*;
+
+use cxx::{CxxString, CxxVector};
+
+#[cxx::bridge(namespace = "android::debugstore")]
+#[allow(unsafe_op_in_unsafe_fn)]
+mod cxxffi {
+    extern "Rust" {
+        fn debug_store_to_string() -> String;
+        fn debug_store_record(name: &CxxString, data: &CxxVector<CxxString>);
+        fn debug_store_begin(name: &CxxString, data: &CxxVector<CxxString>) -> u64;
+        fn debug_store_end(id: u64, data: &CxxVector<CxxString>);
+    }
+
+    #[namespace = "android"]
+    unsafe extern "C++" {
+        include!("utils/SystemClock.h");
+        fn uptimeMillis() -> i64;
+    }
+}
+
+fn debug_store_to_string() -> String {
+    DebugStore::get_instance().to_string()
+}
+
+fn debug_store_record(name: &CxxString, data: &CxxVector<CxxString>) {
+    DebugStore::get_instance().record(name.to_string_lossy().into_owned(), cxx_vec_to_pairs(data));
+}
+
+fn debug_store_begin(name: &CxxString, data: &CxxVector<CxxString>) -> u64 {
+    DebugStore::get_instance().begin(name.to_string_lossy().into_owned(), cxx_vec_to_pairs(data))
+}
+
+fn debug_store_end(id: u64, data: &CxxVector<CxxString>) {
+    DebugStore::get_instance().end(id, cxx_vec_to_pairs(data));
+}
+
+fn cxx_vec_to_pairs(vec: &CxxVector<CxxString>) -> Vec<(String, String)> {
+    vec.iter()
+        .map(|s| s.to_string_lossy().into_owned())
+        .collect::<Vec<_>>()
+        .chunks(2)
+        .filter_map(|chunk| match chunk {
+            [k, v] => Some((k.clone(), v.clone())),
+            _ => None,
+        })
+        .collect()
+}
diff --git a/libs/debugstore/rust/src/storage.rs b/libs/debugstore/rust/src/storage.rs
new file mode 100644
index 0000000..2ad7f4e
--- /dev/null
+++ b/libs/debugstore/rust/src/storage.rs
@@ -0,0 +1,134 @@
+/*
+ * 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 crossbeam_queue::ArrayQueue;
+
+/// A thread-safe storage that allows non-blocking attempts to store and visit elements.
+pub struct Storage<T, const N: usize> {
+    insertion_buffer: ArrayQueue<T>,
+}
+
+impl<T, const N: usize> Storage<T, N> {
+    /// Creates a new Storage with the specified size.
+    pub fn new() -> Self {
+        Self { insertion_buffer: ArrayQueue::new(N) }
+    }
+
+    /// Inserts a value into the storage, returning an error if the lock cannot be acquired.
+    pub fn insert(&self, value: T) {
+        self.insertion_buffer.force_push(value);
+    }
+
+    /// Folds over the elements in the storage using the provided function.
+    pub fn fold<U, F>(&self, init: U, mut func: F) -> U
+    where
+        F: FnMut(U, &T) -> U,
+    {
+        let mut acc = init;
+        while let Some(value) = self.insertion_buffer.pop() {
+            acc = func(acc, &value);
+        }
+        acc
+    }
+
+    /// Returns the number of elements that have been inserted into the storage.
+    pub fn len(&self) -> usize {
+        self.insertion_buffer.len()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_insert_and_retrieve() {
+        let storage = Storage::<i32, 10>::new();
+        storage.insert(7);
+
+        let sum = storage.fold(0, |acc, &x| acc + x);
+        assert_eq!(sum, 7, "The sum of the elements should be equal to the inserted value.");
+    }
+
+    #[test]
+    fn test_fold_functionality() {
+        let storage = Storage::<i32, 5>::new();
+        storage.insert(1);
+        storage.insert(2);
+        storage.insert(3);
+
+        let sum = storage.fold(0, |acc, &x| acc + x);
+        assert_eq!(
+            sum, 6,
+            "The sum of the elements should be equal to the sum of inserted values."
+        );
+    }
+
+    #[test]
+    fn test_insert_and_retrieve_multiple_values() {
+        let storage = Storage::<i32, 10>::new();
+        storage.insert(1);
+        storage.insert(2);
+        storage.insert(5);
+
+        let first_sum = storage.fold(0, |acc, &x| acc + x);
+        assert_eq!(first_sum, 8, "The sum of the elements should be equal to the inserted values.");
+
+        storage.insert(30);
+        storage.insert(22);
+
+        let second_sum = storage.fold(0, |acc, &x| acc + x);
+        assert_eq!(
+            second_sum, 52,
+            "The sum of the elements should be equal to the inserted values."
+        );
+    }
+
+    #[test]
+    fn test_storage_limit() {
+        let storage = Storage::<i32, 1>::new();
+        storage.insert(1);
+        // This value should overwrite the previously inserted value (1).
+        storage.insert(4);
+        let sum = storage.fold(0, |acc, &x| acc + x);
+        assert_eq!(sum, 4, "The sum of the elements should be equal to the inserted values.");
+    }
+
+    #[test]
+    fn test_concurrent_insertions() {
+        use std::sync::Arc;
+        use std::thread;
+
+        let storage = Arc::new(Storage::<i32, 100>::new());
+        let threads: Vec<_> = (0..10)
+            .map(|_| {
+                let storage_clone = Arc::clone(&storage);
+                thread::spawn(move || {
+                    for i in 0..10 {
+                        storage_clone.insert(i);
+                    }
+                })
+            })
+            .collect();
+
+        for thread in threads {
+            thread.join().expect("Thread should finish without panicking");
+        }
+
+        let count = storage.fold(0, |acc, _| acc + 1);
+        assert_eq!(count, 100, "Storage should be filled to its limit with concurrent insertions.");
+    }
+}
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index b70e80e..d1b2c5f 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 {
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/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..3d29529
--- /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(Option<()>);
+
+        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 0f16f71..2faa330 100644
--- a/libs/gui/tests/Android.bp
+++ b/libs/gui/tests/Android.bp
@@ -21,7 +21,8 @@
     cppflags: [
         "-Wall",
         "-Werror",
-        "-Wno-extra",
+        "-Wextra",
+        "-Wthread-safety",
         "-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_BQ_SETFRAMERATE=true",
     ],
 
diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp
index ea7078d..946ff05 100644
--- a/libs/gui/tests/BLASTBufferQueue_test.cpp
+++ b/libs/gui/tests/BLASTBufferQueue_test.cpp
@@ -18,6 +18,7 @@
 
 #include <gui/BLASTBufferQueue.h>
 
+#include <android-base/thread_annotations.h>
 #include <android/hardware/graphics/common/1.2/types.h>
 #include <gui/AidlStatusUtil.h>
 #include <gui/BufferQueueCore.h>
@@ -61,7 +62,8 @@
     }
 
     void waitOnNumberReleased(int32_t expectedNumReleased) {
-        std::unique_lock<std::mutex> lock(mMutex);
+        std::unique_lock lock{mMutex};
+        base::ScopedLockAssertion assumeLocked(mMutex);
         while (mNumReleased < expectedNumReleased) {
             ASSERT_NE(mReleaseCallback.wait_for(lock, std::chrono::seconds(3)),
                       std::cv_status::timeout)
@@ -134,11 +136,18 @@
 
     void clearSyncTransaction() { mBlastBufferQueueAdapter->clearSyncTransaction(); }
 
-    int getWidth() { return mBlastBufferQueueAdapter->mSize.width; }
+    int getWidth() {
+        std::scoped_lock lock(mBlastBufferQueueAdapter->mMutex);
+        return mBlastBufferQueueAdapter->mSize.width;
+    }
 
-    int getHeight() { return mBlastBufferQueueAdapter->mSize.height; }
+    int getHeight() {
+        std::scoped_lock lock(mBlastBufferQueueAdapter->mMutex);
+        return mBlastBufferQueueAdapter->mSize.height;
+    }
 
     std::function<void(Transaction*)> getTransactionReadyCallback() {
+        std::scoped_lock lock(mBlastBufferQueueAdapter->mMutex);
         return mBlastBufferQueueAdapter->mTransactionReadyCallback;
     }
 
@@ -147,6 +156,7 @@
     }
 
     const sp<SurfaceControl> getSurfaceControl() {
+        std::scoped_lock lock(mBlastBufferQueueAdapter->mMutex);
         return mBlastBufferQueueAdapter->mSurfaceControl;
     }
 
@@ -156,6 +166,7 @@
 
     void waitForCallbacks() {
         std::unique_lock lock{mBlastBufferQueueAdapter->mMutex};
+        base::ScopedLockAssertion assumeLocked(mBlastBufferQueueAdapter->mMutex);
         // Wait until all but one of the submitted buffers have been released.
         while (mBlastBufferQueueAdapter->mSubmitted.size() > 1) {
             mBlastBufferQueueAdapter->mCallbackCV.wait(lock);
@@ -166,8 +177,8 @@
         mBlastBufferQueueAdapter->waitForCallback(frameNumber);
     }
 
-    void validateNumFramesSubmitted(int64_t numFramesSubmitted) {
-        std::unique_lock lock{mBlastBufferQueueAdapter->mMutex};
+    void validateNumFramesSubmitted(size_t numFramesSubmitted) {
+        std::scoped_lock lock{mBlastBufferQueueAdapter->mMutex};
         ASSERT_EQ(numFramesSubmitted, mBlastBufferQueueAdapter->mSubmitted.size());
     }
 
@@ -201,7 +212,7 @@
         mDisplayWidth = resolution.getWidth();
         mDisplayHeight = resolution.getHeight();
         ALOGD("Display: %dx%d orientation:%d", mDisplayWidth, mDisplayHeight,
-              displayState.orientation);
+              static_cast<int32_t>(displayState.orientation));
 
         mRootSurfaceControl = mClient->createSurface(String8("RootTestSurface"), mDisplayWidth,
                                                      mDisplayHeight, PIXEL_FORMAT_RGBA_8888,
@@ -240,8 +251,8 @@
 
     void fillBuffer(uint32_t* bufData, Rect rect, uint32_t stride, uint8_t r, uint8_t g,
                     uint8_t b) {
-        for (uint32_t row = rect.top; row < rect.bottom; row++) {
-            for (uint32_t col = rect.left; col < rect.right; col++) {
+        for (int32_t row = rect.top; row < rect.bottom; row++) {
+            for (int32_t col = rect.left; col < rect.right; col++) {
                 uint8_t* pixel = (uint8_t*)(bufData + (row * stride) + col);
                 *pixel = r;
                 *(pixel + 1) = g;
@@ -271,16 +282,16 @@
                             bool outsideRegion = false) {
         sp<GraphicBuffer>& captureBuf = mCaptureResults.buffer;
         const auto epsilon = 3;
-        const auto width = captureBuf->getWidth();
-        const auto height = captureBuf->getHeight();
+        const int32_t width = static_cast<int32_t>(captureBuf->getWidth());
+        const int32_t height = static_cast<int32_t>(captureBuf->getHeight());
         const auto stride = captureBuf->getStride();
 
         uint32_t* bufData;
         captureBuf->lock(static_cast<uint32_t>(GraphicBuffer::USAGE_SW_READ_OFTEN),
                          reinterpret_cast<void**>(&bufData));
 
-        for (uint32_t row = 0; row < height; row++) {
-            for (uint32_t col = 0; col < width; col++) {
+        for (int32_t row = 0; row < height; row++) {
+            for (int32_t col = 0; col < width; col++) {
                 uint8_t* pixel = (uint8_t*)(bufData + (row * stride) + col);
                 ASSERT_NE(nullptr, pixel);
                 bool inRegion;
@@ -352,8 +363,8 @@
     // create BLASTBufferQueue adapter associated with this surface
     BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
     ASSERT_EQ(mSurfaceControl, adapter.getSurfaceControl());
-    ASSERT_EQ(mDisplayWidth, adapter.getWidth());
-    ASSERT_EQ(mDisplayHeight, adapter.getHeight());
+    ASSERT_EQ(static_cast<int32_t>(mDisplayWidth), adapter.getWidth());
+    ASSERT_EQ(static_cast<int32_t>(mDisplayHeight), adapter.getHeight());
     ASSERT_EQ(nullptr, adapter.getTransactionReadyCallback());
 }
 
@@ -371,10 +382,10 @@
 
     int32_t width;
     igbProducer->query(NATIVE_WINDOW_WIDTH, &width);
-    ASSERT_EQ(mDisplayWidth / 2, width);
+    ASSERT_EQ(static_cast<int32_t>(mDisplayWidth) / 2, width);
     int32_t height;
     igbProducer->query(NATIVE_WINDOW_HEIGHT, &height);
-    ASSERT_EQ(mDisplayHeight / 2, height);
+    ASSERT_EQ(static_cast<int32_t>(mDisplayHeight) / 2, height);
 }
 
 TEST_F(BLASTBufferQueueTest, SyncNextTransaction) {
@@ -476,7 +487,7 @@
         ASSERT_EQ(OK, igbProducer->requestBuffer(slot, &buf));
         allocated.push_back({slot, fence});
     }
-    for (int i = 0; i < allocated.size(); i++) {
+    for (size_t i = 0; i < allocated.size(); i++) {
         igbProducer->cancelBuffer(allocated[i].first, allocated[i].second);
     }
 
@@ -1313,14 +1324,14 @@
     // Before connecting to the surface, we do not get a valid transform hint
     int transformHint;
     surface->query(NATIVE_WINDOW_TRANSFORM_HINT, &transformHint);
-    ASSERT_EQ(ui::Transform::ROT_0, transformHint);
+    ASSERT_EQ(ui::Transform::ROT_0, static_cast<ui::Transform::RotationFlags>(transformHint));
 
     ASSERT_EQ(NO_ERROR,
               surface->connect(NATIVE_WINDOW_API_CPU, new TestProducerListener(igbProducer)));
 
     // After connecting to the surface, we should get the correct hint.
     surface->query(NATIVE_WINDOW_TRANSFORM_HINT, &transformHint);
-    ASSERT_EQ(ui::Transform::ROT_90, transformHint);
+    ASSERT_EQ(ui::Transform::ROT_90, static_cast<ui::Transform::RotationFlags>(transformHint));
 
     ANativeWindow_Buffer buffer;
     surface->lock(&buffer, nullptr /* inOutDirtyBounds */);
@@ -1331,13 +1342,13 @@
 
     // The hint does not change and matches the value used when dequeueing the buffer.
     surface->query(NATIVE_WINDOW_TRANSFORM_HINT, &transformHint);
-    ASSERT_EQ(ui::Transform::ROT_90, transformHint);
+    ASSERT_EQ(ui::Transform::ROT_90, static_cast<ui::Transform::RotationFlags>(transformHint));
 
     surface->unlockAndPost();
 
     // After queuing the buffer, we get the updated transform hint
     surface->query(NATIVE_WINDOW_TRANSFORM_HINT, &transformHint);
-    ASSERT_EQ(ui::Transform::ROT_0, transformHint);
+    ASSERT_EQ(ui::Transform::ROT_0, static_cast<ui::Transform::RotationFlags>(transformHint));
 
     adapter.waitForCallbacks();
 }
@@ -1573,7 +1584,7 @@
     FrameEvents* events = nullptr;
     events = history.getFrame(1);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(1u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeA);
 
@@ -1591,7 +1602,7 @@
     ASSERT_NE(nullptr, events);
 
     // frame number, requestedPresentTime, and postTime should not have changed
-    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(1u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeA);
 
@@ -1606,7 +1617,7 @@
     // we should also have gotten the initial values for the next frame
     events = history.getFrame(2);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(2, events->frameNumber);
+    ASSERT_EQ(2u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeB);
 
@@ -1622,7 +1633,7 @@
     // Check the first frame...
     events = history.getFrame(1);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(1u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeA);
     ASSERT_GE(events->latchTime, postedTimeA);
@@ -1636,7 +1647,7 @@
     // ...and the second
     events = history.getFrame(2);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(2, events->frameNumber);
+    ASSERT_EQ(2u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeB);
     ASSERT_GE(events->latchTime, postedTimeB);
@@ -1650,7 +1661,7 @@
     // ...and finally the third!
     events = history.getFrame(3);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(3, events->frameNumber);
+    ASSERT_EQ(3u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeC, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeC);
 
@@ -1677,7 +1688,7 @@
     FrameEvents* events = nullptr;
     events = history.getFrame(1);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(1u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeA);
 
@@ -1692,7 +1703,7 @@
     ASSERT_NE(nullptr, events);
 
     // frame number, requestedPresentTime, and postTime should not have changed
-    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(1u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeA);
 
@@ -1715,7 +1726,7 @@
     adapter.waitForCallback(3);
 
     // frame number, requestedPresentTime, and postTime should not have changed
-    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(1u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeA);
 
@@ -1729,7 +1740,7 @@
     // we should also have gotten values for the presented frame
     events = history.getFrame(2);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(2, events->frameNumber);
+    ASSERT_EQ(2u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeB);
     ASSERT_GE(events->latchTime, postedTimeB);
@@ -1751,7 +1762,7 @@
 
     // frame number, requestedPresentTime, and postTime should not have changed
     events = history.getFrame(1);
-    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(1u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeA);
 
@@ -1765,7 +1776,7 @@
     // we should also have gotten values for the presented frame
     events = history.getFrame(2);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(2, events->frameNumber);
+    ASSERT_EQ(2u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeB);
     ASSERT_GE(events->latchTime, postedTimeB);
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index df7739c..1ec6f91 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -119,8 +119,7 @@
     }
 
     sp<IServiceManager> serviceManager = defaultServiceManager();
-    sp<IBinder> binderProducer =
-        serviceManager->getService(PRODUCER_NAME);
+    sp<IBinder> binderProducer = serviceManager->waitForService(PRODUCER_NAME);
     mProducer = interface_cast<IGraphicBufferProducer>(binderProducer);
     EXPECT_TRUE(mProducer != nullptr);
     sp<IBinder> binderConsumer =
@@ -1114,7 +1113,7 @@
 
     // Check onBuffersDiscarded is called with correct slots
     auto buffersDiscarded = pl->getDiscardedSlots();
-    ASSERT_EQ(buffersDiscarded.size(), 1);
+    ASSERT_EQ(buffersDiscarded.size(), 1u);
     ASSERT_EQ(buffersDiscarded[0], releasedSlot);
 
     // Check no free buffers in dump
@@ -1239,7 +1238,7 @@
     ASSERT_EQ(OK, mConsumer->detachBuffer(item.mSlot));
 
     // Check whether the slot from IProducerListener is same to the detached slot.
-    ASSERT_EQ(pl->getDetachedSlots().size(), 1);
+    ASSERT_EQ(pl->getDetachedSlots().size(), 1u);
     ASSERT_EQ(pl->getDetachedSlots()[0], slots[1]);
 
     // Dequeue another buffer.
diff --git a/libs/gui/tests/DisplayedContentSampling_test.cpp b/libs/gui/tests/DisplayedContentSampling_test.cpp
index 0a2750a..bffb3f0 100644
--- a/libs/gui/tests/DisplayedContentSampling_test.cpp
+++ b/libs/gui/tests/DisplayedContentSampling_test.cpp
@@ -116,10 +116,10 @@
     EXPECT_EQ(OK, status);
     if (stats.numFrames <= 0) return;
 
-    if (componentMask & (0x1 << 0)) EXPECT_NE(0, stats.component_0_sample.size());
-    if (componentMask & (0x1 << 1)) EXPECT_NE(0, stats.component_1_sample.size());
-    if (componentMask & (0x1 << 2)) EXPECT_NE(0, stats.component_2_sample.size());
-    if (componentMask & (0x1 << 3)) EXPECT_NE(0, stats.component_3_sample.size());
+    if (componentMask & (0x1 << 0)) EXPECT_NE(0u, stats.component_0_sample.size());
+    if (componentMask & (0x1 << 1)) EXPECT_NE(0u, stats.component_1_sample.size());
+    if (componentMask & (0x1 << 2)) EXPECT_NE(0u, stats.component_2_sample.size());
+    if (componentMask & (0x1 << 3)) EXPECT_NE(0u, stats.component_3_sample.size());
 }
 
 } // namespace android
diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp
index 8d5d1e4..f441eaa 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -24,6 +24,7 @@
 
 #include <memory>
 
+#include <android-base/thread_annotations.h>
 #include <android/gui/BnWindowInfosReportedListener.h>
 #include <android/keycodes.h>
 #include <android/native_window.h>
@@ -78,21 +79,22 @@
 class SynchronousWindowInfosReportedListener : public gui::BnWindowInfosReportedListener {
 public:
     binder::Status onWindowInfosReported() override {
-        std::lock_guard<std::mutex> lock{mMutex};
+        std::scoped_lock lock{mLock};
         mWindowInfosReported = true;
         mConditionVariable.notify_one();
         return binder::Status::ok();
     }
 
     void wait() {
-        std::unique_lock<std::mutex> lock{mMutex};
-        mConditionVariable.wait(lock, [&] { return mWindowInfosReported; });
+        std::unique_lock lock{mLock};
+        android::base::ScopedLockAssertion assumeLocked(mLock);
+        mConditionVariable.wait(lock, [&]() REQUIRES(mLock) { return mWindowInfosReported; });
     }
 
 private:
-    std::mutex mMutex;
+    std::mutex mLock;
     std::condition_variable mConditionVariable;
-    bool mWindowInfosReported{false};
+    bool mWindowInfosReported GUARDED_BY(mLock){false};
 };
 
 class InputSurface {
@@ -250,7 +252,7 @@
         EXPECT_EQ(0, mev->getFlags() & VERIFIED_MOTION_EVENT_FLAGS);
     }
 
-    void expectKey(uint32_t keycode) {
+    void expectKey(int32_t keycode) {
         InputEvent *ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
         ASSERT_EQ(InputEventType::KEY, ev->getType());
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 93bf4fa..f4b059c 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -173,7 +173,7 @@
         // Acquire and free 1+extraDiscardedBuffers buffer, check onBufferReleased is called.
         std::vector<BufferItem> releasedItems;
         releasedItems.resize(1+extraDiscardedBuffers);
-        for (int i = 0; i < releasedItems.size(); i++) {
+        for (size_t i = 0; i < releasedItems.size(); i++) {
             ASSERT_EQ(NO_ERROR, consumer->acquireBuffer(&releasedItems[i], 0));
             ASSERT_EQ(NO_ERROR, consumer->releaseBuffer(releasedItems[i].mSlot,
                     releasedItems[i].mFrameNumber, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR,
@@ -197,7 +197,7 @@
             // Check onBufferDiscarded is called with correct buffer
             auto discardedBuffers = listener->getDiscardedBuffers();
             ASSERT_EQ(discardedBuffers.size(), releasedItems.size());
-            for (int i = 0; i < releasedItems.size(); i++) {
+            for (size_t i = 0; i < releasedItems.size(); i++) {
                 ASSERT_EQ(discardedBuffers[i], releasedItems[i].mGraphicBuffer);
             }
 
diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp
index 77292d4..5b61d39 100644
--- a/libs/input/MotionPredictor.cpp
+++ b/libs/input/MotionPredictor.cpp
@@ -18,6 +18,7 @@
 
 #include <input/MotionPredictor.h>
 
+#include <algorithm>
 #include <array>
 #include <cinttypes>
 #include <cmath>
@@ -62,6 +63,11 @@
     return {.x = axisTo.x + x_delta, .y = axisTo.y + y_delta};
 }
 
+float normalizeRange(float x, float min, float max) {
+    const float normalized = (x - min) / (max - min);
+    return std::min(1.0f, std::max(0.0f, normalized));
+}
+
 } // namespace
 
 // --- JerkTracker ---
@@ -255,6 +261,17 @@
     int64_t predictionTime = mBuffers->lastTimestamp();
     const int64_t futureTime = timestamp + mPredictionTimestampOffsetNanos;
 
+    const float jerkMagnitude = mJerkTracker.jerkMagnitude().value_or(0);
+    const float fractionKept =
+            1 - normalizeRange(jerkMagnitude, mModel->config().lowJerk, mModel->config().highJerk);
+    // float to ensure proper division below.
+    const float predictionTimeWindow = futureTime - predictionTime;
+    const int maxNumPredictions = static_cast<int>(
+            std::ceil(predictionTimeWindow / mModel->config().predictionInterval * fractionKept));
+    ALOGD_IF(isDebug(),
+             "jerk (d^3p/normalizedDt^3): %f, fraction of prediction window pruned: %f, max number "
+             "of predictions: %d",
+             jerkMagnitude, 1 - fractionKept, maxNumPredictions);
     for (size_t i = 0; i < static_cast<size_t>(predictedR.size()) && predictionTime <= futureTime;
          ++i) {
         if (predictedR[i] < mModel->config().distanceNoiseFloor) {
@@ -269,13 +286,12 @@
             break;
         }
         if (input_flags::enable_prediction_pruning_via_jerk_thresholding()) {
-            // TODO(b/266747654): Stop predictions if confidence is < some threshold
-            // Arbitrarily high pruning index, will correct once jerk thresholding is implemented.
-            const size_t upperBoundPredictionIndex = std::numeric_limits<size_t>::max();
-            if (i > upperBoundPredictionIndex) {
+            if (i >= static_cast<size_t>(maxNumPredictions)) {
                 break;
             }
         }
+        // TODO(b/266747654): Stop predictions if confidence is < some
+        // threshold. Currently predictions are pruned via jerk thresholding.
 
         const TfLiteMotionPredictorSample::Point predictedPoint =
                 convertPrediction(axisFrom, axisTo, predictedR[i], predictedPhi[i]);
diff --git a/libs/input/TfLiteMotionPredictor.cpp b/libs/input/TfLiteMotionPredictor.cpp
index d17476e..b843a4b 100644
--- a/libs/input/TfLiteMotionPredictor.cpp
+++ b/libs/input/TfLiteMotionPredictor.cpp
@@ -281,6 +281,8 @@
     Config config{
             .predictionInterval = parseXMLInt64(*configRoot, "prediction-interval"),
             .distanceNoiseFloor = parseXMLFloat(*configRoot, "distance-noise-floor"),
+            .lowJerk = parseXMLFloat(*configRoot, "low-jerk"),
+            .highJerk = parseXMLFloat(*configRoot, "high-jerk"),
     };
 
     return std::unique_ptr<TfLiteMotionPredictorModel>(
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index e67a65a..ee140b7 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -36,6 +36,7 @@
         "tensorflow_headers",
     ],
     static_libs: [
+        "libflagtest",
         "libgmock",
         "libgui_window_info_static",
         "libinput",
diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp
index f74874c..dc38fef 100644
--- a/libs/input/tests/MotionPredictor_test.cpp
+++ b/libs/input/tests/MotionPredictor_test.cpp
@@ -14,9 +14,12 @@
  * limitations under the License.
  */
 
+// TODO(b/331815574): Decouple this test from assumed config values.
 #include <chrono>
 #include <cmath>
 
+#include <com_android_input_flags.h>
+#include <flag_macros.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <gui/constants.h>
@@ -197,18 +200,14 @@
 TEST(MotionPredictorTest, FollowsGesture) {
     MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
                               []() { return true /*enable prediction*/; });
+    predictor.record(getMotionEvent(DOWN, 3.75, 3, 20ms));
+    predictor.record(getMotionEvent(MOVE, 4.8, 3, 30ms));
+    predictor.record(getMotionEvent(MOVE, 6.2, 3, 40ms));
+    predictor.record(getMotionEvent(MOVE, 8, 3, 50ms));
+    EXPECT_NE(nullptr, predictor.predict(90 * NSEC_PER_MSEC));
 
-    // MOVE without a DOWN is ignored.
-    predictor.record(getMotionEvent(MOVE, 1, 3, 10ms));
-    EXPECT_EQ(nullptr, predictor.predict(20 * NSEC_PER_MSEC));
-
-    predictor.record(getMotionEvent(DOWN, 2, 5, 20ms));
-    predictor.record(getMotionEvent(MOVE, 2, 7, 30ms));
-    predictor.record(getMotionEvent(MOVE, 3, 9, 40ms));
-    EXPECT_NE(nullptr, predictor.predict(50 * NSEC_PER_MSEC));
-
-    predictor.record(getMotionEvent(UP, 4, 11, 50ms));
-    EXPECT_EQ(nullptr, predictor.predict(20 * NSEC_PER_MSEC));
+    predictor.record(getMotionEvent(UP, 10.25, 3, 60ms));
+    EXPECT_EQ(nullptr, predictor.predict(100 * NSEC_PER_MSEC));
 }
 
 TEST(MotionPredictorTest, MultipleDevicesNotSupported) {
@@ -250,6 +249,63 @@
     ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_TOUCHSCREEN));
 }
 
+TEST_WITH_FLAGS(
+        MotionPredictorTest, LowJerkNoPruning,
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                            enable_prediction_pruning_via_jerk_thresholding))) {
+    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
+                              []() { return true /*enable prediction*/; });
+
+    // Jerk is low (0.05 normalized).
+    predictor.record(getMotionEvent(DOWN, 2, 7, 20ms));
+    predictor.record(getMotionEvent(MOVE, 2.75, 7, 30ms));
+    predictor.record(getMotionEvent(MOVE, 3.8, 7, 40ms));
+    predictor.record(getMotionEvent(MOVE, 5.2, 7, 50ms));
+    predictor.record(getMotionEvent(MOVE, 7, 7, 60ms));
+    std::unique_ptr<MotionEvent> predicted = predictor.predict(90 * NSEC_PER_MSEC);
+    EXPECT_NE(nullptr, predicted);
+    EXPECT_EQ(static_cast<size_t>(5), predicted->getHistorySize() + 1);
+}
+
+TEST_WITH_FLAGS(
+        MotionPredictorTest, HighJerkPredictionsPruned,
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                            enable_prediction_pruning_via_jerk_thresholding))) {
+    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
+                              []() { return true /*enable prediction*/; });
+
+    // Jerk is incredibly high.
+    predictor.record(getMotionEvent(DOWN, 0, 5, 20ms));
+    predictor.record(getMotionEvent(MOVE, 0, 70, 30ms));
+    predictor.record(getMotionEvent(MOVE, 0, 139, 40ms));
+    predictor.record(getMotionEvent(MOVE, 0, 1421, 50ms));
+    predictor.record(getMotionEvent(MOVE, 0, 41233, 60ms));
+    std::unique_ptr<MotionEvent> predicted = predictor.predict(90 * NSEC_PER_MSEC);
+    EXPECT_EQ(nullptr, predicted);
+}
+
+TEST_WITH_FLAGS(
+        MotionPredictorTest, MediumJerkPredictionsSomePruned,
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                            enable_prediction_pruning_via_jerk_thresholding))) {
+    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));
+    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
+    // will be pruned. If model prediction window is close enough to predict()
+    // call time window, then half of the model predictions (5/2 -> 2) will be
+    // ouputted.
+    EXPECT_EQ(static_cast<size_t>(3), predicted->getHistorySize() + 1);
+}
+
 using AtomFields = MotionPredictorMetricsManager::AtomFields;
 using ReportAtomFunction = MotionPredictorMetricsManager::ReportAtomFunction;
 
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 48219bd..76c492e 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -838,13 +838,6 @@
         if (!inserted) {
             return Error() << "Duplicate entry for " << info;
         }
-        if (info.layoutParamsFlags.test(WindowInfo::Flag::SECURE) &&
-            !info.inputConfig.test(WindowInfo::InputConfig::NOT_VISIBLE) &&
-            !info.inputConfig.test(WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)) {
-            return Error()
-                    << "Window with FLAG_SECURE does not set InputConfig::SENSITIVE_FOR_TRACING: "
-                    << info;
-        }
     }
     return {};
 }
@@ -2539,11 +2532,19 @@
             if (!isHoverAction) {
                 const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN ||
                         maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN;
-                tempTouchState.addOrUpdateWindow(windowHandle, InputTarget::DispatchMode::AS_IS,
-                                                 targetFlags, entry.deviceId, {pointer},
-                                                 isDownOrPointerDown
-                                                         ? std::make_optional(entry.eventTime)
-                                                         : std::nullopt);
+                Result<void> addResult =
+                        tempTouchState.addOrUpdateWindow(windowHandle,
+                                                         InputTarget::DispatchMode::AS_IS,
+                                                         targetFlags, entry.deviceId, {pointer},
+                                                         isDownOrPointerDown
+                                                                 ? std::make_optional(
+                                                                           entry.eventTime)
+                                                                 : std::nullopt);
+                if (!addResult.ok()) {
+                    LOG(ERROR) << "Error while processing " << entry << " for "
+                               << windowHandle->getName();
+                    logDispatchStateLocked();
+                }
                 // If this is the pointer going down and the touched window has a wallpaper
                 // then also add the touched wallpaper windows so they are locked in for the
                 // duration of the touch gesture. We do not collect wallpapers during HOVER_MOVE or
@@ -3053,7 +3054,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/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 f8aa625..0caa5e1 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -70,14 +70,14 @@
     });
 }
 
-void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle,
-                                   InputTarget::DispatchMode dispatchMode,
-                                   ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId,
-                                   const std::vector<PointerProperties>& touchingPointers,
-                                   std::optional<nsecs_t> firstDownTimeInTarget) {
+android::base::Result<void> TouchState::addOrUpdateWindow(
+        const sp<WindowInfoHandle>& windowHandle, InputTarget::DispatchMode dispatchMode,
+        ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId,
+        const std::vector<PointerProperties>& touchingPointers,
+        std::optional<nsecs_t> firstDownTimeInTarget) {
     if (touchingPointers.empty()) {
         LOG(FATAL) << __func__ << "No pointers specified for " << windowHandle->getName();
-        return;
+        return android::base::Error();
     }
     for (TouchedWindow& touchedWindow : windows) {
         // We do not compare windows by token here because two windows that share the same token
@@ -91,11 +91,12 @@
             // For cases like hover enter/exit or DISPATCH_AS_OUTSIDE a touch window might not have
             // downTime set initially. Need to update existing window when a pointer is down for the
             // window.
-            touchedWindow.addTouchingPointers(deviceId, touchingPointers);
+            android::base::Result<void> addResult =
+                    touchedWindow.addTouchingPointers(deviceId, touchingPointers);
             if (firstDownTimeInTarget) {
                 touchedWindow.trySetDownTimeInTarget(deviceId, *firstDownTimeInTarget);
             }
-            return;
+            return addResult;
         }
     }
     TouchedWindow touchedWindow;
@@ -107,6 +108,7 @@
         touchedWindow.trySetDownTimeInTarget(deviceId, *firstDownTimeInTarget);
     }
     windows.push_back(touchedWindow);
+    return {};
 }
 
 void TouchState::addHoveringPointerToWindow(const sp<WindowInfoHandle>& windowHandle,
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 3d534bc..559a3fd 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -43,11 +43,11 @@
     void removeTouchingPointer(DeviceId deviceId, int32_t pointerId);
     void removeTouchingPointerFromWindow(DeviceId deviceId, int32_t pointerId,
                                          const sp<android::gui::WindowInfoHandle>& windowHandle);
-    void addOrUpdateWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
-                           InputTarget::DispatchMode dispatchMode,
-                           ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId,
-                           const std::vector<PointerProperties>& touchingPointers,
-                           std::optional<nsecs_t> firstDownTimeInTarget = std::nullopt);
+    android::base::Result<void> addOrUpdateWindow(
+            const sp<android::gui::WindowInfoHandle>& windowHandle,
+            InputTarget::DispatchMode dispatchMode, ftl::Flags<InputTarget::Flags> targetFlags,
+            DeviceId deviceId, const std::vector<PointerProperties>& touchingPointers,
+            std::optional<nsecs_t> firstDownTimeInTarget = std::nullopt);
     void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
                                     DeviceId deviceId, const PointerProperties& pointer);
     void removeHoveringPointer(DeviceId deviceId, int32_t pointerId);
diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp
index 037d7c8..1f86f66 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.cpp
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -20,6 +20,7 @@
 #include <android-base/stringprintf.h>
 #include <input/PrintTools.h>
 
+using android::base::Result;
 using android::base::StringPrintf;
 
 namespace android {
@@ -89,8 +90,8 @@
     hoveringPointers.push_back(pointer);
 }
 
-void TouchedWindow::addTouchingPointers(DeviceId deviceId,
-                                        const std::vector<PointerProperties>& pointers) {
+Result<void> TouchedWindow::addTouchingPointers(DeviceId deviceId,
+                                                const std::vector<PointerProperties>& pointers) {
     std::vector<PointerProperties>& touchingPointers = mDeviceStates[deviceId].touchingPointers;
     const size_t initialSize = touchingPointers.size();
     for (const PointerProperties& pointer : pointers) {
@@ -98,11 +99,14 @@
             return properties.id == pointer.id;
         });
     }
-    if (touchingPointers.size() != initialSize) {
+    const bool foundInconsistentState = touchingPointers.size() != initialSize;
+    touchingPointers.insert(touchingPointers.end(), pointers.begin(), pointers.end());
+    if (foundInconsistentState) {
         LOG(ERROR) << __func__ << ": " << dumpVector(pointers, streamableToString) << ", device "
                    << deviceId << " already in " << *this;
+        return android::base::Error();
     }
-    touchingPointers.insert(touchingPointers.end(), pointers.begin(), pointers.end());
+    return {};
 }
 
 bool TouchedWindow::hasTouchingPointers() const {
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index 0d1531f..4f0ad16 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -46,7 +46,8 @@
     bool hasTouchingPointers() const;
     bool hasTouchingPointers(DeviceId deviceId) const;
     std::vector<PointerProperties> getTouchingPointers(DeviceId deviceId) const;
-    void addTouchingPointers(DeviceId deviceId, const std::vector<PointerProperties>& pointers);
+    android::base::Result<void> addTouchingPointers(DeviceId deviceId,
+                                                    const std::vector<PointerProperties>& pointers);
     void removeTouchingPointer(DeviceId deviceId, int32_t pointerId);
     void removeTouchingPointers(DeviceId deviceId, std::bitset<MAX_POINTER_ID + 1> pointers);
     bool hasActiveStylus() const;
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp
index f8ee95f..415b696 100644
--- a/services/inputflinger/dispatcher/trace/InputTracer.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp
@@ -86,8 +86,16 @@
         // This is a global monitor, assume its target is the system.
         return {.uid = gui::Uid{AID_SYSTEM}, .isSecureWindow = false};
     }
-    const bool isSensitiveTarget = target.windowHandle->getInfo()->inputConfig.test(
-            gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING);
+    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/tests/FakeWindows.cpp b/services/inputflinger/tests/FakeWindows.cpp
index bfe09bc..a6955ec 100644
--- a/services/inputflinger/tests/FakeWindows.cpp
+++ b/services/inputflinger/tests/FakeWindows.cpp
@@ -309,6 +309,9 @@
     }
     std::unique_ptr<MotionEvent> motionEvent =
             std::unique_ptr<MotionEvent>(static_cast<MotionEvent*>(event.release()));
+    if (motionEvent == nullptr) {
+        return nullptr;
+    }
     EXPECT_THAT(*motionEvent, matcher) << " on " << mName;
     return motionEvent;
 }
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 7f25608..62a9235 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -59,6 +59,8 @@
 using namespace ftl::flag_operators;
 using testing::AllOf;
 using testing::Not;
+using testing::Pointee;
+using testing::UnorderedElementsAre;
 
 namespace {
 
@@ -12290,16 +12292,24 @@
             AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId)));
 
     // Continue movements from both stylus and touch. Touch and stylus will be delivered to spy
+    // Instead of sending the two MOVE events for each input device together, and then receiving
+    // them both, process them one at at time. InputConsumer is always in the batching mode, which
+    // means that the two MOVE events will be initially put into a batch. Once the events are
+    // batched, the 'consume' call may result in any of the MOVE events to be sent first (depending
+    // on the implementation of InputConsumer), which would mean that the order of the received
+    // events could be different depending on whether there are 1 or 2 events pending in the
+    // InputChannel at the time the test calls 'consume'. To make assertions simpler here, and to
+    // avoid this confusing behaviour, send and receive each MOVE event separately.
     mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_STYLUS)
                                       .deviceId(stylusDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(52))
                                       .build());
+    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
     mDispatcher->notifyMotion(
             MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
                     .deviceId(touchDeviceId)
                     .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(52))
                     .build());
-    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
     spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
 
     spy->assertNoEvents();
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index a661292..8ce61d8 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -68,6 +68,13 @@
 
     void setDisplayModePtr(ftl::NonNull<DisplayModePtr>) final EXCLUDES(mMutex);
 
+    bool isCurrentMode(const ftl::NonNull<DisplayModePtr>& modePtr) const EXCLUDES(mMutex) {
+        std::lock_guard lock(mMutex);
+        return mDisplayModePtr->getId() == modePtr->getId() &&
+                mDisplayModePtr->getVsyncRate().getPeriodNsecs() ==
+                mRateMap.find(idealPeriod())->second.slope;
+    }
+
     void setRenderRate(Fps, bool applyImmediately) final EXCLUDES(mMutex);
 
     void onFrameBegin(TimePoint expectedPresentTime, TimePoint lastConfirmedPresentTime) final
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
index 9b8f310..8038364 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
@@ -141,8 +141,7 @@
     std::lock_guard lock(mMutex);
     mLastHwVsync.reset();
 
-    if (!mSupportKernelIdleTimer &&
-        modePtr->getVsyncRate().getPeriodNsecs() == mTracker.currentPeriod() && !force) {
+    if (!mSupportKernelIdleTimer && mTracker.isCurrentMode(modePtr) && !force) {
         endPeriodTransition();
         setIgnorePresentFencesInternal(false);
         mMoreSamplesNeeded = false;
diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h
index 8787cdb..134d28e 100644
--- a/services/surfaceflinger/Scheduler/VSyncTracker.h
+++ b/services/surfaceflinger/Scheduler/VSyncTracker.h
@@ -71,6 +71,11 @@
      */
     virtual Period minFramePeriod() const = 0;
 
+    /**
+     * Checks if the sourced mode is equal to the mode in the tracker.
+     */
+    virtual bool isCurrentMode(const ftl::NonNull<DisplayModePtr>& modePtr) const = 0;
+
     /* Inform the tracker that the samples it has are not accurate for prediction. */
     virtual void resetModel() = 0;
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 949a030..81697da 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -7838,17 +7838,19 @@
 
     status_t validate = validateScreenshotPermissions(args);
     if (validate != OK) {
+        ALOGD("Permission denied to captureDisplay");
         invokeScreenCaptureError(validate, captureListener);
         return;
     }
 
     if (!args.displayToken) {
+        ALOGD("Invalid display token to captureDisplay");
         invokeScreenCaptureError(BAD_VALUE, captureListener);
         return;
     }
 
     if (args.captureSecureLayers && !hasCaptureBlackoutContentPermission()) {
-        ALOGE("Attempting to capture secure layers without CAPTURE_BLACKOUT_CONTENT");
+        ALOGD("Attempting to capture secure layers without CAPTURE_BLACKOUT_CONTENT");
         invokeScreenCaptureError(PERMISSION_DENIED, captureListener);
         return;
     }
@@ -7861,6 +7863,7 @@
         Mutex::Autolock lock(mStateLock);
         sp<DisplayDevice> display = getDisplayDeviceLocked(args.displayToken);
         if (!display) {
+            ALOGD("Unable to find display device for captureDisplay");
             invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
             return;
         }
@@ -7877,7 +7880,7 @@
             if (excludeLayer != UNASSIGNED_LAYER_ID) {
                 excludeLayerIds.emplace(excludeLayer);
             } else {
-                ALOGW("Invalid layer handle passed as excludeLayer to captureDisplay");
+                ALOGD("Invalid layer handle passed as excludeLayer to captureDisplay");
                 invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
                 return;
             }
@@ -7915,6 +7918,7 @@
 
         const auto display = getDisplayDeviceLocked(displayId);
         if (!display) {
+            ALOGD("Unable to find display device for captureDisplay");
             invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
             return;
         }
@@ -7932,7 +7936,7 @@
     constexpr auto kMaxTextureSize = 16384;
     if (size.width <= 0 || size.height <= 0 || size.width >= kMaxTextureSize ||
         size.height >= kMaxTextureSize) {
-        ALOGE("capture display resolved to invalid size %d x %d", size.width, size.height);
+        ALOGD("captureDisplay resolved to invalid size %d x %d", size.width, size.height);
         invokeScreenCaptureError(BAD_VALUE, captureListener);
         return;
     }
@@ -7979,6 +7983,7 @@
 
     status_t validate = validateScreenshotPermissions(args);
     if (validate != OK) {
+        ALOGD("Permission denied to captureLayers");
         invokeScreenCaptureError(validate, captureListener);
         return;
     }
@@ -7990,7 +7995,7 @@
     ui::Dataspace dataspace = args.dataspace;
 
     if (args.captureSecureLayers && !hasCaptureBlackoutContentPermission()) {
-        ALOGE("Attempting to capture secure layers without CAPTURE_BLACKOUT_CONTENT");
+        ALOGD("Attempting to capture secure layers without CAPTURE_BLACKOUT_CONTENT");
         invokeScreenCaptureError(PERMISSION_DENIED, captureListener);
         return;
     }
@@ -8000,7 +8005,7 @@
 
         parent = LayerHandle::getLayer(args.layerHandle);
         if (parent == nullptr) {
-            ALOGE("captureLayers called with an invalid or removed parent");
+            ALOGD("captureLayers called with an invalid or removed parent");
             invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
             return;
         }
@@ -8019,6 +8024,7 @@
         if (crop.isEmpty() || args.frameScaleX <= 0.0f || args.frameScaleY <= 0.0f) {
             // Error out if the layer has no source bounds (i.e. they are boundless) and a source
             // crop was not specified, or an invalid frame scale was provided.
+            ALOGD("Boundless layer, unspecified crop, or invalid frame scale to captureLayers");
             invokeScreenCaptureError(BAD_VALUE, captureListener);
             return;
         }
@@ -8029,7 +8035,7 @@
             if (excludeLayer != UNASSIGNED_LAYER_ID) {
                 excludeLayerIds.emplace(excludeLayer);
             } else {
-                ALOGW("Invalid layer handle passed as excludeLayer to captureLayers");
+                ALOGD("Invalid layer handle passed as excludeLayer to captureLayers");
                 invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
                 return;
             }
@@ -8038,7 +8044,7 @@
 
     // really small crop or frameScale
     if (reqSize.width <= 0 || reqSize.height <= 0) {
-        ALOGW("Failed to captureLayers: crop or scale too small");
+        ALOGD("Failed to captureLayers: crop or scale too small");
         invokeScreenCaptureError(BAD_VALUE, captureListener);
         return;
     }
@@ -8103,7 +8109,7 @@
     }
 
     if (captureListener == nullptr) {
-        ALOGE("capture screen must provide a capture listener callback");
+        ALOGD("capture screen must provide a capture listener callback");
         invokeScreenCaptureError(BAD_VALUE, captureListener);
         return;
     }
@@ -9869,6 +9875,7 @@
         std::optional<DisplayId> id = DisplayId::fromValue(static_cast<uint64_t>(displayId));
         mFlinger->captureDisplay(*id, args, captureListener);
     } else {
+        ALOGD("Permission denied to captureDisplayById");
         invokeScreenCaptureError(PERMISSION_DENIED, captureListener);
     }
     return binderStatusFromStatusT(NO_ERROR);
diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
index d7b220f..0b9fd58 100644
--- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
@@ -22,13 +22,6 @@
  } # ce_fence_promise
 
 flag {
-  name: "dont_skip_on_early_ro2"
-  namespace: "core_graphics"
-  description: "This flag is guarding the behaviour where SurfaceFlinger is trying to opportunistically present a frame when the configuration change from late to early"
-  bug: "273702768"
-} # dont_skip_on_early_ro2
-
-flag {
   name: "frame_rate_category_mrr"
   namespace: "core_graphics"
   description: "Enable to use frame rate category and newer frame rate votes such as GTE in SurfaceFlinger scheduler, to guard dVRR changes from MRR devices"
diff --git a/services/surfaceflinger/tests/LayerTransactionTest.h b/services/surfaceflinger/tests/LayerTransactionTest.h
index c9af432..5b056d0 100644
--- a/services/surfaceflinger/tests/LayerTransactionTest.h
+++ b/services/surfaceflinger/tests/LayerTransactionTest.h
@@ -106,6 +106,10 @@
         return colorLayer;
     }
 
+    sp<SurfaceControl> mirrorSurface(SurfaceControl* mirrorFromSurface) {
+        return mClient->mirrorSurface(mirrorFromSurface);
+    }
+
     ANativeWindow_Buffer getBufferQueueLayerBuffer(const sp<SurfaceControl>& layer) {
         // wait for previous transactions (such as setSize) to complete
         Transaction().apply(true);
diff --git a/services/surfaceflinger/tests/ScreenCapture_test.cpp b/services/surfaceflinger/tests/ScreenCapture_test.cpp
index 18262f6..9a78550 100644
--- a/services/surfaceflinger/tests/ScreenCapture_test.cpp
+++ b/services/surfaceflinger/tests/ScreenCapture_test.cpp
@@ -1039,6 +1039,29 @@
     ASSERT_TRUE(mCapture->capturedHdrLayers());
 }
 
+TEST_F(ScreenCaptureTest, captureOffscreenNullSnapshot) {
+    sp<SurfaceControl> layer;
+    ASSERT_NO_FATAL_FAILURE(layer = createLayer("test layer", 32, 32,
+                                                ISurfaceComposerClient::eFXSurfaceBufferState,
+                                                mBGSurfaceControl.get()));
+
+    // A mirrored layer will not have a snapshot. Testing an offscreen mirrored layer
+    // ensures that the screenshot path handles cases where snapshots are null.
+    sp<SurfaceControl> mirroredLayer;
+    ASSERT_NO_FATAL_FAILURE(mirroredLayer = mirrorSurface(layer.get()));
+
+    LayerCaptureArgs captureArgs;
+    captureArgs.layerHandle = mirroredLayer->getHandle();
+    captureArgs.sourceCrop = Rect(0, 0, 1, 1);
+
+    // Screenshot path should only use the children of the layer hierarchy so
+    // that it will not create a new snapshot. A snapshot would otherwise be
+    // created to pass on the properties of the parent, which is not needed
+    // for the purposes of this test since we explicitly want a null snapshot.
+    captureArgs.childrenOnly = true;
+    ScreenCapture::captureLayers(&mCapture, captureArgs);
+}
+
 // In the following tests we verify successful skipping of a parent layer,
 // so we use the same verification logic and only change how we mutate
 // the parent layer to verify that various properties are ignored.
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
index d701a97..3b09554 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
@@ -54,6 +54,7 @@
     void onFrameBegin(TimePoint, TimePoint) final {}
     void onFrameMissed(TimePoint) final {}
     void dump(std::string&) const final {}
+    bool isCurrentMode(const ftl::NonNull<DisplayModePtr>&) const final { return false; };
 
 protected:
     std::mutex mutable mMutex;
diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
index e3aa4ef..51373c1 100644
--- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
@@ -230,7 +230,8 @@
 TEST_F(VSyncReactorTest, changingPeriodBackAbortsConfirmationProcess) {
     nsecs_t sampleTime = 0;
     nsecs_t const newPeriod = 5000;
-    mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
+    auto modePtr = displayMode(newPeriod);
+    mReactor.onDisplayModeChanged(modePtr, false);
     bool periodFlushed = true;
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
@@ -238,7 +239,9 @@
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
 
-    mReactor.onDisplayModeChanged(displayMode(period), false);
+    modePtr = displayMode(period);
+    EXPECT_CALL(*mMockTracker, isCurrentMode(modePtr)).WillOnce(Return(true));
+    mReactor.onDisplayModeChanged(modePtr, false);
     EXPECT_FALSE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
 }
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
index c311901..4f44d1b 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
@@ -40,6 +40,7 @@
     MOCK_METHOD(void, onFrameBegin, (TimePoint, TimePoint), (override));
     MOCK_METHOD(void, onFrameMissed, (TimePoint), (override));
     MOCK_METHOD(void, dump, (std::string&), (const, override));
+    MOCK_METHOD(bool, isCurrentMode, (const ftl::NonNull<DisplayModePtr>&), (const, override));
 };
 
 } // namespace android::mock