Re-organize and export libinput_rust as a rust library
We export some definitions from libinput_rust as the "input" crate so
they can be used directly by other rust libraries.
Bug: 278783893
Test: atest libinput_rust_test
Change-Id: Icba79b67103f710d8bb1cb8892016bbd4e3e6ff9
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 30e4afe..f9f3d47 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -109,62 +109,6 @@
],
}
-genrule {
- name: "libinput_cxx_bridge_code",
- tools: ["cxxbridge"],
- cmd: "$(location cxxbridge) $(in) >> $(out)",
- srcs: ["input_verifier.rs"],
- out: ["inputverifier_generated.cpp"],
-}
-
-genrule {
- name: "libinput_cxx_bridge_header",
- tools: ["cxxbridge"],
- cmd: "$(location cxxbridge) $(in) --header >> $(out)",
- srcs: ["input_verifier.rs"],
- out: ["input_verifier.rs.h"],
-}
-
-rust_defaults {
- name: "libinput_rust_defaults",
- srcs: ["input_verifier.rs"],
- host_supported: true,
- rustlibs: [
- "libbitflags",
- "libcxx",
- "libinput_bindgen",
- "liblogger",
- "liblog_rust",
- "inputconstants-rust",
- ],
-
- shared_libs: [
- "libbase",
- "liblog",
- ],
-}
-
-rust_ffi_static {
- name: "libinput_rust",
- crate_name: "input",
- defaults: ["libinput_rust_defaults"],
-}
-
-rust_test {
- name: "libinput_rust_test",
- defaults: ["libinput_rust_defaults"],
- whole_static_libs: [
- "libinput_from_rust_to_cpp",
- ],
- test_options: {
- unit_test: true,
- },
- test_suites: ["device_tests"],
- sanitize: {
- hwaddress: true,
- },
-}
-
cc_library {
name: "libinput",
cpp_std: "c++20",
@@ -176,7 +120,6 @@
"-Wno-unused-parameter",
],
srcs: [
- "FromRustToCpp.cpp",
"Input.cpp",
"InputDevice.cpp",
"InputEventLabels.cpp",
@@ -208,8 +151,6 @@
"toolbox_input_labels",
],
- generated_sources: ["libinput_cxx_bridge_code"],
-
shared_libs: [
"libbase",
"libcutils",
@@ -235,7 +176,7 @@
],
whole_static_libs: [
- "libinput_rust",
+ "libinput_rust_ffi",
],
export_static_lib_headers: [
diff --git a/libs/input/InputVerifier.cpp b/libs/input/InputVerifier.cpp
index 32b4ca0..b0546a5 100644
--- a/libs/input/InputVerifier.cpp
+++ b/libs/input/InputVerifier.cpp
@@ -18,7 +18,7 @@
#include <android-base/logging.h>
#include <input/InputVerifier.h>
-#include "input_verifier.rs.h"
+#include "input_cxx_bridge.rs.h"
using android::base::Error;
using android::base::Result;
diff --git a/libs/input/input_verifier.rs b/libs/input/input_verifier.rs
deleted file mode 100644
index 2e05a63..0000000
--- a/libs/input/input_verifier.rs
+++ /dev/null
@@ -1,421 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//! Validate the incoming motion stream.
-//! This class is not thread-safe.
-//! State is stored in the "InputVerifier" object
-//! that can be created via the 'create' method.
-//! Usage:
-//! Box<InputVerifier> verifier = create("inputChannel name");
-//! result = process_movement(verifier, ...);
-//! if (result) {
-//! crash(result.error_message());
-//! }
-
-use std::collections::HashMap;
-use std::collections::HashSet;
-
-use bitflags::bitflags;
-use log::info;
-
-#[cxx::bridge(namespace = "android::input")]
-mod ffi {
- #[namespace = "android"]
- unsafe extern "C++" {
- include!("ffi/FromRustToCpp.h");
- fn shouldLog(tag: &str) -> bool;
- }
- #[namespace = "android::input::verifier"]
- extern "Rust" {
- type InputVerifier;
-
- fn create(name: String) -> Box<InputVerifier>;
- fn process_movement(
- verifier: &mut InputVerifier,
- device_id: i32,
- action: u32,
- pointer_properties: &[RustPointerProperties],
- flags: i32,
- ) -> String;
- }
-
- pub struct RustPointerProperties {
- id: i32,
- }
-}
-
-use crate::ffi::shouldLog;
-use crate::ffi::RustPointerProperties;
-
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
-struct DeviceId(i32);
-
-fn process_movement(
- verifier: &mut InputVerifier,
- device_id: i32,
- action: u32,
- pointer_properties: &[RustPointerProperties],
- flags: i32,
-) -> String {
- let result = verifier.process_movement(
- DeviceId(device_id),
- action,
- pointer_properties,
- Flags::from_bits(flags).unwrap(),
- );
- match result {
- Ok(()) => "".to_string(),
- Err(e) => e,
- }
-}
-
-fn create(name: String) -> Box<InputVerifier> {
- Box::new(InputVerifier::new(&name))
-}
-
-#[repr(u32)]
-enum MotionAction {
- Down = input_bindgen::AMOTION_EVENT_ACTION_DOWN,
- Up = input_bindgen::AMOTION_EVENT_ACTION_UP,
- Move = input_bindgen::AMOTION_EVENT_ACTION_MOVE,
- Cancel = input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
- Outside = input_bindgen::AMOTION_EVENT_ACTION_OUTSIDE,
- PointerDown { action_index: usize } = input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN,
- PointerUp { action_index: usize } = input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP,
- HoverEnter = input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
- HoverMove = input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
- HoverExit = input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT,
- Scroll = input_bindgen::AMOTION_EVENT_ACTION_SCROLL,
- ButtonPress = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
- ButtonRelease = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE,
-}
-
-fn get_action_index(action: u32) -> usize {
- let index = (action & input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
- >> input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
- index.try_into().unwrap()
-}
-
-impl From<u32> for MotionAction {
- fn from(action: u32) -> Self {
- let action_masked = action & input_bindgen::AMOTION_EVENT_ACTION_MASK;
- let action_index = get_action_index(action);
- match action_masked {
- input_bindgen::AMOTION_EVENT_ACTION_DOWN => MotionAction::Down,
- input_bindgen::AMOTION_EVENT_ACTION_UP => MotionAction::Up,
- input_bindgen::AMOTION_EVENT_ACTION_MOVE => MotionAction::Move,
- input_bindgen::AMOTION_EVENT_ACTION_CANCEL => MotionAction::Cancel,
- input_bindgen::AMOTION_EVENT_ACTION_OUTSIDE => MotionAction::Outside,
- input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN => {
- MotionAction::PointerDown { action_index }
- }
- input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP => {
- MotionAction::PointerUp { action_index }
- }
- input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER => MotionAction::HoverEnter,
- input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE => MotionAction::HoverMove,
- input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT => MotionAction::HoverExit,
- input_bindgen::AMOTION_EVENT_ACTION_SCROLL => MotionAction::Scroll,
- input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS => MotionAction::ButtonPress,
- input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE => MotionAction::ButtonRelease,
- _ => panic!("Unknown action: {}", action),
- }
- }
-}
-
-bitflags! {
- struct Flags: i32 {
- const CANCELED = input_bindgen::AMOTION_EVENT_FLAG_CANCELED;
- }
-}
-
-fn motion_action_to_string(action: u32) -> String {
- match action.into() {
- MotionAction::Down => "DOWN".to_string(),
- MotionAction::Up => "UP".to_string(),
- MotionAction::Move => "MOVE".to_string(),
- MotionAction::Cancel => "CANCEL".to_string(),
- MotionAction::Outside => "OUTSIDE".to_string(),
- MotionAction::PointerDown { action_index } => {
- format!("POINTER_DOWN({})", action_index)
- }
- MotionAction::PointerUp { action_index } => {
- format!("POINTER_UP({})", action_index)
- }
- MotionAction::HoverMove => "HOVER_MOVE".to_string(),
- MotionAction::Scroll => "SCROLL".to_string(),
- MotionAction::HoverEnter => "HOVER_ENTER".to_string(),
- MotionAction::HoverExit => "HOVER_EXIT".to_string(),
- MotionAction::ButtonPress => "BUTTON_PRESS".to_string(),
- MotionAction::ButtonRelease => "BUTTON_RELEASE".to_string(),
- }
-}
-
-/**
- * Log all of the movements that are sent to this verifier. Helps to identify the streams that lead
- * to inconsistent events.
- * Enable this via "adb shell setprop log.tag.InputVerifierLogEvents DEBUG"
- */
-fn log_events() -> bool {
- shouldLog("InputVerifierLogEvents")
-}
-
-struct InputVerifier {
- name: String,
- touching_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>,
-}
-
-impl InputVerifier {
- fn new(name: &str) -> Self {
- logger::init(
- logger::Config::default()
- .with_tag_on_device("InputVerifier")
- .with_min_level(log::Level::Trace),
- );
- Self { name: name.to_owned(), touching_pointer_ids_by_device: HashMap::new() }
- }
-
- fn process_movement(
- &mut self,
- device_id: DeviceId,
- action: u32,
- pointer_properties: &[RustPointerProperties],
- flags: Flags,
- ) -> Result<(), String> {
- if log_events() {
- info!(
- "Processing {} for device {:?} ({} pointer{}) on {}",
- motion_action_to_string(action),
- device_id,
- pointer_properties.len(),
- if pointer_properties.len() == 1 { "" } else { "s" },
- self.name
- );
- }
-
- match action.into() {
- MotionAction::Down => {
- let it = self
- .touching_pointer_ids_by_device
- .entry(device_id)
- .or_insert_with(HashSet::new);
- let pointer_id = pointer_properties[0].id;
- if it.contains(&pointer_id) {
- return Err(format!(
- "{}: Invalid DOWN event - pointers already down for device {:?}: {:?}",
- self.name, device_id, it
- ));
- }
- it.insert(pointer_id);
- }
- MotionAction::PointerDown { action_index } => {
- if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
- return Err(format!(
- "{}: Received POINTER_DOWN but no pointers are currently down \
- for device {:?}",
- self.name, device_id
- ));
- }
- let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
- let pointer_id = pointer_properties[action_index].id;
- if it.contains(&pointer_id) {
- return Err(format!(
- "{}: Pointer with id={} not found in the properties",
- self.name, pointer_id
- ));
- }
- it.insert(pointer_id);
- }
- MotionAction::Move => {
- if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
- return Err(format!(
- "{}: ACTION_MOVE touching pointers don't match",
- self.name
- ));
- }
- }
- MotionAction::PointerUp { action_index } => {
- if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
- return Err(format!(
- "{}: Received POINTER_UP but no pointers are currently down for device \
- {:?}",
- self.name, device_id
- ));
- }
- let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
- let pointer_id = pointer_properties[action_index].id;
- it.remove(&pointer_id);
- }
- MotionAction::Up => {
- if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
- return Err(format!(
- "{} Received ACTION_UP but no pointers are currently down for device {:?}",
- self.name, device_id
- ));
- }
- let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
- if it.len() != 1 {
- return Err(format!(
- "{}: Got ACTION_UP, but we have pointers: {:?} for device {:?}",
- self.name, it, device_id
- ));
- }
- let pointer_id = pointer_properties[0].id;
- if !it.contains(&pointer_id) {
- return Err(format!(
- "{}: Got ACTION_UP, but pointerId {} is not touching. Touching pointers:\
- {:?} for device {:?}",
- self.name, pointer_id, it, device_id
- ));
- }
- it.clear();
- }
- MotionAction::Cancel => {
- if flags.contains(Flags::CANCELED) {
- return Err(format!(
- "{}: For ACTION_CANCEL, must set FLAG_CANCELED",
- self.name
- ));
- }
- if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
- return Err(format!(
- "{}: Got ACTION_CANCEL, but the pointers don't match. \
- Existing pointers: {:?}",
- self.name, self.touching_pointer_ids_by_device
- ));
- }
- self.touching_pointer_ids_by_device.remove(&device_id);
- }
- _ => return Ok(()),
- }
- Ok(())
- }
-
- fn ensure_touching_pointers_match(
- &self,
- device_id: DeviceId,
- pointer_properties: &[RustPointerProperties],
- ) -> bool {
- let Some(pointers) = self.touching_pointer_ids_by_device.get(&device_id) else {
- return false;
- };
-
- for pointer_property in pointer_properties.iter() {
- let pointer_id = pointer_property.id;
- if !pointers.contains(&pointer_id) {
- return false;
- }
- }
- true
- }
-}
-
-#[cfg(test)]
-mod tests {
- use crate::DeviceId;
- use crate::Flags;
- use crate::InputVerifier;
- use crate::RustPointerProperties;
- #[test]
- fn single_pointer_stream() {
- let mut verifier = InputVerifier::new("Test");
- let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
- assert!(verifier
- .process_movement(
- DeviceId(1),
- input_bindgen::AMOTION_EVENT_ACTION_DOWN,
- &pointer_properties,
- Flags::empty(),
- )
- .is_ok());
- assert!(verifier
- .process_movement(
- DeviceId(1),
- input_bindgen::AMOTION_EVENT_ACTION_MOVE,
- &pointer_properties,
- Flags::empty(),
- )
- .is_ok());
- assert!(verifier
- .process_movement(
- DeviceId(1),
- input_bindgen::AMOTION_EVENT_ACTION_UP,
- &pointer_properties,
- Flags::empty(),
- )
- .is_ok());
- }
-
- #[test]
- fn multi_device_stream() {
- let mut verifier = InputVerifier::new("Test");
- let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
- assert!(verifier
- .process_movement(
- DeviceId(1),
- input_bindgen::AMOTION_EVENT_ACTION_DOWN,
- &pointer_properties,
- Flags::empty(),
- )
- .is_ok());
- assert!(verifier
- .process_movement(
- DeviceId(1),
- input_bindgen::AMOTION_EVENT_ACTION_MOVE,
- &pointer_properties,
- Flags::empty(),
- )
- .is_ok());
- assert!(verifier
- .process_movement(
- DeviceId(2),
- input_bindgen::AMOTION_EVENT_ACTION_DOWN,
- &pointer_properties,
- Flags::empty(),
- )
- .is_ok());
- assert!(verifier
- .process_movement(
- DeviceId(2),
- input_bindgen::AMOTION_EVENT_ACTION_MOVE,
- &pointer_properties,
- Flags::empty(),
- )
- .is_ok());
- assert!(verifier
- .process_movement(
- DeviceId(1),
- input_bindgen::AMOTION_EVENT_ACTION_UP,
- &pointer_properties,
- Flags::empty(),
- )
- .is_ok());
- }
-
- #[test]
- fn test_invalid_up() {
- let mut verifier = InputVerifier::new("Test");
- let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
- assert!(verifier
- .process_movement(
- DeviceId(1),
- input_bindgen::AMOTION_EVENT_ACTION_UP,
- &pointer_properties,
- Flags::empty(),
- )
- .is_err());
- }
-}
diff --git a/libs/input/rust/Android.bp b/libs/input/rust/Android.bp
new file mode 100644
index 0000000..018d199
--- /dev/null
+++ b/libs/input/rust/Android.bp
@@ -0,0 +1,72 @@
+// Copyright 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+rust_defaults {
+ name: "libinput_rust_defaults",
+ crate_name: "input",
+ srcs: ["lib.rs"],
+ host_supported: true,
+ rustlibs: [
+ "libbitflags",
+ "libcxx",
+ "libinput_bindgen",
+ "liblogger",
+ "liblog_rust",
+ "inputconstants-rust",
+ ],
+ whole_static_libs: [
+ "libinput_from_rust_to_cpp",
+ ],
+ shared_libs: [
+ "libbase",
+ ],
+}
+
+rust_library {
+ name: "libinput_rust",
+ defaults: ["libinput_rust_defaults"],
+}
+
+rust_ffi_static {
+ name: "libinput_rust_ffi",
+ defaults: ["libinput_rust_defaults"],
+}
+
+rust_test {
+ name: "libinput_rust_test",
+ defaults: ["libinput_rust_defaults"],
+ test_options: {
+ unit_test: true,
+ },
+ test_suites: ["device_tests"],
+ sanitize: {
+ hwaddress: true,
+ },
+}
+
+genrule {
+ name: "libinput_cxx_bridge_code",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) >> $(out)",
+ srcs: ["lib.rs"],
+ out: ["input_cxx_bridge_generated.cpp"],
+}
+
+genrule {
+ name: "libinput_cxx_bridge_header",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) --header >> $(out)",
+ srcs: ["lib.rs"],
+ out: ["input_cxx_bridge.rs.h"],
+}
diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs
new file mode 100644
index 0000000..a308c26
--- /dev/null
+++ b/libs/input/rust/input.rs
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Common definitions of the Android Input Framework in rust.
+
+use bitflags::bitflags;
+use std::fmt;
+
+/// The InputDevice ID.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub struct DeviceId(pub i32);
+
+/// A rust enum representation of a MotionEvent action.
+#[repr(u32)]
+pub enum MotionAction {
+ /// ACTION_DOWN
+ Down = input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ /// ACTION_UP
+ Up = input_bindgen::AMOTION_EVENT_ACTION_UP,
+ /// ACTION_MOVE
+ Move = input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ /// ACTION_CANCEL
+ Cancel = input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
+ /// ACTION_OUTSIDE
+ Outside = input_bindgen::AMOTION_EVENT_ACTION_OUTSIDE,
+ /// ACTION_POINTER_DOWN
+ PointerDown {
+ /// The index of the affected pointer.
+ action_index: usize,
+ } = input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN,
+ /// ACTION_POINTER_UP
+ PointerUp {
+ /// The index of the affected pointer.
+ action_index: usize,
+ } = input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP,
+ /// ACTION_HOVER_ENTER
+ HoverEnter = input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+ /// ACTION_HOVER_MOVE
+ HoverMove = input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
+ /// ACTION_HOVER_EXIT
+ HoverExit = input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT,
+ /// ACTION_SCROLL
+ Scroll = input_bindgen::AMOTION_EVENT_ACTION_SCROLL,
+ /// ACTION_BUTTON_PRESS
+ ButtonPress = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+ /// ACTION_BUTTON_RELEASE
+ ButtonRelease = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+}
+
+impl fmt::Display for MotionAction {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ MotionAction::Down => write!(f, "DOWN"),
+ MotionAction::Up => write!(f, "UP"),
+ MotionAction::Move => write!(f, "MOVE"),
+ MotionAction::Cancel => write!(f, "CANCEL"),
+ MotionAction::Outside => write!(f, "OUTSIDE"),
+ MotionAction::PointerDown { action_index } => {
+ write!(f, "POINTER_DOWN({})", action_index)
+ }
+ MotionAction::PointerUp { action_index } => write!(f, "POINTER_UP({})", action_index),
+ MotionAction::HoverMove => write!(f, "HOVER_MOVE"),
+ MotionAction::Scroll => write!(f, "SCROLL"),
+ MotionAction::HoverEnter => write!(f, "HOVER_ENTER"),
+ MotionAction::HoverExit => write!(f, "HOVER_EXIT"),
+ MotionAction::ButtonPress => write!(f, "BUTTON_PRESS"),
+ MotionAction::ButtonRelease => write!(f, "BUTTON_RELEASE"),
+ }
+ }
+}
+
+impl From<u32> for MotionAction {
+ fn from(action: u32) -> Self {
+ let (action_masked, action_index) = MotionAction::breakdown_action(action);
+ match action_masked {
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN => MotionAction::Down,
+ input_bindgen::AMOTION_EVENT_ACTION_UP => MotionAction::Up,
+ input_bindgen::AMOTION_EVENT_ACTION_MOVE => MotionAction::Move,
+ input_bindgen::AMOTION_EVENT_ACTION_CANCEL => MotionAction::Cancel,
+ input_bindgen::AMOTION_EVENT_ACTION_OUTSIDE => MotionAction::Outside,
+ input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN => {
+ MotionAction::PointerDown { action_index }
+ }
+ input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP => {
+ MotionAction::PointerUp { action_index }
+ }
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER => MotionAction::HoverEnter,
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE => MotionAction::HoverMove,
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT => MotionAction::HoverExit,
+ input_bindgen::AMOTION_EVENT_ACTION_SCROLL => MotionAction::Scroll,
+ input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS => MotionAction::ButtonPress,
+ input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE => MotionAction::ButtonRelease,
+ _ => panic!("Unknown action: {}", action),
+ }
+ }
+}
+
+impl MotionAction {
+ fn breakdown_action(action: u32) -> (u32, usize) {
+ let action_masked = action & input_bindgen::AMOTION_EVENT_ACTION_MASK;
+ let index = (action & input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
+ >> input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+ (action_masked, index.try_into().unwrap())
+ }
+}
+
+bitflags! {
+ /// MotionEvent flags.
+ pub struct MotionFlags: i32 {
+ /// FLAG_CANCELED
+ const CANCELED = input_bindgen::AMOTION_EVENT_FLAG_CANCELED;
+ }
+}
diff --git a/libs/input/rust/input_verifier.rs b/libs/input/rust/input_verifier.rs
new file mode 100644
index 0000000..1cc1129
--- /dev/null
+++ b/libs/input/rust/input_verifier.rs
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Contains the InputVerifier, used to validate a stream of input events.
+
+use crate::ffi::RustPointerProperties;
+use crate::input::{DeviceId, MotionAction, MotionFlags};
+use log::info;
+use std::collections::HashMap;
+use std::collections::HashSet;
+
+/// The InputVerifier is used to validate a stream of input events.
+pub struct InputVerifier {
+ name: String,
+ should_log: bool,
+ touching_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>,
+}
+
+impl InputVerifier {
+ /// Create a new InputVerifier.
+ pub fn new(name: &str, should_log: bool) -> Self {
+ logger::init(
+ logger::Config::default()
+ .with_tag_on_device("InputVerifier")
+ .with_min_level(log::Level::Trace),
+ );
+ Self { name: name.to_owned(), should_log, touching_pointer_ids_by_device: HashMap::new() }
+ }
+
+ /// Process a pointer movement event from an InputDevice.
+ /// If the event is not valid, we return an error string that describes the issue.
+ pub fn process_movement(
+ &mut self,
+ device_id: DeviceId,
+ action: u32,
+ pointer_properties: &[RustPointerProperties],
+ flags: MotionFlags,
+ ) -> Result<(), String> {
+ if self.should_log {
+ info!(
+ "Processing {} for device {:?} ({} pointer{}) on {}",
+ MotionAction::from(action).to_string(),
+ device_id,
+ pointer_properties.len(),
+ if pointer_properties.len() == 1 { "" } else { "s" },
+ self.name
+ );
+ }
+
+ match action.into() {
+ MotionAction::Down => {
+ let it = self
+ .touching_pointer_ids_by_device
+ .entry(device_id)
+ .or_insert_with(HashSet::new);
+ let pointer_id = pointer_properties[0].id;
+ if it.contains(&pointer_id) {
+ return Err(format!(
+ "{}: Invalid DOWN event - pointers already down for device {:?}: {:?}",
+ self.name, device_id, it
+ ));
+ }
+ it.insert(pointer_id);
+ }
+ MotionAction::PointerDown { action_index } => {
+ if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
+ return Err(format!(
+ "{}: Received POINTER_DOWN but no pointers are currently down \
+ for device {:?}",
+ self.name, device_id
+ ));
+ }
+ let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
+ let pointer_id = pointer_properties[action_index].id;
+ if it.contains(&pointer_id) {
+ return Err(format!(
+ "{}: Pointer with id={} not found in the properties",
+ self.name, pointer_id
+ ));
+ }
+ it.insert(pointer_id);
+ }
+ MotionAction::Move => {
+ if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
+ return Err(format!(
+ "{}: ACTION_MOVE touching pointers don't match",
+ self.name
+ ));
+ }
+ }
+ MotionAction::PointerUp { action_index } => {
+ if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
+ return Err(format!(
+ "{}: Received POINTER_UP but no pointers are currently down for device \
+ {:?}",
+ self.name, device_id
+ ));
+ }
+ let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
+ let pointer_id = pointer_properties[action_index].id;
+ it.remove(&pointer_id);
+ }
+ MotionAction::Up => {
+ if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
+ return Err(format!(
+ "{} Received ACTION_UP but no pointers are currently down for device {:?}",
+ self.name, device_id
+ ));
+ }
+ let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
+ if it.len() != 1 {
+ return Err(format!(
+ "{}: Got ACTION_UP, but we have pointers: {:?} for device {:?}",
+ self.name, it, device_id
+ ));
+ }
+ let pointer_id = pointer_properties[0].id;
+ if !it.contains(&pointer_id) {
+ return Err(format!(
+ "{}: Got ACTION_UP, but pointerId {} is not touching. Touching pointers:\
+ {:?} for device {:?}",
+ self.name, pointer_id, it, device_id
+ ));
+ }
+ it.clear();
+ }
+ MotionAction::Cancel => {
+ if flags.contains(MotionFlags::CANCELED) {
+ return Err(format!(
+ "{}: For ACTION_CANCEL, must set FLAG_CANCELED",
+ self.name
+ ));
+ }
+ if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
+ return Err(format!(
+ "{}: Got ACTION_CANCEL, but the pointers don't match. \
+ Existing pointers: {:?}",
+ self.name, self.touching_pointer_ids_by_device
+ ));
+ }
+ self.touching_pointer_ids_by_device.remove(&device_id);
+ }
+ _ => return Ok(()),
+ }
+ Ok(())
+ }
+
+ fn ensure_touching_pointers_match(
+ &self,
+ device_id: DeviceId,
+ pointer_properties: &[RustPointerProperties],
+ ) -> bool {
+ let Some(pointers) = self.touching_pointer_ids_by_device.get(&device_id) else {
+ return false;
+ };
+
+ for pointer_property in pointer_properties.iter() {
+ let pointer_id = pointer_property.id;
+ if !pointers.contains(&pointer_id) {
+ return false;
+ }
+ }
+ true
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::input_verifier::InputVerifier;
+ use crate::DeviceId;
+ use crate::MotionFlags;
+ use crate::RustPointerProperties;
+ #[test]
+ fn single_pointer_stream() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ input_bindgen::AMOTION_EVENT_ACTION_UP,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ }
+
+ #[test]
+ fn multi_device_stream() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(2),
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(2),
+ input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ input_bindgen::AMOTION_EVENT_ACTION_UP,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ }
+
+ #[test]
+ fn test_invalid_up() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ input_bindgen::AMOTION_EVENT_ACTION_UP,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_err());
+ }
+}
diff --git a/libs/input/rust/lib.rs b/libs/input/rust/lib.rs
new file mode 100644
index 0000000..25b2ecb
--- /dev/null
+++ b/libs/input/rust/lib.rs
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! The rust component of libinput.
+
+mod input;
+mod input_verifier;
+
+pub use input::{DeviceId, MotionAction, MotionFlags};
+pub use input_verifier::InputVerifier;
+
+#[cxx::bridge(namespace = "android::input")]
+mod ffi {
+ #[namespace = "android"]
+ unsafe extern "C++" {
+ include!("ffi/FromRustToCpp.h");
+ fn shouldLog(tag: &str) -> bool;
+ }
+
+ #[namespace = "android::input::verifier"]
+ extern "Rust" {
+ /// Used to validate the incoming motion stream.
+ /// This class is not thread-safe.
+ /// State is stored in the "InputVerifier" object
+ /// that can be created via the 'create' method.
+ /// Usage:
+ ///
+ /// ```ignore
+ /// Box<InputVerifier> verifier = create("inputChannel name");
+ /// result = process_movement(verifier, ...);
+ /// if (result) {
+ /// crash(result.error_message());
+ /// }
+ /// ```
+ type InputVerifier;
+ fn create(name: String) -> Box<InputVerifier>;
+ fn process_movement(
+ verifier: &mut InputVerifier,
+ device_id: i32,
+ action: u32,
+ pointer_properties: &[RustPointerProperties],
+ flags: i32,
+ ) -> String;
+ }
+
+ #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+ pub struct RustPointerProperties {
+ pub id: i32,
+ }
+}
+
+use crate::ffi::RustPointerProperties;
+
+fn process_movement(
+ verifier: &mut InputVerifier,
+ device_id: i32,
+ action: u32,
+ pointer_properties: &[RustPointerProperties],
+ flags: i32,
+) -> String {
+ let result = verifier.process_movement(
+ DeviceId(device_id),
+ action,
+ pointer_properties,
+ MotionFlags::from_bits(flags).unwrap(),
+ );
+ match result {
+ Ok(()) => "".to_string(),
+ Err(e) => e,
+ }
+}
+
+fn create(name: String) -> Box<InputVerifier> {
+ Box::new(InputVerifier::new(&name, ffi::shouldLog("InputVerifierLogEvents")))
+}