Merge "Create test for screenshots with null snapshots" into main
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/InputConsumer.h b/include/input/InputConsumer.h
index 560e804..611478c 100644
--- a/include/input/InputConsumer.h
+++ b/include/input/InputConsumer.h
@@ -111,6 +111,11 @@
std::shared_ptr<InputChannel> mChannel;
+ // TODO(b/311142655): delete this temporary tracing after the ANR bug is fixed
+ const std::string mProcessingTraceTag;
+ const std::string mLifetimeTraceTag;
+ const int32_t mLifetimeTraceCookie;
+
// The current input message.
InputMessage mMsg;
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/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index 2d1b51a..e4f1890 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -178,6 +178,8 @@
static_cast<uint32_t>(os::InputConfig::CLONE),
GLOBAL_STYLUS_BLOCKS_TOUCH =
static_cast<uint32_t>(os::InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH),
+ SENSITIVE_FOR_TRACING =
+ static_cast<uint32_t>(os::InputConfig::SENSITIVE_FOR_TRACING),
// clang-format on
};
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 9791212..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 {
@@ -195,7 +197,7 @@
EXPECT_EQ(hasFocus, focusEvent->getHasFocus());
}
- void expectTap(int x, int y) {
+ void expectTap(float x, float y) {
InputEvent* ev = consumeEvent();
ASSERT_NE(ev, nullptr);
ASSERT_EQ(InputEventType::MOTION, ev->getType());
@@ -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());
@@ -268,6 +270,11 @@
EXPECT_EQ(0, keyEvent->getFlags() & VERIFIED_KEY_EVENT_FLAGS);
}
+ void assertNoEvent() {
+ InputEvent* event = consumeEvent(/*timeout=*/100ms);
+ ASSERT_EQ(event, nullptr) << "Expected no event, but got " << *event;
+ }
+
virtual ~InputSurface() {
if (mClientChannel) {
mInputFlinger->removeInputChannel(mClientChannel->getConnectionToken());
@@ -937,9 +944,7 @@
surface->showAt(100, 100);
injectTap(101, 101);
-
- EXPECT_NE(surface->consumeEvent(), nullptr);
- EXPECT_NE(surface->consumeEvent(), nullptr);
+ surface->expectTap(1, 1);
surface->requestFocus();
surface->assertFocusChange(true);
@@ -956,9 +961,7 @@
surface->showAt(100, 100);
injectTap(101, 101);
-
- EXPECT_NE(surface->consumeEvent(), nullptr);
- EXPECT_NE(surface->consumeEvent(), nullptr);
+ surface->expectTap(.5, .5);
surface->requestFocus();
surface->assertFocusChange(true);
@@ -977,12 +980,12 @@
obscuringSurface->mInputInfo.ownerUid = gui::Uid{22222};
obscuringSurface->showAt(100, 100);
injectTap(101, 101);
- EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+ surface->assertNoEvent();
surface->requestFocus();
surface->assertFocusChange(true);
injectKey(AKEYCODE_V);
- EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+ surface->assertNoEvent();
}
TEST_F(InputSurfacesTest, strict_unobscured_input_partially_obscured_window) {
@@ -998,12 +1001,12 @@
injectTap(101, 101);
- EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+ surface->assertNoEvent();
surface->requestFocus();
surface->assertFocusChange(true);
injectKey(AKEYCODE_V);
- EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+ surface->assertNoEvent();
}
TEST_F(InputSurfacesTest, strict_unobscured_input_alpha_window) {
@@ -1020,12 +1023,12 @@
injectTap(101, 101);
- EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+ surface->assertNoEvent();
surface->requestFocus();
surface->assertFocusChange(true);
injectKey(AKEYCODE_V);
- EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+ surface->assertNoEvent();
}
TEST_F(InputSurfacesTest, strict_unobscured_input_cropped_window) {
@@ -1042,12 +1045,12 @@
injectTap(111, 111);
- EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+ surface->assertNoEvent();
surface->requestFocus();
surface->assertFocusChange(true);
injectKey(AKEYCODE_V);
- EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+ surface->assertNoEvent();
}
TEST_F(InputSurfacesTest, ignore_touch_region_with_zero_sized_blast) {
@@ -1071,13 +1074,12 @@
surface->showAt(100, 100);
injectTap(101, 101);
-
- EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+ surface->assertNoEvent();
surface->requestFocus();
surface->assertFocusChange(true);
injectKey(AKEYCODE_V);
- EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+ surface->assertNoEvent();
}
TEST_F(InputSurfacesTest, layer_with_valid_crop_can_be_focused) {
@@ -1112,7 +1114,7 @@
// Does not receive events outside its crop
injectTap(26, 26);
- EXPECT_EQ(containerSurface->consumeEvent(/*timeout=*/100ms), nullptr);
+ containerSurface->assertNoEvent();
}
/**
@@ -1137,7 +1139,7 @@
// Does not receive events outside parent bounds
injectTap(31, 31);
- EXPECT_EQ(containerSurface->consumeEvent(/*timeout=*/100ms), nullptr);
+ containerSurface->assertNoEvent();
}
/**
@@ -1163,7 +1165,7 @@
// Does not receive events outside crop layer bounds
injectTap(21, 21);
injectTap(71, 71);
- EXPECT_EQ(containerSurface->consumeEvent(/*timeout=*/100ms), nullptr);
+ containerSurface->assertNoEvent();
}
TEST_F(InputSurfacesTest, child_container_with_no_input_channel_blocks_parent) {
@@ -1180,7 +1182,7 @@
[&](auto &t, auto &sc) { t.reparent(sc, parent->mSurfaceControl); });
injectTap(101, 101);
- EXPECT_EQ(parent->consumeEvent(/*timeout=*/100ms), nullptr);
+ parent->assertNoEvent();
}
class MultiDisplayTests : public InputSurfacesTest {
@@ -1229,7 +1231,7 @@
// Touches should be dropped if the layer is on an invalid display.
injectTapOnDisplay(101, 101, layerStack.id);
- EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+ surface->assertNoEvent();
// However, we still let the window be focused and receive keys.
surface->requestFocus(layerStack.id);
@@ -1267,12 +1269,12 @@
injectTapOnDisplay(101, 101, layerStack.id);
- EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+ surface->assertNoEvent();
surface->requestFocus(layerStack.id);
surface->assertFocusChange(true);
injectKeyOnDisplay(AKEYCODE_V, layerStack.id);
- EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+ surface->assertNoEvent();
}
TEST_F(MultiDisplayTests, dont_drop_input_for_secure_layer_on_secure_display) {
@@ -1292,8 +1294,7 @@
surface->showAt(100, 100);
injectTapOnDisplay(101, 101, layerStack.id);
- EXPECT_NE(surface->consumeEvent(), nullptr);
- EXPECT_NE(surface->consumeEvent(), nullptr);
+ surface->expectTap(1, 1);
surface->requestFocus(layerStack.id);
surface->assertFocusChange(true);
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/InputConsumer.cpp b/libs/input/InputConsumer.cpp
index 41ecfe3..be2110e 100644
--- a/libs/input/InputConsumer.cpp
+++ b/libs/input/InputConsumer.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <cstdint>
#define LOG_TAG "InputTransport"
#define ATRACE_TAG ATRACE_TAG_INPUT
@@ -194,9 +195,21 @@
InputConsumer::InputConsumer(const std::shared_ptr<InputChannel>& channel,
bool enableTouchResampling)
- : mResampleTouch(enableTouchResampling), mChannel(channel), mMsgDeferred(false) {}
+ : mResampleTouch(enableTouchResampling),
+ mChannel(channel),
+ mProcessingTraceTag(StringPrintf("InputConsumer processing on %s (%p)",
+ mChannel->getName().c_str(), this)),
+ mLifetimeTraceTag(StringPrintf("InputConsumer lifetime on %s (%p)",
+ mChannel->getName().c_str(), this)),
+ mLifetimeTraceCookie(
+ static_cast<int32_t>(reinterpret_cast<std::uintptr_t>(this) & 0xFFFFFFFF)),
+ mMsgDeferred(false) {
+ ATRACE_ASYNC_BEGIN(mLifetimeTraceTag.c_str(), /*cookie=*/mLifetimeTraceCookie);
+}
-InputConsumer::~InputConsumer() {}
+InputConsumer::~InputConsumer() {
+ ATRACE_ASYNC_END(mLifetimeTraceTag.c_str(), /*cookie=*/mLifetimeTraceCookie);
+}
bool InputConsumer::isTouchResamplingEnabled() {
return property_get_bool(PROPERTY_RESAMPLING_ENABLED, true);
@@ -228,7 +241,7 @@
mMsg.header.seq);
// Trace the event processing timeline - event was just read from the socket
- ATRACE_ASYNC_BEGIN("InputConsumer processing", /*cookie=*/mMsg.header.seq);
+ ATRACE_ASYNC_BEGIN(mProcessingTraceTag.c_str(), /*cookie=*/mMsg.header.seq);
}
if (result) {
// Consume the next batched event unless batches are being held for later.
@@ -769,7 +782,7 @@
popConsumeTime(seq);
// Trace the event processing timeline - event was just finished
- ATRACE_ASYNC_END("InputConsumer processing", /*cookie=*/seq);
+ ATRACE_ASYNC_END(mProcessingTraceTag.c_str(), /*cookie=*/seq);
}
return result;
}
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/android/os/InputConfig.aidl b/libs/input/android/os/InputConfig.aidl
index 5d39155..6b97cbb 100644
--- a/libs/input/android/os/InputConfig.aidl
+++ b/libs/input/android/os/InputConfig.aidl
@@ -157,4 +157,12 @@
* like StatusBar and TaskBar.
*/
GLOBAL_STYLUS_BLOCKS_TOUCH = 1 << 17,
+
+ /**
+ * InputConfig used to indicate that this window is sensitive for tracing.
+ * This must be set on windows that use {@link WindowManager.LayoutParams#FLAG_SECURE},
+ * but it may also be set without setting FLAG_SECURE. The tracing configuration will
+ * determine how these sensitive events are eventually traced.
+ */
+ SENSITIVE_FOR_TRACING = 1 << 18,
}
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index e041c51..e161c2a 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -137,3 +137,10 @@
is_fixed_read_only: true
}
+
+flag {
+ name: "enable_multi_device_same_window_stream"
+ namespace: "input"
+ description: "Allow multiple input devices to be active in the same window simultaneously"
+ bug: "330752824"
+}
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 4d70a33..4389d65 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -2532,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
@@ -4383,7 +4391,7 @@
// different pointer ids than we expected based on the previous ACTION_DOWN
// or ACTION_POINTER_DOWN events that caused us to decide to split the pointers
// in this way.
- ALOGW("Dropping split motion event because the pointer count is %d but "
+ ALOGW("Dropping split motion event because the pointer count is %zu but "
"we expected there to be %zu pointers. This probably means we received "
"a broken sequence of pointer ids from the input device: %s",
pointerCoords.size(), pointerIds.count(),
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index 1fec9b7..c2d2b7b 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -95,12 +95,14 @@
return true;
}
- if (!mMotionMementos.empty()) {
- const MotionMemento& lastMemento = mMotionMementos.back();
- if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) &&
- !isStylusEvent(entry.source, entry.pointerProperties)) {
- // We already have a stylus stream, and the new event is not from stylus.
- return false;
+ if (!input_flags::enable_multi_device_same_window_stream()) {
+ if (!mMotionMementos.empty()) {
+ const MotionMemento& lastMemento = mMotionMementos.back();
+ if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) &&
+ !isStylusEvent(entry.source, entry.pointerProperties)) {
+ // We already have a stylus stream, and the new event is not from stylus.
+ return false;
+ }
}
}
@@ -345,24 +347,26 @@
return false;
}
- if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties)) {
- // A stylus is already active.
- if (isStylusEvent(motionEntry.source, motionEntry.pointerProperties) &&
- actionMasked == AMOTION_EVENT_ACTION_DOWN) {
- // If this new event is from a different device, then cancel the old
- // stylus and allow the new stylus to take over, but only if it's going down.
- // Otherwise, they will start to race each other.
- return true;
+ if (!input_flags::enable_multi_device_same_window_stream()) {
+ if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties)) {
+ // A stylus is already active.
+ if (isStylusEvent(motionEntry.source, motionEntry.pointerProperties) &&
+ actionMasked == AMOTION_EVENT_ACTION_DOWN) {
+ // If this new event is from a different device, then cancel the old
+ // stylus and allow the new stylus to take over, but only if it's going down.
+ // Otherwise, they will start to race each other.
+ return true;
+ }
+
+ // Keep the current stylus gesture.
+ return false;
}
- // Keep the current stylus gesture.
- return false;
- }
-
- // Cancel the current gesture if this is a start of a new gesture from a new device.
- if (actionMasked == AMOTION_EVENT_ACTION_DOWN ||
- actionMasked == AMOTION_EVENT_ACTION_HOVER_ENTER) {
- return true;
+ // Cancel the current gesture if this is a start of a new gesture from a new device.
+ if (actionMasked == AMOTION_EVENT_ACTION_DOWN ||
+ actionMasked == AMOTION_EVENT_ACTION_HOVER_ENTER) {
+ return true;
+ }
}
// By default, don't cancel any events.
return false;
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 1d4d11c..415b696 100644
--- a/services/inputflinger/dispatcher/trace/InputTracer.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp
@@ -86,8 +86,17 @@
// This is a global monitor, assume its target is the system.
return {.uid = gui::Uid{AID_SYSTEM}, .isSecureWindow = false};
}
- return {target.windowHandle->getInfo()->ownerUid,
- target.windowHandle->getInfo()->layoutParamsFlags.test(gui::WindowInfo::Flag::SECURE)};
+ 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};
}
} // namespace
diff --git a/services/inputflinger/tests/FakeWindows.cpp b/services/inputflinger/tests/FakeWindows.cpp
index 0ac2f0f..a6955ec 100644
--- a/services/inputflinger/tests/FakeWindows.cpp
+++ b/services/inputflinger/tests/FakeWindows.cpp
@@ -298,16 +298,21 @@
const ::testing::Matcher<MotionEvent>& matcher) {
std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
if (event == nullptr) {
- ADD_FAILURE() << "No event";
+ std::ostringstream matcherDescription;
+ matcher.DescribeTo(&matcherDescription);
+ ADD_FAILURE() << "No event (expected " << matcherDescription.str() << ") on " << mName;
return nullptr;
}
if (event->getType() != InputEventType::MOTION) {
- ADD_FAILURE() << "Instead of motion event, got " << *event;
+ ADD_FAILURE() << "Instead of motion event, got " << *event << " on " << mName;
return nullptr;
}
std::unique_ptr<MotionEvent> motionEvent =
std::unique_ptr<MotionEvent>(static_cast<MotionEvent*>(event.release()));
- EXPECT_THAT(*motionEvent, matcher);
+ 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 4bb64fc..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 {
@@ -132,6 +134,40 @@
return event;
}
+/**
+ * Provide a local override for a flag value. The value is restored when the object of this class
+ * goes out of scope.
+ * This class is not intended to be used directly, because its usage is cumbersome.
+ * Instead, a wrapper macro SCOPED_FLAG_OVERRIDE is provided.
+ */
+class ScopedFlagOverride {
+public:
+ ScopedFlagOverride(std::function<bool()> read, std::function<void(bool)> write, bool value)
+ : mInitialValue(read()), mWriteValue(write) {
+ mWriteValue(value);
+ }
+ ~ScopedFlagOverride() { mWriteValue(mInitialValue); }
+
+private:
+ const bool mInitialValue;
+ std::function<void(bool)> mWriteValue;
+};
+
+typedef bool (*readFlagValueFunction)();
+typedef void (*writeFlagValueFunction)(bool);
+
+/**
+ * Use this macro to locally override a flag value.
+ * Example usage:
+ * SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
+ * Note: this works by creating a local variable in your current scope. Don't call this twice for
+ * the same flag, because the variable names will clash!
+ */
+#define SCOPED_FLAG_OVERRIDE(NAME, VALUE) \
+ readFlagValueFunction read##NAME = com::android::input::flags::NAME; \
+ writeFlagValueFunction write##NAME = com::android::input::flags::NAME; \
+ ScopedFlagOverride override##NAME(read##NAME, write##NAME, (VALUE))
+
} // namespace
// --- InputDispatcherTest ---
@@ -1257,7 +1293,9 @@
* This test reproduces a crash where there is a mismatch between the downTime and eventTime.
* In the buggy implementation, a tap on the right window would cause a crash.
*/
-TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) {
+TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap_legacy) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
+
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> leftWindow =
sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
@@ -1353,6 +1391,99 @@
}
/**
+ * Two windows: a window on the left and a window on the right.
+ * Mouse is hovered from the right window into the left window.
+ * Next, we tap on the left window, where the cursor was last seen.
+ * The second tap is done onto the right window.
+ * The mouse and tap are from two different devices.
+ * We technically don't need to set the downtime / eventtime for these events, but setting these
+ * explicitly helps during debugging.
+ * This test reproduces a crash where there is a mismatch between the downTime and eventTime.
+ * In the buggy implementation, a tap on the right window would cause a crash.
+ */
+TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> leftWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+ leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+ sp<FakeWindowHandle> rightWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+ rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+ mDispatcher->onWindowInfosChanged(
+ {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+ // All times need to start at the current time, otherwise the dispatcher will drop the events as
+ // stale.
+ const nsecs_t baseTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ const int32_t mouseDeviceId = 6;
+ const int32_t touchDeviceId = 4;
+ // Move the cursor from right
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .deviceId(mouseDeviceId)
+ .downTime(baseTime + 10)
+ .eventTime(baseTime + 20)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(100))
+ .build());
+ rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+
+ // .. to the left window
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .deviceId(mouseDeviceId)
+ .downTime(baseTime + 10)
+ .eventTime(baseTime + 30)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(100))
+ .build());
+ rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
+ leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+ // Now tap the left window
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .downTime(baseTime + 40)
+ .eventTime(baseTime + 40)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+ .build());
+ leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+
+ // release tap
+ mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .downTime(baseTime + 40)
+ .eventTime(baseTime + 50)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+ .build());
+ leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP));
+
+ // Tap the window on the right
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .downTime(baseTime + 60)
+ .eventTime(baseTime + 60)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+ .build());
+ rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+
+ // release tap
+ mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .downTime(baseTime + 60)
+ .eventTime(baseTime + 70)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+ .build());
+ rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP));
+
+ // No more events
+ leftWindow->assertNoEvents();
+ rightWindow->assertNoEvents();
+}
+
+/**
* Start hovering in a window. While this hover is still active, make another window appear on top.
* The top, obstructing window has no input channel, so it's not supposed to receive input.
* While the top window is present, the hovering is stopped.
@@ -1500,6 +1631,7 @@
* touch is dropped, because stylus should be preferred over touch.
*/
TEST_F(InputDispatcherMultiDeviceTest, StylusDownBlocksTouchDown) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -1542,11 +1674,60 @@
}
/**
+ * One window. Stylus down on the window. Next, touch from another device goes down. Ensure that
+ * touch is not dropped, because multiple devices are allowed to be active in the same window.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, StylusDownDoesNotBlockTouchDown) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ constexpr int32_t touchDeviceId = 4;
+ constexpr int32_t stylusDeviceId = 2;
+
+ // Stylus down
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+ // Touch down
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+
+ // Touch move
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+ // Stylus move
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+ WithCoords(101, 111)));
+
+ window->assertNoEvents();
+}
+
+/**
* One window and one spy window. Stylus down on the window. Next, touch from another device goes
* down. Ensure that touch is dropped, because stylus should be preferred over touch.
* Similar test as above, but with added SPY window.
*/
TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyBlocksTouchDown) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -1600,10 +1781,74 @@
}
/**
+ * One window and one spy window. Stylus down on the window. Next, touch from another device goes
+ * down. Ensure that touch is not dropped, because multiple devices can be active at the same time.
+ * Similar test as above, but with added SPY window.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyDoesNotBlockTouchDown) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> spyWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+ spyWindow->setFrame(Rect(0, 0, 200, 200));
+ spyWindow->setTrustedOverlay(true);
+ spyWindow->setSpy(true);
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ constexpr int32_t touchDeviceId = 4;
+ constexpr int32_t stylusDeviceId = 2;
+
+ // Stylus down
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+ // Touch down
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+
+ // Touch move
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+ // Subsequent stylus movements are delivered correctly
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+ WithCoords(101, 111)));
+ spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+ WithCoords(101, 111)));
+
+ window->assertNoEvents();
+ spyWindow->assertNoEvents();
+}
+
+/**
* One window. Stylus hover on the window. Next, touch from another device goes down. Ensure that
* touch is dropped, because stylus hover takes precedence.
*/
TEST_F(InputDispatcherMultiDeviceTest, StylusHoverBlocksTouchDown) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -1651,10 +1896,65 @@
}
/**
+ * One window. Stylus hover on the window. Next, touch from another device goes down. Ensure that
+ * touch is not dropped, because stylus hover and touch can be both active at the same time.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDoesNotBlockTouchDown) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ constexpr int32_t touchDeviceId = 4;
+ constexpr int32_t stylusDeviceId = 2;
+
+ // Stylus down on the window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+ .build());
+ window->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
+
+ // Touch down on window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+ .build());
+ // Touch move on window
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+ // Subsequent stylus movements are delivered correctly
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE),
+ WithDeviceId(stylusDeviceId), WithCoords(101, 111)));
+
+ // and subsequent touches continue to work
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+ window->assertNoEvents();
+}
+
+/**
* One window. Touch down on the window. Then, stylus hover on the window from another device.
* Ensure that touch is canceled, because stylus hover should take precedence.
*/
TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusHover) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -1704,11 +2004,66 @@
}
/**
+ * One window. Touch down on the window. Then, stylus hover on the window from another device.
+ * Ensure that touch is not canceled, because stylus hover can be active at the same time as touch.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, TouchIsNotCanceledByStylusHover) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ constexpr int32_t touchDeviceId = 4;
+ constexpr int32_t stylusDeviceId = 2;
+
+ // Touch down on window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+ .build());
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+ // Stylus hover on the window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+ .build());
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+ .build());
+ // Stylus hover movement is received normally
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
+ WithDeviceId(stylusDeviceId), WithCoords(100, 110)));
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE),
+ WithDeviceId(stylusDeviceId), WithCoords(101, 111)));
+
+ // Subsequent touch movements also work
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId),
+ WithCoords(142, 147)));
+
+ window->assertNoEvents();
+}
+
+/**
* One window. Stylus down on the window. Then, stylus from another device goes down. Ensure that
* the latest stylus takes over. That is, old stylus should be canceled and the new stylus should
* become active.
*/
TEST_F(InputDispatcherMultiDeviceTest, LatestStylusWins) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -1756,10 +2111,59 @@
}
/**
+ * One window. Stylus down on the window. Then, stylus from another device goes down. Ensure that
+ * both stylus devices can function simultaneously.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, TwoStylusDevicesActiveAtTheSameTime) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ constexpr int32_t stylusDeviceId1 = 3;
+ constexpr int32_t stylusDeviceId2 = 5;
+
+ // Touch down on window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId1)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(99).y(100))
+ .build());
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId1)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(101))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId1)));
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId1)));
+
+ // Second stylus down
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId2)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(9).y(10))
+ .build());
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId2)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(10).y(11))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId2)));
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId2)));
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId1)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(102))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId1)));
+ window->assertNoEvents();
+}
+
+/**
* One window. Touch down on the window. Then, stylus down on the window from another device.
* Ensure that is canceled, because stylus down should be preferred over touch.
*/
TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusDown) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -1800,13 +2204,65 @@
}
/**
+ * One window. Touch down on the window. Then, stylus down on the window from another device.
+ * Ensure that both touch and stylus are functioning independently.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, TouchIsNotCanceledByStylusDown) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ constexpr int32_t touchDeviceId = 4;
+ constexpr int32_t stylusDeviceId = 2;
+
+ // Touch down on window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+ .build());
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+ // Stylus down on the window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+ // Subsequent stylus movements are delivered correctly
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+ WithCoords(101, 111)));
+
+ // Touch continues to work too
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(148).y(149))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+}
+
+/**
* Two windows: a window on the left and a window on the right.
* Mouse is clicked on the left window and remains down. Touch is touched on the right and remains
* down. Then, on the left window, also place second touch pointer down.
* This test tries to reproduce a crash.
* In the buggy implementation, second pointer down on the left window would cause a crash.
*/
-TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch) {
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch_legacy) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> leftWindow =
sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
@@ -1885,6 +2341,88 @@
/**
* Two windows: a window on the left and a window on the right.
+ * Mouse is clicked on the left window and remains down. Touch is touched on the right and remains
+ * down. Then, on the left window, also place second touch pointer down.
+ * This test tries to reproduce a crash.
+ * In the buggy implementation, second pointer down on the left window would cause a crash.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> leftWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+ leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+ sp<FakeWindowHandle> rightWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+ rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+ mDispatcher->onWindowInfosChanged(
+ {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+ const int32_t touchDeviceId = 4;
+ const int32_t mouseDeviceId = 6;
+
+ // Start hovering over the left window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+ .deviceId(mouseDeviceId)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100))
+ .build());
+ leftWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId)));
+
+ // Mouse down on left window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+ .deviceId(mouseDeviceId)
+ .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100))
+ .build());
+
+ leftWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId)));
+ leftWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId)));
+
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE)
+ .deviceId(mouseDeviceId)
+ .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+ .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100))
+ .build());
+ leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
+
+ // First touch pointer down on right window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+ .build());
+ leftWindow->assertNoEvents();
+
+ rightWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+ // Second touch pointer down on left window
+ mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(100).y(100))
+ .build());
+ // Since this is now a new splittable pointer going down on the left window, and it's coming
+ // from a different device, it will be split and delivered to left window separately.
+ leftWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+ // This MOVE event is not necessary (doesn't carry any new information), but it's there in the
+ // current implementation.
+ const std::map<int32_t, PointF> expectedPointers{{0, PointF{100, 100}}};
+ rightWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_MOVE), WithPointers(expectedPointers)));
+
+ leftWindow->assertNoEvents();
+ rightWindow->assertNoEvents();
+}
+
+/**
+ * Two windows: a window on the left and a window on the right.
* Mouse is hovered on the left window and stylus is hovered on the right window.
*/
TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHover) {
@@ -1944,7 +2482,8 @@
* Stylus down on the left window and remains down. Touch goes down on the right and remains down.
* Check the stream that's received by the spy.
*/
-TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy) {
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy_legacy) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> spyWindow =
@@ -2014,6 +2553,83 @@
}
/**
+ * Three windows: a window on the left and a window on the right.
+ * And a spy window that's positioned above all of them.
+ * Stylus down on the left window and remains down. Touch goes down on the right and remains down.
+ * Check the stream that's received by the spy.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> spyWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+ spyWindow->setFrame(Rect(0, 0, 400, 400));
+ spyWindow->setTrustedOverlay(true);
+ spyWindow->setSpy(true);
+
+ sp<FakeWindowHandle> leftWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+ leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+ sp<FakeWindowHandle> rightWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+
+ rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+ mDispatcher->onWindowInfosChanged(
+ {{*spyWindow->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+ const int32_t stylusDeviceId = 1;
+ const int32_t touchDeviceId = 2;
+
+ // Stylus down on the left window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100))
+ .build());
+ leftWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+ // Touch down on the right window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+ .build());
+ leftWindow->assertNoEvents();
+ rightWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+
+ // Stylus movements continue. They should be delivered to the left window and to the spy window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110))
+ .build());
+ leftWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
+
+ // Further touch MOVE events keep going to the right window and to the spy
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(310).y(110))
+ .build());
+ rightWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+ spyWindow->assertNoEvents();
+ leftWindow->assertNoEvents();
+ rightWindow->assertNoEvents();
+}
+
+/**
* Three windows: a window on the left, a window on the right, and a spy window positioned above
* both.
* Check hover in left window and touch down in the right window.
@@ -2022,6 +2638,7 @@
* respectively.
*/
TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverBlocksTouchWithSpy) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> spyWindow =
@@ -2089,6 +2706,84 @@
}
/**
+ * Three windows: a window on the left, a window on the right, and a spy window positioned above
+ * both.
+ * Check hover in left window and touch down in the right window.
+ * At first, spy should receive hover. Next, spy should receive touch.
+ * At the same time, left and right should be getting independent streams of hovering and touch,
+ * respectively.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverDoesNotBlockTouchWithSpy) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> spyWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+ spyWindow->setFrame(Rect(0, 0, 400, 400));
+ spyWindow->setTrustedOverlay(true);
+ spyWindow->setSpy(true);
+
+ sp<FakeWindowHandle> leftWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+ leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+ sp<FakeWindowHandle> rightWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+ rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+ mDispatcher->onWindowInfosChanged(
+ {{*spyWindow->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+ const int32_t stylusDeviceId = 1;
+ const int32_t touchDeviceId = 2;
+
+ // Stylus hover on the left window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100))
+ .build());
+ leftWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
+
+ // Touch down on the right window.
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+ .build());
+ leftWindow->assertNoEvents();
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+ rightWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+
+ // Stylus movements continue. They should be delivered to the left window and the spy.
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110))
+ .build());
+ leftWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+
+ // Touch movements continue. They should be delivered to the right window and the spy
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(301).y(101))
+ .build());
+ rightWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+ spyWindow->assertNoEvents();
+ leftWindow->assertNoEvents();
+ rightWindow->assertNoEvents();
+}
+
+/**
* On a single window, use two different devices: mouse and touch.
* Touch happens first, with two pointers going down, and then the first pointer leaving.
* Mouse is clicked next, which causes the touch stream to be aborted with ACTION_CANCEL.
@@ -2096,7 +2791,8 @@
* because the mouse is currently down, and a POINTER_DOWN event from the touchscreen does not
* represent a new gesture.
*/
-TEST_F(InputDispatcherMultiDeviceTest, MixedTouchAndMouseWithPointerDown) {
+TEST_F(InputDispatcherMultiDeviceTest, MixedTouchAndMouseWithPointerDown_legacy) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -2169,10 +2865,89 @@
}
/**
+ * On a single window, use two different devices: mouse and touch.
+ * Touch happens first, with two pointers going down, and then the first pointer leaving.
+ * Mouse is clicked next, which should not interfere with the touch stream.
+ * Finally, a second touch pointer goes down again. Ensure the second touch pointer is also
+ * delivered correctly.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, MixedTouchAndMouseWithPointerDown) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 400, 400));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ const int32_t touchDeviceId = 4;
+ const int32_t mouseDeviceId = 6;
+
+ // First touch pointer down
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+ .build());
+ // Second touch pointer down
+ mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100))
+ .build());
+ // First touch pointer lifts. The second one remains down
+ mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100))
+ .build());
+ window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+ window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
+ window->consumeMotionEvent(WithMotionAction(POINTER_0_UP));
+
+ // Mouse down
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+ .deviceId(mouseDeviceId)
+ .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(320).y(100))
+ .build());
+
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId)));
+
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE)
+ .deviceId(mouseDeviceId)
+ .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+ .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(320).y(100))
+ .build());
+ window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
+
+ // Second touch pointer down.
+ mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_0_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(350).y(100))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(POINTER_0_DOWN), WithDeviceId(touchDeviceId),
+ WithPointerCount(2u)));
+
+ // Mouse movements should continue to work
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+ .deviceId(mouseDeviceId)
+ .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(330).y(110))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(mouseDeviceId)));
+
+ window->assertNoEvents();
+}
+
+/**
* Inject a touch down and then send a new event via 'notifyMotion'. Ensure the new event cancels
* the injected event.
*/
-TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent) {
+TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent_legacy) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -2205,6 +2980,40 @@
}
/**
+ * Inject a touch down and then send a new event via 'notifyMotion'. Ensure the new event runs
+ * parallel to the injected event.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 400, 400));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ const int32_t touchDeviceId = 4;
+ // Pretend a test injects an ACTION_DOWN mouse event, but forgets to lift up the touch after
+ // completion.
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(*mDispatcher,
+ MotionEventBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+ .deviceId(ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(50).y(50))
+ .build()));
+ window->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(VIRTUAL_KEYBOARD_ID)));
+
+ // Now a real touch comes. The injected pointer will remain, and the new gesture will also be
+ // allowed through.
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+}
+
+/**
* This test is similar to the test above, but the sequence of injected events is different.
*
* Two windows: a window on the left and a window on the right.
@@ -2218,7 +3027,8 @@
* This test reproduces a crash where there is a mismatch between the downTime and eventTime.
* In the buggy implementation, second finger down on the left window would cause a crash.
*/
-TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch) {
+TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch_legacy) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> leftWindow =
sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
@@ -2290,11 +3100,88 @@
}
/**
+ * This test is similar to the test above, but the sequence of injected events is different.
+ *
+ * Two windows: a window on the left and a window on the right.
+ * Mouse is hovered over the left window.
+ * Next, we tap on the left window, where the cursor was last seen.
+ *
+ * After that, we send one finger down onto the right window, and then a second finger down onto
+ * the left window.
+ * The touch is split, so this last gesture should cause 2 ACTION_DOWN events, one in the right
+ * window (first), and then another on the left window (second).
+ * This test reproduces a crash where there is a mismatch between the downTime and eventTime.
+ * In the buggy implementation, second finger down on the left window would cause a crash.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> leftWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+ leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+ sp<FakeWindowHandle> rightWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+ rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+ mDispatcher->onWindowInfosChanged(
+ {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+ const int32_t mouseDeviceId = 6;
+ const int32_t touchDeviceId = 4;
+ // Hover over the left window. Keep the cursor there.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+ .deviceId(mouseDeviceId)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(50).y(50))
+ .build());
+ leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+
+ // Tap on left window
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+ .build());
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+ .build());
+ leftWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithDeviceId(touchDeviceId)));
+ leftWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithDeviceId(touchDeviceId)));
+
+ // First finger down on right window
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+ .build());
+ rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+
+ // Second finger down on the left window
+ mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(100).y(100))
+ .build());
+ leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+ rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
+
+ // No more events
+ leftWindow->assertNoEvents();
+ rightWindow->assertNoEvents();
+}
+
+/**
* Start hovering with a stylus device, and then tap with a touch device. Ensure no crash occurs.
* While the touch is down, new hover events from the stylus device should be ignored. After the
* touch is gone, stylus hovering should start working again.
*/
TEST_F(InputDispatcherMultiDeviceTest, StylusHoverIgnoresTouchTap) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -2356,6 +3243,61 @@
}
/**
+ * Start hovering with a stylus device, and then tap with a touch device. Ensure no crash occurs.
+ * While the touch is down, hovering from the stylus is not affected. After the touch is gone,
+ * check that the stylus hovering continues to work.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, StylusHoverWithTouchTap) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ const int32_t stylusDeviceId = 5;
+ const int32_t touchDeviceId = 4;
+ // Start hovering with stylus
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
+ .build());
+ window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+ // Finger down on the window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+
+ // Continue hovering with stylus.
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(60).y(60))
+ .build());
+ // Hovers continue to work
+ window->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+
+ // Lift up the finger
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP), WithDeviceId(touchDeviceId)));
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(70).y(70))
+ .build());
+ window->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+ window->assertNoEvents();
+}
+
+/**
* If stylus is down anywhere on the screen, then touches should not be delivered to windows that
* have InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH.
*
@@ -2601,7 +3543,8 @@
* ACTION_DOWN event because that's a new gesture, and pilfering should no longer be active.
* While the mouse is down, new move events from the touch device should be ignored.
*/
-TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) {
+TEST_F(InputDispatcherTest, TouchPilferAndMouseMove_legacy) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> spyWindow =
sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
@@ -2698,6 +3641,114 @@
}
/**
+ * Start hovering with a mouse, and then tap with a touch device. Pilfer the touch stream.
+ * Next, click with the mouse device. Both windows (spy and regular) should receive the new mouse
+ * ACTION_DOWN event because that's a new gesture, and pilfering should no longer be active.
+ * While the mouse is down, new move events from the touch device should continue to work.
+ */
+TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> spyWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+ spyWindow->setFrame(Rect(0, 0, 200, 200));
+ spyWindow->setTrustedOverlay(true);
+ spyWindow->setSpy(true);
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ const int32_t mouseDeviceId = 7;
+ const int32_t touchDeviceId = 4;
+
+ // Hover a bit with mouse first
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+ .deviceId(mouseDeviceId)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100))
+ .build());
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId)));
+ window->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId)));
+
+ // Start touching
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+ .build());
+
+ spyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+ window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(55).y(55))
+ .build());
+ spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+ window->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+
+ // Pilfer the stream
+ EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindow->getToken()));
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId)));
+ // Hover is not pilfered! Only touch.
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(60).y(60))
+ .build());
+ spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+
+ // Mouse down
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+ .deviceId(mouseDeviceId)
+ .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100))
+ .build());
+
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId)));
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId)));
+ window->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId)));
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId)));
+
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE)
+ .deviceId(mouseDeviceId)
+ .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+ .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100))
+ .build());
+ spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
+ window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
+
+ // Mouse move!
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+ .deviceId(mouseDeviceId)
+ .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(110))
+ .build());
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(mouseDeviceId)));
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(mouseDeviceId)));
+
+ // Touch move!
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(65).y(65))
+ .build());
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+ // No more events
+ spyWindow->assertNoEvents();
+ window->assertNoEvents();
+}
+
+/**
* On the display, have a single window, and also an area where there's no window.
* First pointer touches the "no window" area of the screen. Second pointer touches the window.
* Make sure that the window receives the second pointer, and first pointer is simply ignored.
@@ -2910,7 +3961,8 @@
* The clicking of mouse is a new ACTION_DOWN event. Since it's from a different device, the
* currently active gesture should be canceled, and the new one should proceed.
*/
-TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) {
+TEST_F(InputDispatcherTest, TwoPointersDownMouseClick_legacy) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -2964,6 +4016,65 @@
window->assertNoEvents();
}
+/**
+ * Put two fingers down (and don't release them) and click the mouse button.
+ * The clicking of mouse is a new ACTION_DOWN event. Since it's from a different device, the
+ * currently active gesture should not be canceled, and the new one should proceed in parallel.
+ */
+TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 600, 800));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ const int32_t touchDeviceId = 4;
+ const int32_t mouseDeviceId = 6;
+
+ // Two pointers down
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+ .build());
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120))
+ .build());
+ window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+ window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
+
+ // Send a series of mouse events for a mouse click
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+ .deviceId(mouseDeviceId)
+ .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId)));
+
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE)
+ .deviceId(mouseDeviceId)
+ .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+ .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
+ .build());
+ window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
+
+ // Try to send more touch events while the mouse is down. Since it's a continuation of an
+ // already active gesture, it should be sent normally.
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(101).y(101))
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(121).y(121))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+ window->assertNoEvents();
+}
+
TEST_F(InputDispatcherTest, HoverWithSpyWindows) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
@@ -2996,7 +4107,8 @@
spyWindow->assertNoEvents();
}
-TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) {
+TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows_legacy) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> spyWindow =
@@ -3102,6 +4214,102 @@
spyWindow->assertNoEvents();
}
+TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> spyWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+ spyWindow->setFrame(Rect(0, 0, 600, 800));
+ spyWindow->setTrustedOverlay(true);
+ spyWindow->setSpy(true);
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 600, 800));
+
+ mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+ mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ // Send mouse cursor to the window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100))
+ .build());
+
+ // Move mouse cursor
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(110))
+ .build());
+
+ window->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithSource(AINPUT_SOURCE_MOUSE)));
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithSource(AINPUT_SOURCE_MOUSE)));
+ window->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE)));
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE)));
+ // Touch down on the window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(SECOND_DEVICE_ID)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(200))
+ .build());
+ window->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+ // pilfer the motion, retaining the gesture on the spy window.
+ EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindow->getToken()));
+ window->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_CANCEL), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+ // Mouse hover is not pilfered
+
+ // Touch UP on the window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(SECOND_DEVICE_ID)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(200))
+ .build());
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_UP), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+ // Previously, a touch was pilfered. However, that gesture was just finished. Now, we are going
+ // to send a new gesture. It should again go to both windows (spy and the window below), just
+ // like the first gesture did, before pilfering. The window configuration has not changed.
+
+ // One more tap - DOWN
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(SECOND_DEVICE_ID)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(250).y(250))
+ .build());
+ window->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+ // Touch UP on the window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(SECOND_DEVICE_ID)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(250).y(250))
+ .build());
+ window->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_UP), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_UP), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+ // Mouse movement continues normally as well
+ // Move mouse cursor
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(120).y(130))
+ .build());
+ window->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE)));
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE)));
+
+ window->assertNoEvents();
+ spyWindow->assertNoEvents();
+}
+
// This test is different from the test above that HOVER_ENTER and HOVER_EXIT events are injected
// directly in this test.
TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) {
@@ -3227,7 +4435,8 @@
/**
* If mouse is hovering when the touch goes down, the hovering should be stopped via HOVER_EXIT.
*/
-TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) {
+TEST_F(InputDispatcherTest, TouchDownAfterMouseHover_legacy) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -3258,11 +4467,43 @@
}
/**
+ * If mouse is hovering when the touch goes down, the hovering should not be stopped.
+ */
+TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 100, 100));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ const int32_t mouseDeviceId = 7;
+ const int32_t touchDeviceId = 4;
+
+ // Start hovering with the mouse
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+ .deviceId(mouseDeviceId)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(10).y(10))
+ .build());
+ window->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId)));
+
+ // Touch goes down
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+}
+
+/**
* Inject a mouse hover event followed by a tap from touchscreen.
* The tap causes a HOVER_EXIT event to be generated because the current event
* stream's source has been switched.
*/
-TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) {
+TEST_F(InputDispatcherTest, MouseHoverAndTouchTap_legacy) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -3296,6 +4537,45 @@
WithSource(AINPUT_SOURCE_TOUCHSCREEN))));
}
+/**
+ * Send a mouse hover event followed by a tap from touchscreen.
+ * The tap causes a HOVER_EXIT event to be generated because the current event
+ * stream's source has been switched.
+ */
+TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 100, 100));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(50).y(50))
+ .build());
+
+ window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+ WithSource(AINPUT_SOURCE_MOUSE)));
+
+ // Tap on the window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(10).y(10))
+ .build());
+
+ window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+ WithSource(AINPUT_SOURCE_MOUSE)));
+
+ window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(10).y(10))
+ .build());
+
+ window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+}
+
TEST_F(InputDispatcherTest, HoverEnterMoveRemoveWindowsInSecondDisplay) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> windowDefaultDisplay =
@@ -10901,7 +12181,8 @@
* Pilfer from spy window.
* Check that the pilfering only affects the pointers that are actually being received by the spy.
*/
-TEST_F(InputDispatcherPilferPointersTest, MultiDevicePilfer) {
+TEST_F(InputDispatcherPilferPointersTest, MultiDevicePilfer_legacy) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
sp<FakeWindowHandle> spy = createSpy();
spy->setFrame(Rect(0, 0, 200, 200));
sp<FakeWindowHandle> leftWindow = createForeground();
@@ -10959,6 +12240,83 @@
rightWindow->assertNoEvents();
}
+/**
+ * A window on the left and a window on the right. Also, a spy window that's above all of the
+ * windows, and spanning both left and right windows.
+ * Send simultaneous motion streams from two different devices, one to the left window, and another
+ * to the right window.
+ * Pilfer from spy window.
+ * Check that the pilfering affects all of the pointers that are actually being received by the spy.
+ * The spy should receive both the touch and the stylus events after pilfer.
+ */
+TEST_F(InputDispatcherPilferPointersTest, MultiDevicePilfer) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+ sp<FakeWindowHandle> spy = createSpy();
+ spy->setFrame(Rect(0, 0, 200, 200));
+ sp<FakeWindowHandle> leftWindow = createForeground();
+ leftWindow->setFrame(Rect(0, 0, 100, 100));
+
+ sp<FakeWindowHandle> rightWindow = createForeground();
+ rightWindow->setFrame(Rect(100, 0, 200, 100));
+
+ constexpr int32_t stylusDeviceId = 1;
+ constexpr int32_t touchDeviceId = 2;
+
+ mDispatcher->onWindowInfosChanged(
+ {{*spy->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+ // Stylus down on left window and spy
+ mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
+ .build());
+ leftWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+ spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+ // Finger down on right window and spy
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+ .build());
+ rightWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+ spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+
+ // Act: pilfer from spy. Spy is currently receiving touch events.
+ EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
+ leftWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId)));
+ rightWindow->consumeMotionEvent(
+ 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(touchDeviceId)));
+
+ spy->assertNoEvents();
+ leftWindow->assertNoEvents();
+ rightWindow->assertNoEvents();
+}
+
TEST_F(InputDispatcherPilferPointersTest, NoPilferingWithHoveringPointers) {
auto window = createForeground();
auto spy = createSpy();
@@ -11423,7 +12781,8 @@
/*pointerId=*/0));
}
-TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse) {
+TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse_legacy) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
@@ -11493,4 +12852,76 @@
/*pointerId=*/0));
}
+/**
+ * TODO(b/313689709) - correctly support multiple mouse devices, because they should be controlling
+ * the same cursor, and therefore have a shared motion event stream.
+ */
+TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse) {
+ SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
+ ADISPLAY_ID_DEFAULT);
+ left->setFrame(Rect(0, 0, 100, 100));
+ sp<FakeWindowHandle> right = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Right Window", ADISPLAY_ID_DEFAULT);
+ right->setFrame(Rect(100, 0, 200, 100));
+
+ mDispatcher->onWindowInfosChanged({{*left->getInfo(), *right->getInfo()}, {}, 0, 0});
+
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+
+ // Hover move into the window.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(50).y(50))
+ .rawXCursorPosition(50)
+ .rawYCursorPosition(50)
+ .deviceId(DEVICE_ID)
+ .build());
+
+ left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+
+ // Move the mouse with another device
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(51).y(50))
+ .rawXCursorPosition(51)
+ .rawYCursorPosition(50)
+ .deviceId(SECOND_DEVICE_ID)
+ .build());
+ left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+ // TODO(b/313689709): InputDispatcher's touch state is not updated, even though the window gets
+ // a HOVER_EXIT from the first device.
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT,
+ SECOND_DEVICE_ID,
+ /*pointerId=*/0));
+
+ // Move the mouse outside the window. Document the current behavior, where the window does not
+ // receive HOVER_EXIT even though the mouse left the window.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(150).y(50))
+ .rawXCursorPosition(150)
+ .rawYCursorPosition(50)
+ .deviceId(SECOND_DEVICE_ID)
+ .build());
+
+ right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT,
+ SECOND_DEVICE_ID,
+ /*pointerId=*/0));
+}
+
} // namespace android::inputdispatcher
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
index e2d17ee..86bcf20 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
@@ -143,7 +143,7 @@
compositionengine::OutputLayer* getBlurLayer() const;
- bool hasUnsupportedDataspace() const;
+ bool hasKnownColorShift() const;
bool hasProtectedLayers() const;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
index dc3821c..5e3e3d8 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
@@ -74,6 +74,7 @@
BlurRegions = 1u << 18,
HasProtectedContent = 1u << 19,
CachingHint = 1u << 20,
+ DimmingEnabled = 1u << 21,
};
// clang-format on
@@ -248,6 +249,10 @@
ui::Dataspace getDataspace() const { return mOutputDataspace.get(); }
+ hardware::graphics::composer::hal::PixelFormat getPixelFormat() const {
+ return mPixelFormat.get();
+ }
+
float getHdrSdrRatio() const {
return getOutputLayer()->getLayerFE().getCompositionState()->currentHdrSdrRatio;
};
@@ -258,6 +263,8 @@
gui::CachingHint getCachingHint() const { return mCachingHint.get(); }
+ bool isDimmingEnabled() const { return mIsDimmingEnabled.get(); }
+
float getFps() const { return getOutputLayer()->getLayerFE().getCompositionState()->fps; }
void dump(std::string& result) const;
@@ -498,7 +505,10 @@
return std::vector<std::string>{toString(cachingHint)};
}};
- static const constexpr size_t kNumNonUniqueFields = 19;
+ OutputLayerState<bool, LayerStateField::DimmingEnabled> mIsDimmingEnabled{
+ [](auto layer) { return layer->getLayerFE().getCompositionState()->dimmingEnabled; }};
+
+ static const constexpr size_t kNumNonUniqueFields = 20;
std::array<StateInterface*, kNumNonUniqueFields> getNonUniqueFields() {
std::array<const StateInterface*, kNumNonUniqueFields> constFields =
@@ -516,7 +526,7 @@
&mAlpha, &mLayerMetadata, &mVisibleRegion, &mOutputDataspace,
&mPixelFormat, &mColorTransform, &mCompositionType, &mSidebandStream,
&mBuffer, &mSolidColor, &mBackgroundBlurRadius, &mBlurRegions,
- &mFrameNumber, &mIsProtected, &mCachingHint};
+ &mFrameNumber, &mIsProtected, &mCachingHint, &mIsDimmingEnabled};
}
};
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
index 1f53588..ea9442d 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -27,8 +27,7 @@
#include <renderengine/DisplaySettings.h>
#include <renderengine/RenderEngine.h>
#include <ui/DebugUtils.h>
-#include <utils/Trace.h>
-
+#include <ui/HdrRenderTypeUtils.h>
#include <utils/Trace.h>
namespace android::compositionengine::impl::planner {
@@ -306,7 +305,7 @@
return false;
}
- if (hasUnsupportedDataspace()) {
+ if (hasKnownColorShift()) {
return false;
}
@@ -366,12 +365,21 @@
return mBlurLayer ? mBlurLayer->getOutputLayer() : nullptr;
}
-bool CachedSet::hasUnsupportedDataspace() const {
+bool CachedSet::hasKnownColorShift() const {
return std::any_of(mLayers.cbegin(), mLayers.cend(), [](const Layer& layer) {
auto dataspace = layer.getState()->getDataspace();
- const auto transfer = static_cast<ui::Dataspace>(dataspace & ui::Dataspace::TRANSFER_MASK);
- if (transfer == ui::Dataspace::TRANSFER_ST2084 || transfer == ui::Dataspace::TRANSFER_HLG) {
- // Skip HDR.
+
+ // Layers are never dimmed when rendering a cached set, meaning that we may ask HWC to
+ // dim a cached set. But this means that we can never cache any HDR layers so that we
+ // don't accidentally dim those layers.
+ const auto hdrType = getHdrRenderType(dataspace, layer.getState()->getPixelFormat(),
+ layer.getState()->getHdrSdrRatio());
+ if (hdrType != HdrRenderType::SDR) {
+ return true;
+ }
+
+ // Layers that have dimming disabled pretend that they're HDR.
+ if (!layer.getState()->isDimmingEnabled()) {
return true;
}
@@ -380,10 +388,6 @@
// to avoid flickering/color differences.
return true;
}
- // TODO(b/274804887): temp fix of overdimming issue, skip caching if hsdr/sdr ratio > 1.01f
- if (layer.getState()->getHdrSdrRatio() > 1.01f) {
- return true;
- }
return false;
});
}
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
index 0a5c43a..4bafed2 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -439,7 +439,7 @@
if (!layerDeniedFromCaching && layerIsInactive &&
(firstLayer || runHasFirstLayer || !layerHasBlur) &&
- !currentSet->hasUnsupportedDataspace()) {
+ !currentSet->hasKnownColorShift()) {
if (isPartOfRun) {
builder.increment();
} else {
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index d2eff75..54ee0ef 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -1339,6 +1339,55 @@
EXPECT_EQ(nullptr, overrideBuffer3);
}
+TEST_F(FlattenerTest, flattenLayers_skipsLayersDisablingDimming) {
+ auto& layerState1 = mTestLayers[0]->layerState;
+ const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer;
+
+ auto& layerState2 = mTestLayers[1]->layerState;
+ const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer;
+
+ // The third layer disables dimming, which means it should not be cached
+ auto& layerState3 = mTestLayers[2]->layerState;
+ const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer;
+ mTestLayers[2]->layerFECompositionState.dimmingEnabled = false;
+ mTestLayers[2]->layerState->update(&mTestLayers[2]->outputLayer);
+
+ const std::vector<const LayerState*> layers = {
+ layerState1.get(),
+ layerState2.get(),
+ layerState3.get(),
+ };
+
+ initializeFlattener(layers);
+
+ mTime += 200ms;
+ initializeOverrideBuffer(layers);
+ EXPECT_EQ(getNonBufferHash(layers),
+ mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+
+ // This will render a CachedSet.
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
+ mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
+
+ // We've rendered a CachedSet, but we haven't merged it in.
+ EXPECT_EQ(nullptr, overrideBuffer1);
+ EXPECT_EQ(nullptr, overrideBuffer2);
+ EXPECT_EQ(nullptr, overrideBuffer3);
+
+ // This time we merge the CachedSet in, so we have a new hash, and we should
+ // only have two sets.
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
+ initializeOverrideBuffer(layers);
+ EXPECT_NE(getNonBufferHash(layers),
+ mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+ mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
+
+ EXPECT_NE(nullptr, overrideBuffer1);
+ EXPECT_EQ(overrideBuffer1, overrideBuffer2);
+ EXPECT_EQ(nullptr, overrideBuffer3);
+}
+
TEST_F(FlattenerTest, flattenLayers_skipsColorLayers) {
auto& layerState1 = mTestLayers[0]->layerState;
const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer;
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
index 044917e..39fce2b 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
@@ -304,6 +304,16 @@
EXPECT_EQ(Composition::CLIENT, mLayerState->getCompositionType());
}
+TEST_F(LayerStateTest, getHdrSdrRatio) {
+ OutputLayerCompositionState outputLayerCompositionState;
+ LayerFECompositionState layerFECompositionState;
+ layerFECompositionState.currentHdrSdrRatio = 2.f;
+ setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
+ layerFECompositionState);
+ mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+ EXPECT_EQ(2.f, mLayerState->getHdrSdrRatio());
+}
+
TEST_F(LayerStateTest, updateCompositionType) {
OutputLayerCompositionState outputLayerCompositionState;
LayerFECompositionState layerFECompositionState;
@@ -1033,6 +1043,47 @@
EXPECT_TRUE(otherLayerState->compare(*mLayerState));
}
+TEST_F(LayerStateTest, updateDimmingEnabled) {
+ OutputLayerCompositionState outputLayerCompositionState;
+ LayerFECompositionState layerFECompositionState;
+ layerFECompositionState.dimmingEnabled = true;
+ setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
+ layerFECompositionState);
+ mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+ EXPECT_TRUE(mLayerState->isDimmingEnabled());
+
+ mock::OutputLayer newOutputLayer;
+ sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
+ LayerFECompositionState layerFECompositionStateTwo;
+ layerFECompositionStateTwo.dimmingEnabled = false;
+ setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
+ layerFECompositionStateTwo);
+ ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+ EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::DimmingEnabled), updates);
+ EXPECT_FALSE(mLayerState->isDimmingEnabled());
+}
+
+TEST_F(LayerStateTest, compareDimmingEnabled) {
+ OutputLayerCompositionState outputLayerCompositionState;
+ LayerFECompositionState layerFECompositionState;
+ layerFECompositionState.dimmingEnabled = true;
+ setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
+ layerFECompositionState);
+ mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+ mock::OutputLayer newOutputLayer;
+ sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
+ LayerFECompositionState layerFECompositionStateTwo;
+ layerFECompositionStateTwo.dimmingEnabled = false;
+ setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
+ layerFECompositionStateTwo);
+ auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
+
+ verifyNonUniqueDifferingFields(*mLayerState, *otherLayerState, LayerStateField::DimmingEnabled);
+
+ EXPECT_TRUE(mLayerState->compare(*otherLayerState));
+ EXPECT_TRUE(otherLayerState->compare(*mLayerState));
+}
+
TEST_F(LayerStateTest, dumpDoesNotCrash) {
OutputLayerCompositionState outputLayerCompositionState;
LayerFECompositionState layerFECompositionState;
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 62f8fb1..45ab7dd 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -560,10 +560,8 @@
return DesiredModeAction::InitiateRenderRateSwitch;
}
- // Set the render frame rate to the active physical refresh rate to schedule the next
- // frame as soon as possible.
setActiveMode(activeMode.modePtr->getId(), activeMode.modePtr->getVsyncRate(),
- activeMode.modePtr->getVsyncRate());
+ activeMode.modePtr->getPeakFps());
// Initiate a mode change.
mDesiredModeOpt = std::move(desiredMode);
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index 0966fe0..7daeefe 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -1028,6 +1028,8 @@
const LayerSnapshot& parentSnapshot,
const LayerHierarchy::TraversalPath& path,
const Args& args) {
+ using InputConfig = gui::WindowInfo::InputConfig;
+
if (requested.windowInfoHandle) {
snapshot.inputInfo = *requested.windowInfoHandle->getInfo();
} else {
@@ -1056,6 +1058,11 @@
snapshot.dropInputMode = gui::DropInputMode::NONE;
}
+ if (snapshot.isSecure ||
+ parentSnapshot.inputInfo.inputConfig.test(InputConfig::SENSITIVE_FOR_TRACING)) {
+ snapshot.inputInfo.inputConfig |= InputConfig::SENSITIVE_FOR_TRACING;
+ }
+
updateVisibility(snapshot, snapshot.isVisible);
if (!needsInputInfo(snapshot, requested)) {
return;
@@ -1068,14 +1075,14 @@
auto displayInfo = displayInfoOpt.value_or(sDefaultInfo);
if (!requested.windowInfoHandle) {
- snapshot.inputInfo.inputConfig = gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL;
+ snapshot.inputInfo.inputConfig = InputConfig::NO_INPUT_CHANNEL;
}
fillInputFrameInfo(snapshot.inputInfo, displayInfo.transform, snapshot);
if (noValidDisplay) {
// Do not let the window receive touches if it is not associated with a valid display
// transform. We still allow the window to receive keys and prevent ANRs.
- snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::NOT_TOUCHABLE;
+ snapshot.inputInfo.inputConfig |= InputConfig::NOT_TOUCHABLE;
}
snapshot.inputInfo.alpha = snapshot.color.a;
@@ -1085,7 +1092,7 @@
// If the window will be blacked out on a display because the display does not have the secure
// flag and the layer has the secure flag set, then drop input.
if (!displayInfo.isSecure && snapshot.isSecure) {
- snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT;
+ snapshot.inputInfo.inputConfig |= InputConfig::DROP_INPUT;
}
if (requested.touchCropId != UNASSIGNED_LAYER_ID || path.isClone()) {
@@ -1102,7 +1109,7 @@
// Inherit the trusted state from the parent hierarchy, but don't clobber the trusted state
// if it was set by WM for a known system overlay
if (snapshot.isTrustedOverlay) {
- snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::TRUSTED_OVERLAY;
+ snapshot.inputInfo.inputConfig |= InputConfig::TRUSTED_OVERLAY;
}
snapshot.inputInfo.contentSize = snapshot.croppedBufferSize.getSize();
@@ -1110,10 +1117,10 @@
// If the layer is a clone, we need to crop the input region to cloned root to prevent
// touches from going outside the cloned area.
if (path.isClone()) {
- snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::CLONE;
+ snapshot.inputInfo.inputConfig |= InputConfig::CLONE;
// Cloned layers shouldn't handle watch outside since their z order is not determined by
// WM or the client.
- snapshot.inputInfo.inputConfig.clear(gui::WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH);
+ snapshot.inputInfo.inputConfig.clear(InputConfig::WATCH_OUTSIDE_TOUCH);
}
}
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 867f3af..028bd19 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -585,11 +585,13 @@
return false;
}
- static constexpr uint64_t deniedFlags = layer_state_t::eProducerDisconnect |
- layer_state_t::eLayerChanged | layer_state_t::eRelativeLayerChanged |
- layer_state_t::eTransparentRegionChanged | layer_state_t::eFlagsChanged |
- layer_state_t::eBlurRegionsChanged | layer_state_t::eLayerStackChanged |
- layer_state_t::eAutoRefreshChanged | layer_state_t::eReparent;
+ const uint64_t deniedFlags = layer_state_t::eProducerDisconnect | layer_state_t::eLayerChanged |
+ layer_state_t::eRelativeLayerChanged | layer_state_t::eTransparentRegionChanged |
+ layer_state_t::eFlagsChanged | layer_state_t::eBlurRegionsChanged |
+ layer_state_t::eLayerStackChanged | layer_state_t::eReparent |
+ (FlagManager::getInstance().latch_unsignaled_with_auto_refresh_changed()
+ ? 0
+ : layer_state_t::eAutoRefreshChanged);
if (s.what & deniedFlags) {
ATRACE_FORMAT_INSTANT("%s: false [has denied flags 0x%" PRIx64 "]", __func__,
s.what & deniedFlags);
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 80eee15..073bad3 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -3792,8 +3792,10 @@
const uint64_t deniedFlags = layer_state_t::eProducerDisconnect | layer_state_t::eLayerChanged |
layer_state_t::eRelativeLayerChanged | layer_state_t::eTransparentRegionChanged |
layer_state_t::eFlagsChanged | layer_state_t::eBlurRegionsChanged |
- layer_state_t::eLayerStackChanged | layer_state_t::eAutoRefreshChanged |
- layer_state_t::eReparent;
+ layer_state_t::eLayerStackChanged | layer_state_t::eReparent |
+ (FlagManager::getInstance().latch_unsignaled_with_auto_refresh_changed()
+ ? 0
+ : layer_state_t::eAutoRefreshChanged);
if ((s.what & requiredFlags) != requiredFlags) {
ATRACE_FORMAT_INSTANT("%s: false [missing required flags 0x%" PRIx64 "]", __func__,
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/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index d9334d6..12043d4 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -140,6 +140,8 @@
DUMP_READ_ONLY_FLAG(ce_fence_promise);
DUMP_READ_ONLY_FLAG(idle_screen_refresh_rate_timeout);
DUMP_READ_ONLY_FLAG(graphite_renderengine);
+ DUMP_READ_ONLY_FLAG(latch_unsignaled_with_auto_refresh_changed);
+
#undef DUMP_READ_ONLY_FLAG
#undef DUMP_SERVER_FLAG
#undef DUMP_FLAG_INTERVAL
@@ -229,6 +231,7 @@
FLAG_MANAGER_READ_ONLY_FLAG(protected_if_client, "")
FLAG_MANAGER_READ_ONLY_FLAG(ce_fence_promise, "");
FLAG_MANAGER_READ_ONLY_FLAG(graphite_renderengine, "debug.renderengine.graphite")
+FLAG_MANAGER_READ_ONLY_FLAG(latch_unsignaled_with_auto_refresh_changed, "");
/// Trunk stable server flags ///
FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "")
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index 819e587..0239eb0 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -78,6 +78,7 @@
bool ce_fence_promise() const;
bool idle_screen_refresh_rate_timeout() const;
bool graphite_renderengine() const;
+ bool latch_unsignaled_with_auto_refresh_changed() const;
protected:
// overridden for unit tests
diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
index a8fd6b7..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"
@@ -39,4 +32,15 @@
}
} # frame_rate_category_mrr
+flag {
+ name: "latch_unsignaled_with_auto_refresh_changed"
+ namespace: "core_graphics"
+ description: "Ignore eAutoRefreshChanged with latch unsignaled"
+ bug: "331513837"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+} # latch_unsignaled_with_auto_refresh_changed
+
# IMPORTANT - please keep alphabetize to reduce merge conflicts
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
index 67e6249..e8e7667 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
@@ -281,6 +281,24 @@
mLifecycleManager.applyTransactions(transactions);
}
+ void setInputInfo(uint32_t id, std::function<void(gui::WindowInfo&)> configureInput) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eInputInfoChanged;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().state.windowInfoHandle =
+ sp<gui::WindowInfoHandle>::make();
+ auto inputInfo = transactions.back().states.front().state.windowInfoHandle->editInfo();
+ if (!inputInfo->token) {
+ inputInfo->token = sp<BBinder>::make();
+ }
+ configureInput(*inputInfo);
+
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
void setTouchableRegionCrop(uint32_t id, Region region, uint32_t touchCropId,
bool replaceTouchableRegionWithCrop) {
std::vector<TransactionState> transactions;
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 94989aa..ae9a89c 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -1198,6 +1198,42 @@
EXPECT_TRUE(getSnapshot(11)->isSecure);
}
+TEST_F(LayerSnapshotTest, setSensitiveForTracingConfigForSecureLayers) {
+ setFlags(11, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure);
+
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+ EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test(
+ gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING));
+ EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test(
+ gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING));
+ EXPECT_FALSE(getSnapshot(1)->inputInfo.inputConfig.test(
+ gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING));
+ EXPECT_FALSE(getSnapshot(12)->inputInfo.inputConfig.test(
+ gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING));
+ EXPECT_FALSE(getSnapshot(2)->inputInfo.inputConfig.test(
+ gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING));
+}
+
+TEST_F(LayerSnapshotTest, setSensitiveForTracingFromInputWindowHandle) {
+ setInputInfo(11, [](auto& inputInfo) {
+ inputInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING;
+ });
+
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+ EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test(
+ gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING));
+ EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test(
+ gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING));
+ EXPECT_FALSE(getSnapshot(1)->inputInfo.inputConfig.test(
+ gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING));
+ EXPECT_FALSE(getSnapshot(12)->inputInfo.inputConfig.test(
+ gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING));
+ EXPECT_FALSE(getSnapshot(2)->inputInfo.inputConfig.test(
+ gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING));
+}
+
// b/314350323
TEST_F(LayerSnapshotTest, propagateDropInputMode) {
setDropInputMode(1, gui::DropInputMode::ALL);
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index 1f2a1ed..7fb9247 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -17,6 +17,7 @@
#undef LOG_TAG
#define LOG_TAG "TransactionApplicationTest"
+#include <common/test/FlagUtils.h>
#include <compositionengine/Display.h>
#include <compositionengine/mock/DisplaySurface.h>
#include <gmock/gmock.h>
@@ -34,8 +35,11 @@
#include "TestableSurfaceFlinger.h"
#include "TransactionState.h"
+#include <com_android_graphics_surfaceflinger_flags.h>
+
namespace android {
+using namespace com::android::graphics::surfaceflinger;
using testing::_;
using testing::Return;
@@ -498,6 +502,44 @@
setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending);
}
+TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_AutoRefreshChanged) {
+ SET_FLAG_FOR_TEST(flags::latch_unsignaled_with_auto_refresh_changed, false);
+ const sp<IBinder> kApplyToken =
+ IInterface::asBinder(TransactionCompletedListener::getIInstance());
+ const auto kLayerId = 1;
+ const auto kExpectedTransactionsPending = 1u;
+
+ const auto unsignaledTransaction =
+ createTransactionInfo(kApplyToken,
+ {
+ createComposerState(kLayerId,
+ fence(Fence::Status::Unsignaled),
+ layer_state_t::eAutoRefreshChanged |
+ layer_state_t::
+ eBufferChanged),
+ });
+ setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_RemovesUnSignaledInTheQueue_AutoRefreshChanged) {
+ SET_FLAG_FOR_TEST(flags::latch_unsignaled_with_auto_refresh_changed, true);
+ const sp<IBinder> kApplyToken =
+ IInterface::asBinder(TransactionCompletedListener::getIInstance());
+ const auto kLayerId = 1;
+ const auto kExpectedTransactionsPending = 0u;
+
+ const auto unsignaledTransaction =
+ createTransactionInfo(kApplyToken,
+ {
+ createComposerState(kLayerId,
+ fence(Fence::Status::Unsignaled),
+ layer_state_t::eAutoRefreshChanged |
+ layer_state_t::
+ eBufferChanged),
+ });
+ setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending);
+}
+
TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_NonBufferChangeClubed) {
const sp<IBinder> kApplyToken =
IInterface::asBinder(TransactionCompletedListener::getIInstance());
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