blob: 2e05a631498411474f6d13e4c98e9185c871bd70 [file] [log] [blame]
Siarhei Vishniakou5c02a712023-05-15 15:45:02 -07001/*
2 * Copyright 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! Validate the incoming motion stream.
18//! This class is not thread-safe.
19//! State is stored in the "InputVerifier" object
20//! that can be created via the 'create' method.
21//! Usage:
22//! Box<InputVerifier> verifier = create("inputChannel name");
23//! result = process_movement(verifier, ...);
24//! if (result) {
25//! crash(result.error_message());
26//! }
27
28use std::collections::HashMap;
29use std::collections::HashSet;
30
31use bitflags::bitflags;
32use log::info;
33
34#[cxx::bridge(namespace = "android::input")]
35mod ffi {
36 #[namespace = "android"]
37 unsafe extern "C++" {
38 include!("ffi/FromRustToCpp.h");
39 fn shouldLog(tag: &str) -> bool;
40 }
41 #[namespace = "android::input::verifier"]
42 extern "Rust" {
43 type InputVerifier;
44
45 fn create(name: String) -> Box<InputVerifier>;
46 fn process_movement(
47 verifier: &mut InputVerifier,
48 device_id: i32,
49 action: u32,
50 pointer_properties: &[RustPointerProperties],
51 flags: i32,
52 ) -> String;
53 }
54
55 pub struct RustPointerProperties {
56 id: i32,
57 }
58}
59
60use crate::ffi::shouldLog;
61use crate::ffi::RustPointerProperties;
62
63#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
64struct DeviceId(i32);
65
66fn process_movement(
67 verifier: &mut InputVerifier,
68 device_id: i32,
69 action: u32,
70 pointer_properties: &[RustPointerProperties],
71 flags: i32,
72) -> String {
73 let result = verifier.process_movement(
74 DeviceId(device_id),
75 action,
76 pointer_properties,
77 Flags::from_bits(flags).unwrap(),
78 );
79 match result {
80 Ok(()) => "".to_string(),
81 Err(e) => e,
82 }
83}
84
85fn create(name: String) -> Box<InputVerifier> {
86 Box::new(InputVerifier::new(&name))
87}
88
89#[repr(u32)]
90enum MotionAction {
91 Down = input_bindgen::AMOTION_EVENT_ACTION_DOWN,
92 Up = input_bindgen::AMOTION_EVENT_ACTION_UP,
93 Move = input_bindgen::AMOTION_EVENT_ACTION_MOVE,
94 Cancel = input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
95 Outside = input_bindgen::AMOTION_EVENT_ACTION_OUTSIDE,
96 PointerDown { action_index: usize } = input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN,
97 PointerUp { action_index: usize } = input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP,
98 HoverEnter = input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
99 HoverMove = input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
100 HoverExit = input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT,
101 Scroll = input_bindgen::AMOTION_EVENT_ACTION_SCROLL,
102 ButtonPress = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
103 ButtonRelease = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE,
104}
105
106fn get_action_index(action: u32) -> usize {
107 let index = (action & input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
108 >> input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
109 index.try_into().unwrap()
110}
111
112impl From<u32> for MotionAction {
113 fn from(action: u32) -> Self {
114 let action_masked = action & input_bindgen::AMOTION_EVENT_ACTION_MASK;
115 let action_index = get_action_index(action);
116 match action_masked {
117 input_bindgen::AMOTION_EVENT_ACTION_DOWN => MotionAction::Down,
118 input_bindgen::AMOTION_EVENT_ACTION_UP => MotionAction::Up,
119 input_bindgen::AMOTION_EVENT_ACTION_MOVE => MotionAction::Move,
120 input_bindgen::AMOTION_EVENT_ACTION_CANCEL => MotionAction::Cancel,
121 input_bindgen::AMOTION_EVENT_ACTION_OUTSIDE => MotionAction::Outside,
122 input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN => {
123 MotionAction::PointerDown { action_index }
124 }
125 input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP => {
126 MotionAction::PointerUp { action_index }
127 }
128 input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER => MotionAction::HoverEnter,
129 input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE => MotionAction::HoverMove,
130 input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT => MotionAction::HoverExit,
131 input_bindgen::AMOTION_EVENT_ACTION_SCROLL => MotionAction::Scroll,
132 input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS => MotionAction::ButtonPress,
133 input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE => MotionAction::ButtonRelease,
134 _ => panic!("Unknown action: {}", action),
135 }
136 }
137}
138
139bitflags! {
140 struct Flags: i32 {
141 const CANCELED = input_bindgen::AMOTION_EVENT_FLAG_CANCELED;
142 }
143}
144
145fn motion_action_to_string(action: u32) -> String {
146 match action.into() {
147 MotionAction::Down => "DOWN".to_string(),
148 MotionAction::Up => "UP".to_string(),
149 MotionAction::Move => "MOVE".to_string(),
150 MotionAction::Cancel => "CANCEL".to_string(),
151 MotionAction::Outside => "OUTSIDE".to_string(),
152 MotionAction::PointerDown { action_index } => {
153 format!("POINTER_DOWN({})", action_index)
154 }
155 MotionAction::PointerUp { action_index } => {
156 format!("POINTER_UP({})", action_index)
157 }
158 MotionAction::HoverMove => "HOVER_MOVE".to_string(),
159 MotionAction::Scroll => "SCROLL".to_string(),
160 MotionAction::HoverEnter => "HOVER_ENTER".to_string(),
161 MotionAction::HoverExit => "HOVER_EXIT".to_string(),
162 MotionAction::ButtonPress => "BUTTON_PRESS".to_string(),
163 MotionAction::ButtonRelease => "BUTTON_RELEASE".to_string(),
164 }
165}
166
167/**
168 * Log all of the movements that are sent to this verifier. Helps to identify the streams that lead
169 * to inconsistent events.
170 * Enable this via "adb shell setprop log.tag.InputVerifierLogEvents DEBUG"
171 */
172fn log_events() -> bool {
173 shouldLog("InputVerifierLogEvents")
174}
175
176struct InputVerifier {
177 name: String,
178 touching_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>,
179}
180
181impl InputVerifier {
182 fn new(name: &str) -> Self {
183 logger::init(
184 logger::Config::default()
185 .with_tag_on_device("InputVerifier")
186 .with_min_level(log::Level::Trace),
187 );
188 Self { name: name.to_owned(), touching_pointer_ids_by_device: HashMap::new() }
189 }
190
191 fn process_movement(
192 &mut self,
193 device_id: DeviceId,
194 action: u32,
195 pointer_properties: &[RustPointerProperties],
196 flags: Flags,
197 ) -> Result<(), String> {
198 if log_events() {
199 info!(
200 "Processing {} for device {:?} ({} pointer{}) on {}",
201 motion_action_to_string(action),
202 device_id,
203 pointer_properties.len(),
204 if pointer_properties.len() == 1 { "" } else { "s" },
205 self.name
206 );
207 }
208
209 match action.into() {
210 MotionAction::Down => {
211 let it = self
212 .touching_pointer_ids_by_device
213 .entry(device_id)
214 .or_insert_with(HashSet::new);
215 let pointer_id = pointer_properties[0].id;
216 if it.contains(&pointer_id) {
217 return Err(format!(
218 "{}: Invalid DOWN event - pointers already down for device {:?}: {:?}",
219 self.name, device_id, it
220 ));
221 }
222 it.insert(pointer_id);
223 }
224 MotionAction::PointerDown { action_index } => {
225 if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
226 return Err(format!(
227 "{}: Received POINTER_DOWN but no pointers are currently down \
228 for device {:?}",
229 self.name, device_id
230 ));
231 }
232 let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
233 let pointer_id = pointer_properties[action_index].id;
234 if it.contains(&pointer_id) {
235 return Err(format!(
236 "{}: Pointer with id={} not found in the properties",
237 self.name, pointer_id
238 ));
239 }
240 it.insert(pointer_id);
241 }
242 MotionAction::Move => {
243 if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
244 return Err(format!(
245 "{}: ACTION_MOVE touching pointers don't match",
246 self.name
247 ));
248 }
249 }
250 MotionAction::PointerUp { action_index } => {
251 if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
252 return Err(format!(
253 "{}: Received POINTER_UP but no pointers are currently down for device \
254 {:?}",
255 self.name, device_id
256 ));
257 }
258 let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
259 let pointer_id = pointer_properties[action_index].id;
260 it.remove(&pointer_id);
261 }
262 MotionAction::Up => {
263 if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
264 return Err(format!(
265 "{} Received ACTION_UP but no pointers are currently down for device {:?}",
266 self.name, device_id
267 ));
268 }
269 let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
270 if it.len() != 1 {
271 return Err(format!(
272 "{}: Got ACTION_UP, but we have pointers: {:?} for device {:?}",
273 self.name, it, device_id
274 ));
275 }
276 let pointer_id = pointer_properties[0].id;
277 if !it.contains(&pointer_id) {
278 return Err(format!(
279 "{}: Got ACTION_UP, but pointerId {} is not touching. Touching pointers:\
280 {:?} for device {:?}",
281 self.name, pointer_id, it, device_id
282 ));
283 }
284 it.clear();
285 }
286 MotionAction::Cancel => {
287 if flags.contains(Flags::CANCELED) {
288 return Err(format!(
289 "{}: For ACTION_CANCEL, must set FLAG_CANCELED",
290 self.name
291 ));
292 }
293 if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
294 return Err(format!(
295 "{}: Got ACTION_CANCEL, but the pointers don't match. \
296 Existing pointers: {:?}",
297 self.name, self.touching_pointer_ids_by_device
298 ));
299 }
300 self.touching_pointer_ids_by_device.remove(&device_id);
301 }
302 _ => return Ok(()),
303 }
304 Ok(())
305 }
306
307 fn ensure_touching_pointers_match(
308 &self,
309 device_id: DeviceId,
310 pointer_properties: &[RustPointerProperties],
311 ) -> bool {
312 let Some(pointers) = self.touching_pointer_ids_by_device.get(&device_id) else {
313 return false;
314 };
315
316 for pointer_property in pointer_properties.iter() {
317 let pointer_id = pointer_property.id;
318 if !pointers.contains(&pointer_id) {
319 return false;
320 }
321 }
322 true
323 }
324}
325
326#[cfg(test)]
327mod tests {
328 use crate::DeviceId;
329 use crate::Flags;
330 use crate::InputVerifier;
331 use crate::RustPointerProperties;
332 #[test]
333 fn single_pointer_stream() {
334 let mut verifier = InputVerifier::new("Test");
335 let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
336 assert!(verifier
337 .process_movement(
338 DeviceId(1),
339 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
340 &pointer_properties,
341 Flags::empty(),
342 )
343 .is_ok());
344 assert!(verifier
345 .process_movement(
346 DeviceId(1),
347 input_bindgen::AMOTION_EVENT_ACTION_MOVE,
348 &pointer_properties,
349 Flags::empty(),
350 )
351 .is_ok());
352 assert!(verifier
353 .process_movement(
354 DeviceId(1),
355 input_bindgen::AMOTION_EVENT_ACTION_UP,
356 &pointer_properties,
357 Flags::empty(),
358 )
359 .is_ok());
360 }
361
362 #[test]
363 fn multi_device_stream() {
364 let mut verifier = InputVerifier::new("Test");
365 let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
366 assert!(verifier
367 .process_movement(
368 DeviceId(1),
369 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
370 &pointer_properties,
371 Flags::empty(),
372 )
373 .is_ok());
374 assert!(verifier
375 .process_movement(
376 DeviceId(1),
377 input_bindgen::AMOTION_EVENT_ACTION_MOVE,
378 &pointer_properties,
379 Flags::empty(),
380 )
381 .is_ok());
382 assert!(verifier
383 .process_movement(
384 DeviceId(2),
385 input_bindgen::AMOTION_EVENT_ACTION_DOWN,
386 &pointer_properties,
387 Flags::empty(),
388 )
389 .is_ok());
390 assert!(verifier
391 .process_movement(
392 DeviceId(2),
393 input_bindgen::AMOTION_EVENT_ACTION_MOVE,
394 &pointer_properties,
395 Flags::empty(),
396 )
397 .is_ok());
398 assert!(verifier
399 .process_movement(
400 DeviceId(1),
401 input_bindgen::AMOTION_EVENT_ACTION_UP,
402 &pointer_properties,
403 Flags::empty(),
404 )
405 .is_ok());
406 }
407
408 #[test]
409 fn test_invalid_up() {
410 let mut verifier = InputVerifier::new("Test");
411 let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
412 assert!(verifier
413 .process_movement(
414 DeviceId(1),
415 input_bindgen::AMOTION_EVENT_ACTION_UP,
416 &pointer_properties,
417 Flags::empty(),
418 )
419 .is_err());
420 }
421}