You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
439 lines
16 KiB
439 lines
16 KiB
/*
|
|
* 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")]
|
|
#[allow(unsafe_op_in_unsafe_fn)]
|
|
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],
|
|
//------rk modify start------
|
|
flags: u32,
|
|
//------rk modify end------
|
|
) -> 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],
|
|
//------rk modify start------
|
|
flags: u32,
|
|
//------rk modify end------
|
|
) -> 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! {
|
|
//------rk modify start------
|
|
struct Flags: u32 {
|
|
const CANCELED = input_bindgen::AMOTION_EVENT_FLAG_CANCELED as u32;
|
|
/// FLAG_WINDOW_IS_OBSCURED
|
|
const WINDOW_IS_OBSCURED = input_bindgen::AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
|
|
/// FLAG_WINDOW_IS_PARTIALLY_OBSCURED
|
|
const WINDOW_IS_PARTIALLY_OBSCURED =
|
|
input_bindgen::AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
|
|
/// FLAG_IS_ACCESSIBILITY_EVENT
|
|
const IS_ACCESSIBILITY_EVENT =
|
|
input_bindgen::AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT;
|
|
/// FLAG_NO_FOCUS_CHANGE
|
|
const NO_FOCUS_CHANGE = input_bindgen::AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE;
|
|
//------rk modify end------
|
|
}
|
|
}
|
|
|
|
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());
|
|
}
|
|
}
|