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