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")))
+}