blob: 2e05a631498411474f6d13e4c98e9185c871bd70 [file] [log] [blame]
/*
* 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());
}
}